aGrUM 3.1.1
a C++ library for (probabilistic) graphical models
databaseTable.cpp
Go to the documentation of this file.
1/****************************************************************************
2 * This file is part of the aGrUM/pyAgrum library. *
3 * *
4 * Copyright (c) 2005-2026 by *
5 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
6 * - Christophe GONZALES(_at_AMU) *
7 * *
8 * The aGrUM/pyAgrum library is free software; you can redistribute it *
9 * and/or modify it under the terms of either : *
10 * *
11 * - the GNU Lesser General Public License as published by *
12 * the Free Software Foundation, either version 3 of the License, *
13 * or (at your option) any later version, *
14 * - the MIT license (MIT), *
15 * - or both in dual license, as here. *
16 * *
17 * (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) *
18 * *
19 * This aGrUM/pyAgrum library is distributed in the hope that it will be *
20 * useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
21 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS *
22 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
25 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
26 * OTHER DEALINGS IN THE SOFTWARE. *
27 * *
28 * See LICENCES for more details. *
29 * *
30 * SPDX-FileCopyrightText: Copyright 2005-2026 *
31 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
32 * - Christophe GONZALES(_at_AMU) *
33 * SPDX-License-Identifier: LGPL-3.0-or-later OR MIT *
34 * *
35 * Contact : info_at_agrum_dot_org *
36 * homepage : http://agrum.gitlab.io *
37 * gitlab : https://gitlab.com/agrumery/agrum *
38 * *
39 ****************************************************************************/
40
41
47
49
50#ifndef DOXYGEN_SHOULD_SKIP_THIS
51
53# ifdef GUM_NO_INLINE
55# endif /* GUM_NO_INLINE */
56
57namespace gum::learning {
58
59 // default constructor
60 DatabaseTable::DatabaseTable(const typename DatabaseTable::MissingValType& missing_symbols,
61 const DBTranslatorSet& translators) :
62 IDatabaseTable< DBTranslatedValue >(missing_symbols, std::vector< std::string >()),
63 _translators_(translators) {
64 if (translators.size()) {
65 // set the variables names according to those of the translators
66 std::vector< std::string > var_names(translators.size());
67 for (std::size_t i = std::size_t(0), size = translators.size(); i < size; ++i) {
68 var_names[i] = _translators_.translator(i).variable()->name();
69 }
70 setVariableNames(var_names, false);
71 }
72
73 GUM_CONSTRUCTOR(DatabaseTable);
74 }
75
76 // default constructor
77 DatabaseTable::DatabaseTable(const DBTranslatorSet& translators) :
78 IDatabaseTable< DBTranslatedValue >(std::vector< std::string >(),
79 std::vector< std::string >()),
80 _translators_(translators) {
81 if (translators.size()) {
82 // set the variables names according to those of the translators
83 std::vector< std::string > var_names(translators.size());
84 for (std::size_t i = std::size_t(0), size = translators.size(); i < size; ++i) {
85 var_names[i] = _translators_.translator(i).variable()->name();
86 }
87 setVariableNames(var_names, false);
88 }
89
90 GUM_CONSTRUCTOR(DatabaseTable);
91 }
92
93 // virtual copy constructor
94 DatabaseTable* DatabaseTable::clone() const { return new DatabaseTable(*this); }
95
96 // copy operator
97 DatabaseTable& DatabaseTable::operator=(const DatabaseTable& from) {
98 if (this != &from) {
99 IDatabaseTable< DBTranslatedValue >::operator=(from);
100 _translators_ = from._translators_;
101 _ignored_cols_ = from._ignored_cols_;
102 }
103
104 return *this;
105 }
106
107 // move constructor
108 DatabaseTable& DatabaseTable::operator=(DatabaseTable&& from) noexcept {
109 if (this != &from) {
110 IDatabaseTable< DBTranslatedValue >::operator=(std::move(from));
111 _translators_ = std::move(from._translators_);
112 _ignored_cols_ = std::move(from._ignored_cols_);
113 }
114
115 return *this;
116 }
117
119 std::size_t DatabaseTable::insertTranslator(const DBTranslator& translator,
120 const std::size_t input_column,
121 const bool unique_column) {
122 // check that there is no ignored_column corresponding to column
123 if (_ignored_cols_.exists(input_column))
125 "Column " << input_column << " is marked as being ignored. "
126 << "So it is forbidden to create a translator for that column.")
127
128 // reserve some place for the new column in the records of the database
129 const std::size_t new_size = this->nbVariables() + 1;
130
131 // create the lambda for reserving some memory for the new column
132 // and the one that undoes what it performed if some thread executing
133 // it raised an exception
134 auto reserve_lambda = [this, new_size](std::size_t begin, std::size_t end, std::size_t index) {
135 for (std::size_t i = begin; i < end; ++i) {
136 this->rows_[i].row().reserve(new_size);
137 }
138 };
139
140 auto undo_reserve_lambda = [](std::size_t begin, std::size_t end, std::size_t index) {};
141
142 // launch the threads executing the lambdas
143 this->_threadProcessDatabase_(reserve_lambda, undo_reserve_lambda);
144
145 // insert the translator into the translator set
146 const std::size_t pos = _translators_.insertTranslator(translator, input_column, unique_column);
147
148 // insert the name of the translator's variable to the set of variable names
149 try {
150 this->variable_names_.push_back(translator.variable()->name());
151 } catch (...) {
152 _translators_.eraseTranslator(pos);
153 throw;
154 }
155
156 // if the databaseTable is not empty, fill the column of the database
157 // corresponding to the translator with missing values
158 if (!IDatabaseTable< DBTranslatedValue >::empty()) {
159 const DBTranslatedValue missing = _translators_[pos].missingValue();
160
161 // create the lambda for adding a new column filled wih a missing value
162 auto fill_lambda = [this, missing](std::size_t begin, std::size_t end, std::size_t index) {
163 std::size_t i = begin;
164 try {
165 for (; i < end; ++i) {
166 this->rows_[i].row().push_back(missing);
167 }
168 } catch (...) {
169 for (std::size_t j = begin; j < i; ++j) {
170 this->rows_[j].row().pop_back();
171 }
172 throw;
173 }
174 // indicate that, now, all the rows contain missing values (at least the new column)
175 for (i = begin; i < end; ++i) {
176 this->has_row_missing_val_[i] = IsMissing::True;
177 }
178 };
179
180 auto undo_fill_lambda = [this](std::size_t begin, std::size_t end, std::size_t index) {
181 for (std::size_t i = begin; i < end; ++i) {
182 auto& row = this->rows_[i].row();
183 row.pop_back();
184
185 // recompute whether the row contains a missing value
186 bool has_missing_value = false;
187 for (std::size_t j = 0, endj = row.size(); j < endj; ++j) {
188 if (_translators_[j].isMissingValue(row[j])) {
189 has_missing_value = true;
190 break;
191 }
192 }
193 this->has_row_missing_val_[i] = has_missing_value ? IsMissing::True : IsMissing::False;
194 }
195 };
196
197 // launch the threads executing the lambdas
198 this->_threadProcessDatabase_(fill_lambda, undo_fill_lambda);
199 }
200
201 return pos;
202 }
203
205 std::size_t DatabaseTable::insertTranslator(const Variable& var,
206 const std::size_t input_column,
207 const bool unique_column) {
208 // check that there is no ignored_column corresponding to column
209 if (_ignored_cols_.exists(input_column))
211 "Column " << input_column << " is marked as being ignored. "
212 << "So it is forbidden to create a translator for that column.")
213
214 // if the databaseTable is not empty, we should fill the column of the
215 // database corresponding to the new translator with missing values. But, the
216 // current method assumes that the list of missing values is empty. Hence, it
217 // should raise an exception
218 if (!IDatabaseTable< DBTranslatedValue >::empty()) {
220 "inserting a new translator into a database creates a new column "
221 << "with missing values. However, you did not define any symbol for "
222 << "such values.")
223 }
224
225 // reserve some place for the new column in the records of the database
226 const std::size_t new_size = this->nbVariables() + 1;
227
228 // create the lambda for reserving some memory for the new column
229 // and the one that undoes what it performed if some thread executing
230 // it raised an exception
231 auto reserve_lambda = [this, new_size](std::size_t begin, std::size_t end, std::size_t index) {
232 for (std::size_t i = begin; i < end; ++i)
233 this->rows_[i].row().reserve(new_size);
234 };
235
236 auto undo_reserve_lambda = [](std::size_t begin, std::size_t end, std::size_t index) {};
237
238 // launch the threads executing the lambdas
239 this->_threadProcessDatabase_(reserve_lambda, undo_reserve_lambda);
240
241 // insert the translator into the translator set
242 const std::size_t pos = _translators_.insertTranslator(var, input_column, unique_column);
243
244 // insert the name of the translator's variable to the set of variable names
245 try {
246 this->variable_names_.push_back(var.name());
247 } catch (...) {
248 _translators_.eraseTranslator(pos);
249 throw;
250 }
251
252 return pos;
253 }
254
256 std::size_t DatabaseTable::insertTranslator(const Variable& var,
257 const std::size_t input_column,
258 const std::vector< std::string >& missing_symbols,
259 const bool unique_column) {
260 // check that there is no ignored_column corresponding to column
261 if (_ignored_cols_.exists(input_column))
263 "Column " << input_column << " is marked as being ignored. "
264 << "So it is forbidden to create a translator for that column.")
265
266 // reserve some place for the new column in the records of the database
267 const std::size_t new_size = this->nbVariables() + 1;
268
269 // create the lambda for reserving some memory for the new column
270 // and the one that undoes what it performed if some thread executing
271 // it raised an exception
272 auto reserve_lambda
273 = [this, new_size](std::size_t begin, std::size_t end, std::size_t index) -> void {
274 for (std::size_t i = begin; i < end; ++i)
275 this->rows_[i].row().reserve(new_size);
276 };
277
278 auto undo_reserve_lambda = [](std::size_t begin, std::size_t end, std::size_t index) -> void {};
279
280 // launch the threads executing the lambdas
281 this->_threadProcessDatabase_(reserve_lambda, undo_reserve_lambda);
282
283 // insert the translator into the translator set
284 const std::size_t pos
285 = _translators_.insertTranslator(var, input_column, missing_symbols, unique_column);
286
287 // insert the name of the translator's variable to the set of variable names
288 try {
289 this->variable_names_.push_back(var.name());
290 } catch (...) {
291 _translators_.eraseTranslator(pos);
292 throw;
293 }
294
295 // if the databaseTable is not empty, fill the column of the database
296 // corresponding to the translator with missing values
297 if (!IDatabaseTable< DBTranslatedValue >::empty()) {
298 const DBTranslatedValue missing = _translators_[pos].missingValue();
299
300 // create the lambda for adding a new column filled wih a missing value
301 auto fill_lambda
302 = [this, missing](std::size_t begin, std::size_t end, std::size_t index) -> void {
303 std::size_t i = begin;
304 try {
305 for (; i < end; ++i) {
306 this->rows_[i].row().push_back(missing);
307 }
308 } catch (...) {
309 for (std::size_t j = begin; j < i; ++j) {
310 this->rows_[j].row().pop_back();
311 }
312 throw;
313 }
314 // indicate that, now, all the rows contain missing values (at least the new column)
315 for (i = begin; i < end; ++i) {
316 this->has_row_missing_val_[i] = IsMissing::True;
317 }
318 };
319
320 auto undo_fill_lambda
321 = [this](std::size_t begin, std::size_t end, std::size_t index) -> void {
322 for (std::size_t i = begin; i < end; ++i) {
323 auto& row = this->rows_[i].row();
324 row.pop_back();
325
326 // recompute whether the row contains a missing value
327 bool has_missing_value = false;
328 for (std::size_t j = 0, endj = row.size(); j < endj; ++j) {
329 if (_translators_[j].isMissingValue(row[j])) {
330 has_missing_value = true;
331 break;
332 }
333 }
334 this->has_row_missing_val_[i] = has_missing_value ? IsMissing::True : IsMissing::False;
335 }
336 };
337
338 // launch the threads executing the lambdas
339 this->_threadProcessDatabase_(fill_lambda, undo_fill_lambda);
340 }
341
342 return pos;
343 }
344
345 // erases the kth translator or all those parsing the kth column of
346 // the input dataset
347 void DatabaseTable::eraseTranslators(const std::size_t k, const bool k_is_input_col) {
348 for (const auto kk: _getKthIndices_(k, k_is_input_col)) {
349 // erase the translator of index kk and the corresponding variable
350 // name. If there remains no more translator in the translator set,
351 // rows_ should become empty
352 this->variable_names_.erase(this->variable_names_.begin() + kk);
353 if (this->variable_names_.empty()) {
354 IDatabaseTable< DBTranslatedValue >::eraseAllRows();
355 } else {
356 const std::size_t nb_trans = _translators_.size();
357
358 auto erase_lambda
359 = [this, nb_trans, kk](std::size_t begin, std::size_t end, std::size_t index) -> void {
360 for (std::size_t i = begin; i < end; ++i) {
361 auto& row = this->rows_[i].row();
362 if (this->_translators_.isMissingValue(row[kk], kk)) {
363 bool has_missing_val = false;
364 for (std::size_t j = std::size_t(0); j < nb_trans; ++j) {
365 if ((j != kk) && this->_translators_.isMissingValue(row[j], j)) {
366 has_missing_val = true;
367 break;
368 }
369 }
370 if (!has_missing_val) this->has_row_missing_val_[i] = IsMissing::False;
371 }
372 row.erase(row.begin() + kk);
373 }
374 };
375
376 auto undo_erase_lambda
377 = [](std::size_t begin, std::size_t end, std::size_t index) -> void {};
378
379 // launch the threads executing the lambdas
380 this->_threadProcessDatabase_(erase_lambda, undo_erase_lambda);
381 }
382 _translators_.eraseTranslator(kk);
383 }
384 }
385
387 void DatabaseTable::changeTranslator(DBTranslator& new_translator,
388 const std::size_t k,
389 const bool k_is_input_col) {
390 // get the index of the column in the database. If it is not found, indicate that
391 // the substitution is impossible
392 const auto db_k = _getKthIndices_(k, k_is_input_col);
393 if (db_k.empty()) {
394 GUM_ERROR(OutOfBounds, "the translator at position " << k << " cannot be found.");
395 }
396 const std::size_t kk = db_k[db_k.size() - 1];
397 if (kk >= _translators_.size()) {
398 GUM_ERROR(OutOfBounds, "the translator at position " << k << " cannot be found.");
399 }
400
401
402 // if the dataset does not contain any data, we can safely substitute the old translator
403 // by the new one
404 if (this->empty()) {
405 // keep into account the name of the new translator
406 this->variable_names_[kk] = new_translator.variable()->name();
407
408 // substitute int the stransltor's set the old translator by the new one
409 _translators_.changeTranslator(new_translator, kk);
410
411 return;
412 }
413
414 // get the translator and check that it is not lossy: as, here, there are some data,
415 // we cannot always ensure that there won't be some loss of information substituting
416 // one translator by another
417 DBTranslator& old_translator = _translators_[kk];
418 if (!old_translator.isLossless()) {
419 // for the moment, we consider that it is impossible to substitute lossy translators
420 // because we may have already lost information that are necessary for the new
421 // translator
423 "Lossy translators cannot yet be substituted by other translators");
424 }
425
426 const std::size_t nb_threads = this->nbProcessingThreads_();
427
428 // how missing values will be translated
429 std::pair< DBTranslatedValue, DBTranslatedValue > miss_mapping(old_translator.missingValue(),
430 new_translator.missingValue());
431
432 // Now, we should compute the mapping from the values and missing symbols of the old
433 // translator to those of the new one.
434
435 // When the database already contains some data, we must ensure that we will be able to
436 // substitute the old translator by the new one without loosing any information. Possible
437 // loss of information may occur in the following cases:
438 // 1/ if the set of missing symbols of the old translator is not a singleton and some of its
439 // missing symbols do not belong to the set of missing symbols of the new translator.
440 // In this case, the translation of this symbol by the new translator should either raise
441 // an exception because the new translator does not know how to handle it, or should
442 // produce a DBTranslatedValue if the new translator thinks this is an observed value.
443 // Now, the problem is that when observing a missing symbol in the database, we have no
444 // way to determine to which above case this should correspond. Hence the substitution
445 // cannot be made unambiguously.
446 // 2/ if the set of (non-missing) values of the old translator is not included in the one
447 // of the new translator
448 // If one of these cases occur, before performing the translation, we must parse the content
449 // of the database: if case 1/ obtains and if the database contains some missing symbols,
450 // then we cannot unambiguously substitute the old translator by the new one, hence an error.
451 // If case 2/ obtains, we must check that all the observed values currently stored into the
452 // database also belong to the set of values the new translator is capable of translating.
453 if (!this->empty()) {
454 // to test case 1, we first determine whether the dataset contains some
455 // missing values
456 bool has_missing_value = false;
457 {
458 std::vector< int > missing_values(nb_threads, 0);
459
460 // a lambda to parse all the translated values for missing symbols
461 auto get_lambda = [this, kk, &missing_values](std::size_t begin,
462 std::size_t end,
463 std::size_t index) -> void {
464 for (std::size_t i = begin; i < end; ++i) {
465 auto& row = this->rows_[i].row();
466 if (this->_translators_.isMissingValue(row[kk], kk)) {
467 missing_values[index] = 1;
468 return;
469 }
470 }
471 };
472
473 auto undo_get_lambda = [](std::size_t begin, std::size_t end, std::size_t index) -> void {};
474
475 // launch the threads executing the lambdas
476 this->_threadProcessDatabase_(get_lambda, undo_get_lambda);
477
478 // if has_missing_values has at least one value 1, there are missing values
479 for (const auto x: missing_values) {
480 if (x) {
481 has_missing_value = true;
482 break;
483 }
484 }
485 }
486
487 // test for case 1/
488 const auto old_missing_symbols = old_translator.missingSymbols();
489 const auto new_missing_symbols = new_translator.missingSymbols();
490 const bool multiple_missing_symbols = old_missing_symbols.size() > 1;
491 const bool old_miss_included = old_missing_symbols.isSubsetOrEqual(new_missing_symbols);
492 if (has_missing_value && multiple_missing_symbols && !old_miss_included) {
493 // here, we know that the the database contains missing values
494 // and we cannot unambiguously perform the translator's substitution
496 "it is impossible to substitute the translator because "
497 "the database contains some missing values that cannot be "
498 "substituted unambiguously");
499 }
500
501 // if the database contains some missing values, two cases can obtain:
502 // a/ old_miss_included is true, in which case all the old missing values
503 // will be translated as missing values in the new translator.
504 // In this case, there is no translation problem.
505 // b/ old_miss_included is false. In this case, we know that there is only
506 // one old missing symbol, which is not inluded in the set of missing
507 // symbols of the new translator. If we can translate its symbol as a
508 // "proper" value in the new translator, that's ok, otherwise we cannot
509 // perform the substitution.
510 if (has_missing_value && !old_miss_included) {
511 try {
512 new_translator.translate(*(old_translator.missingSymbols().begin()));
513 } catch (Exception const&) {
515 "it is impossible to substitute the translator because "
516 "the database contains some missing values that cannot be "
517 "substituted");
518 }
519 }
520
521 // compute the mapping of the missing symbol if this one does not corresponds
522 // to a missing value in the new translator
523 if (has_missing_value && !old_miss_included) {
524 miss_mapping.second = new_translator.translate(*(old_translator.missingSymbols().begin()));
525 }
526
527 // test for case 2/ (if the set of (non-missing) values of the old translator is
528 // not included in the one of the new translator)
529
530 // now, parse the database and check that all the values contained in the
531 // database can be translated
532 std::vector< int > unmapped(nb_threads, 0);
533
534 // a lambda to parse all the translated values
535 auto check_lambda
536 = [this, kk, &old_translator, &new_translator, &unmapped](std::size_t begin,
537 std::size_t end,
538 std::size_t index) -> void {
539 const auto old_miss = old_translator.missingValue().discr_val;
540 for (std::size_t i = begin; i < end; ++i) {
541 const auto& row = this->rows_[i].row();
542 if (row[kk].discr_val != old_miss) {
543 try {
544 new_translator.translate(old_translator.translateBack(row[kk]));
545 } catch (Exception const&) {
546 // ok, here, the translation is impossible
547 unmapped[index] = 1;
548 return;
549 }
550 }
551 }
552 };
553
554 auto undo_check_lambda = [](std::size_t begin, std::size_t end, std::size_t index) -> void {};
555
556 // launch the threads executing the lambdas
557 this->_threadProcessDatabase_(check_lambda, undo_check_lambda);
558
559 // if unmapped has at least one value 1, there are values that we don't know how to
560 // translate
561 for (const auto x: unmapped) {
562 if (x) {
564 "The database contains some values that cannot be translated "
565 "using the new translator");
566 }
567 }
568 }
569
570 // here, we know that we can perform the translator's substitution, so
571 // let's do it
572 auto change_lambda
573 = [this, kk, &old_translator, &new_translator, miss_mapping](std::size_t begin,
574 std::size_t end,
575 std::size_t index) -> void {
576 const auto old_miss = old_translator.missingValue().discr_val;
577 for (std::size_t i = begin; i < end; ++i) {
578 auto& row = this->rows_[i].row();
579 if (row[kk].discr_val == old_miss) {
580 row[kk] = miss_mapping.second;
581 } else {
582 row[kk] = new_translator.translate(old_translator.translateBack(row[kk]));
583 }
584 }
585 };
586
587 auto undo_change_lambda = [](std::size_t begin, std::size_t end, std::size_t index) -> void {};
588
589 // launch the threads executing the lambdas
590 this->_threadProcessDatabase_(change_lambda, undo_change_lambda);
591
592 // keep into account the name of the new translator
593 this->variable_names_[kk] = new_translator.variable()->name();
594
595 // substitute int the stransltor's set the old translator by the new one
596 _translators_.changeTranslator(new_translator, kk);
597 }
598
600 void DatabaseTable::changeTranslator(const Variable& var,
601 const std::size_t k,
602 const bool k_is_input_col,
603 const std::vector< std::string >& missing_symbols,
604 const bool editable_dictionary,
605 const std::size_t max_dico_entries) {
606 // get the appropriate set of missing symbols
607 std::vector< std::string > missing;
608 if (missing_symbols.empty()) {
609 // try to get the missing symbols of the current translator
610 const auto db_k = _getKthIndices_(k, k_is_input_col);
611 if (db_k.empty()) {
612 GUM_ERROR(OutOfBounds, "the translator at position " << k << " cannot be found.");
613 }
614 const std::size_t kk = db_k[db_k.size() - 1];
615 if (kk >= _translators_.size()) {
616 GUM_ERROR(OutOfBounds, "the translator at position " << k << " cannot be found.");
617 }
618
619 const auto& miss = _translators_[kk].missingSymbols();
620 missing.reserve(miss.size());
621 for (const auto& m: miss) {
622 missing.push_back(m);
623 }
624 } else {
625 missing = missing_symbols;
626 }
627
628 // create the DBTranslator corresponding to the parameters
629 DBTranslator* new_translator
630 = DBTranslators::create(var, missing, editable_dictionary, max_dico_entries);
631
632 try {
633 changeTranslator(*new_translator, k, k_is_input_col);
634 } catch (...) {
635 // remove from memory new_translator
636 delete new_translator;
637 throw;
638 }
639
640 // remove from memory new_translator
641 delete new_translator;
642 }
643
645 std::vector< std::pair< Idx, std::shared_ptr< DBTranslator > > >
646 DatabaseTable::betterTranslators() const {
647 std::vector< std::pair< Idx, std::shared_ptr< DBTranslator > > > better;
648
649 for (Idx i = 0, size = _translators_.size(); i < size; ++i) {
650 switch (_translators_[i].variable()->varType()) {
651 // if the translator is discretized, range or continuous, we cannot
652 // improve it
653 case VarType::CONTINUOUS :
654 case VarType::NUMERICAL :
655 case VarType::DISCRETIZED :
656 case VarType::RANGE : break;
657
658 // if the translator can only translate integers ans all the numbers
659 // are consecutive, prefer a range variable
660 case VarType::INTEGER : {
661 const auto& var = dynamic_cast< const IntegerVariable& >(*(_translators_[i].variable()));
662
663 // check that the values in the domain are consecutive
664 const auto& domain = var.integerDomain();
665 if (domain.empty()) break; // we cannot get a better translator
666 int prev = domain[0] - 1;
667 bool ok = true;
668 for (const auto elt: domain) {
669 if (elt != prev + 1) {
670 ok = false;
671 break;
672 } else {
673 prev = elt;
674 }
675 }
676
677 // here, we know that the values are consecutive, hence we can
678 // change the variable to a range variable
679 if (ok) {
680 RangeVariable new_var(var.name(),
681 var.description(),
682 domain[0],
683 domain[domain.size() - 1]);
684
685 // get the set of missing symbols
686 const auto& missing = _translators_[i].missingSymbols();
687 std::vector< std::string > new_missing;
688 new_missing.reserve(missing.size());
689 for (const auto& miss: missing) {
690 new_missing.push_back(miss);
691 }
692 auto new_trans = new DBTranslator4RangeVariable(new_var, new_missing);
693 better.push_back(std::pair< Idx, std::shared_ptr< DBTranslator > >(
694 i,
695 std::shared_ptr< DBTranslator >(new_trans)));
696 }
697 break;
698 }
699
700 // if the translator is a set of labels, check whether those are all
701 // numbers. In this case, if they are integers and consecutive,
702 // prefer a RangeVariable; if they are integers but not consecutive,
703 // prefer an IntegerVariable, else check whether a continuous
704 // variable could be ok
705 case VarType::LABELIZED : {
706 const auto& var
707 = dynamic_cast< const LabelizedVariable& >(*(_translators_[i].variable()));
708 if (!var.domainSize()) break; // we cannot get a better translator
709
710 // get the numerical values of the labels
711 Set< int > int_values;
712 Set< float > real_values;
713 bool ok = true;
714 for (Idx j = 0, s = var.domainSize(); j < s; ++j) {
715 const auto& val = var.label(j);
716 if (DBCell::isReal(val)) {
717 if (DBCell::isInteger(val)) {
718 int_values.insert(std::stoi(val));
719 } else {
720 real_values.insert(std::stof(val));
721 }
722 } else {
723 ok = false;
724 break;
725 }
726 }
727
728 // if there are only numerical values, we can certainly do something
729 if (ok) {
730 // check whether an IntegerVariable or a Range variable would do
731 if (real_values.empty()) {
732 // get the values in increasing order
733 std::vector< int > values;
734 values.reserve(int_values.size());
735 for (const auto val: int_values)
736 values.push_back(val);
737 std::sort(values.begin(), values.end());
738
739 // if all the values are consecutive, then a range variable
740 // would be best
741 int prev = values[0] - 1;
742 bool consecutive = true;
743 for (const auto elt: values) {
744 if (elt != prev + 1) {
745 consecutive = false;
746 break;
747 } else {
748 prev = elt;
749 }
750 }
751
752 if (consecutive) {
753 // here, we should create a range variable since all the
754 // values in the domain are consecutive
755 RangeVariable new_var(var.name(), var.description(), values[0], values.back());
756
757 // get the set of missing symbols
758 const auto& missing = _translators_[i].missingSymbols();
759 std::vector< std::string > new_missing;
760 new_missing.reserve(missing.size());
761 for (const auto& miss: missing) {
762 new_missing.push_back(miss);
763 }
764 auto new_trans = new DBTranslator4RangeVariable(new_var, new_missing);
765 better.emplace_back(i, std::shared_ptr< DBTranslator >(new_trans));
766 } else {
767 // here, the values in the domain are not consecutive, hence
768 // it would be better to create an IntegerVariable
769 IntegerVariable new_var(var.name(), var.description(), values);
770
771 // get the set of missing symbols
772 const auto& missing = _translators_[i].missingSymbols();
773 std::vector< std::string > new_missing;
774 new_missing.reserve(missing.size());
775 for (const auto& miss: missing) {
776 new_missing.push_back(miss);
777 }
778 auto new_trans = new DBTranslator4IntegerVariable(new_var, new_missing);
779 better.emplace_back(i, std::shared_ptr< DBTranslator >(new_trans));
780 }
781 } else {
782 // here, a translator for continuous variable would be suited
783 ContinuousVariable new_var(var.name(), var.description());
784
785 // get the set of missing symbols
786 const auto& missing = _translators_[i].missingSymbols();
787 std::vector< std::string > new_missing;
788 new_missing.reserve(missing.size());
789 for (const auto& miss: missing) {
790 new_missing.push_back(miss);
791 }
792 auto new_trans = new DBTranslator4ContinuousVariable(new_var, new_missing);
793 better.emplace_back(i, std::shared_ptr< DBTranslator >(new_trans));
794 }
795 }
796 break;
797 }
798 }
799 }
800
801 return better;
802 }
803
805 const DBTranslator& DatabaseTable::translator(const std::size_t k,
806 const bool k_is_input_col) const {
807 // find the position of the translator that we look for. This
808 // is variable kk below
809 const std::size_t nb_trans = _translators_.size();
810 const std::size_t kk = _getKthIndex_(k, k_is_input_col);
811
812 // check if the translator exists
813 if (nb_trans <= kk) {
814 if (k_is_input_col) {
816 "there is no translator in the database table that " << "parses Column " << k)
817 } else {
819 "the database has " << nb_trans << " translators, so Translator #" << k
820 << " does not exist")
821 }
822 }
823
824 return _translators_.translator(kk);
825 }
826
828 const Variable& DatabaseTable::variable(const std::size_t k, const bool k_is_input_col) const {
829 // find the position of the translator that contains the variable.
830 // This is variable kk below
831 const std::size_t nb_trans = _translators_.size();
832 const std::size_t kk = _getKthIndex_(k, k_is_input_col);
833
834 // check if the translator exists
835 if (nb_trans <= kk) {
836 if (k_is_input_col) {
838 "there is no variable in the database table that " << "corresponds to Column "
839 << k)
840 } else {
842 "the database has " << nb_trans << " variables, so Variable #" << k
843 << " does not exist")
844 }
845 }
846
847 return _translators_.variable(kk);
848 }
849
851 void DatabaseTable::setVariableNames(const std::vector< std::string >& names,
852 const bool from_external_object) {
853 const std::size_t size = names.size();
854 const std::size_t nb_trans = _translators_.size();
855 if (!from_external_object) {
856 if (nb_trans != size) {
858 "the number of variable's names (i.e., "
859 << size << ") does not correspond to the number of columns of the "
860 << "database table (i.e.," << nb_trans << ")")
861 }
862
863 // update the translator names
864 for (std::size_t i = std::size_t(0); i < size; ++i) {
865 _translators_.translator(i).setVariableName(names[i]);
866 }
867 } else {
868 if (nb_trans && (_translators_.highestInputColumn() >= size)) {
870 "the names vector has " << size << " elements whereas it should have at least "
871 << (_translators_.highestInputColumn() + 1)
872 << "elements so that each translator is assigned a name")
873 }
874
875 // update the translator names
876 for (std::size_t i = std::size_t(0); i < nb_trans; ++i) {
877 _translators_.translator(i).setVariableName(names[_translators_.inputColumn(i)]);
878 }
879 }
880
881 // update variable_names_ using the newly assigned translators names
882 this->variable_names_.resize(nb_trans);
883 for (std::size_t i = std::size_t(0); i < nb_trans; ++i) {
884 this->variable_names_[i] = _translators_.variable(i).name();
885 }
886 }
887
890 void DatabaseTable::ignoreColumn(const std::size_t k, const bool k_is_input_col) {
891 // indicate that the column will be forbidden. If the column is already
892 // forbidden, do nothing. But if the column is assigned to a translator
893 // that does not exist, raise an UndefinedElement exception
894 const std::size_t nb_trans = _translators_.size();
895 if (k_is_input_col) {
896 if (_ignored_cols_.exists(k)) return;
897 _ignored_cols_.insert(k);
898 } else {
899 if (k < nb_trans) {
900 _ignored_cols_.insert(_translators_.inputColumn(k));
901 } else {
903 "It is impossible to ignore the column parsed by Translator #"
904 << k << "because there exist only " << nb_trans << " translators")
905 }
906 }
907
908 // remove all the translators corresponding to k
909 eraseTranslators(k, k_is_input_col);
910 }
911
913 const typename DatabaseTable::template DBVector< std::size_t >
914 DatabaseTable::ignoredColumns() const {
915 const std::size_t nb_trans = _translators_.size();
916
917 if (nb_trans == std::size_t(0)) { return DBVector< std::size_t >{std::size_t(0)}; }
918
919 // get the columns handled by the translators, sorted by increasing order
920 DBVector< std::size_t > cols(nb_trans);
921 for (std::size_t i = std::size_t(0); i < nb_trans; ++i) {
922 cols[i] = _translators_.inputColumn(i);
923 }
924 std::sort(cols.begin(), cols.end());
925
926 // create a vector with all the possible input columns
927 const std::size_t highest = _translators_.highestInputColumn() + 1;
928 DBVector< std::size_t > ignored_cols(highest);
929 std::iota(ignored_cols.begin(), ignored_cols.end(), 0);
930
931 // remove from ignored_cols the elements of cols
932 for (std::size_t i = std::size_t(0), ii = highest - 1, k = std::size_t(0), kk = nb_trans - 1;
933 i < highest;
934 ++i, --ii) {
935 if (cols[kk] == ii) {
936 ignored_cols.erase(ignored_cols.begin() + ii);
937 while ((k < nb_trans) && (cols[kk] == ii)) {
938 --kk;
939 ++k;
940 }
941 if (k == nb_trans) break;
942 }
943 }
944
945 // add the column past the last translator
946 ignored_cols.push_back(highest);
947
948 return ignored_cols;
949 }
950
952 const typename DatabaseTable::template DBVector< std::size_t >
953 DatabaseTable::inputColumns() const {
954 const std::size_t nb_trans = _translators_.size();
955 if (nb_trans == std::size_t(0)) { return DBVector< std::size_t >(); }
956
957 DBVector< std::size_t > input_cols(nb_trans);
958 for (std::size_t i = std::size_t(0); i < nb_trans; ++i) {
959 input_cols[i] = _translators_.inputColumn(i);
960 }
961 return input_cols;
962 }
963
965 std::size_t DatabaseTable::domainSize(const std::size_t k, const bool k_is_input_col) const {
966 // find the position kk of the translator that contains the variable
967 const std::size_t nb_trans = _translators_.size();
968 const std::size_t kk = _getKthIndex_(k, k_is_input_col);
969
970 // check if the translator exists
971 if (nb_trans <= kk) {
972 if (k_is_input_col) {
974 "there is no variable in the database table that " << "corresponds to Column "
975 << k)
976 } else {
978 "the database has " << nb_trans << " variables, so Variable #" << k
979 << " does not exist")
980 }
981 }
982
983 return _translators_.domainSize(kk);
984 }
985
986 // indicates whether a reordering is needed to make the kth
987 // translator sorted by lexicographical order
988 bool DatabaseTable::needsReordering(const std::size_t k, const bool k_is_input_col) const {
989 // find the position kk of the translator that contains the variable
990 const std::size_t nb_trans = _translators_.size();
991 const std::size_t kk = _getKthIndex_(k, k_is_input_col);
992
993 // check if the translator exists
994 if (nb_trans <= kk) {
995 if (k_is_input_col) {
997 "there is no translator in the database table that " << "parses Column " << k)
998 } else {
1000 "the database has " << nb_trans << " translators, so Translator #" << k
1001 << " does not exist")
1002 }
1003 }
1004
1005 return _translators_.needsReordering(kk);
1006 }
1007
1008 // performs a reordering of the kth translator or of the first
1009 // translator corresponding to the kth column of the input database
1010 void DatabaseTable::reorder(const std::size_t k, const bool k_is_input_col) {
1011 // find the position kk of the translator that contains the variable
1012 const std::size_t nb_trans = _translators_.size();
1013 const std::size_t kk = _getKthIndex_(k, k_is_input_col);
1014
1015 // check if the translator exists
1016 if (nb_trans <= kk) {
1017 if (k_is_input_col) {
1019 "there is no translator in the database table that " << "parses Column " << k)
1020 } else {
1022 "the database has " << nb_trans << " translators, so Translator #" << k
1023 << " does not exist")
1024 }
1025 }
1026
1027 // if the translator is not designed for a discrete variable, there
1028 // is no reordering to apply
1029 if (_translators_.translator(kk).getValType() != DBTranslatedValueType::DISCRETE) return;
1030
1031 // get the translation to perform
1032 auto updates = _translators_.reorder(kk);
1033 if (updates.empty()) return;
1034
1035 std::size_t size = updates.size();
1036 std::vector< std::size_t > new_values(size);
1037 for (const auto& update: updates) {
1038 if (update.first >= size) {
1039 size = update.first + 1;
1040 new_values.resize(size);
1041 }
1042 new_values[update.first] = update.second;
1043 }
1044
1045 // apply the translations
1046 auto newtrans_lambda
1047 = [this, kk, &new_values](std::size_t begin, std::size_t end, std::size_t index) -> void {
1048 for (std::size_t i = begin; i < end; ++i) {
1049 auto& elt = this->rows_[i][kk].discr_val;
1050 if (elt != std::numeric_limits< std::size_t >::max()) elt = new_values[elt];
1051 }
1052 };
1053
1054 auto undo_newtrans_lambda
1055 = [](std::size_t begin, std::size_t end, std::size_t index) -> void {};
1056
1057 // launch the threads executing the lambdas
1058 this->_threadProcessDatabase_(newtrans_lambda, undo_newtrans_lambda);
1059 }
1060
1062 void DatabaseTable::insertRow(const std::vector< std::string >& new_row) {
1063 // check that the row can be fully translated, i.e., it contains enough
1064 // columns to be translated
1065 const std::size_t row_size = new_row.size();
1066 if (row_size == std::size_t(0)) return;
1067
1068 if (_translators_.highestInputColumn() >= row_size) {
1070 "the row #" << 1 + size() << " has " << row_size
1071 << " columns whereas the database requires at least "
1072 << (_translators_.highestInputColumn() + 1) << " columns")
1073 }
1074
1075 // convert the new_row into a row of DBTranslatedValue
1076 const std::size_t nb_trans = _translators_.size();
1077 Row< DBTranslatedValue > dbrow;
1078 dbrow.reserve(nb_trans);
1079 bool has_missing_val = false;
1080 for (std::size_t i = std::size_t(0); i < nb_trans; ++i) {
1081 const DBTranslatedValue new_val(_translators_.translate(new_row, i));
1082 if (_translators_.isMissingValue(new_val, i)) has_missing_val = true;
1083 dbrow.pushBack(std::move(new_val));
1084 }
1085
1086 this->insertRow(std::move(dbrow), has_missing_val ? IsMissing::True : IsMissing::False);
1087 }
1088
1091 bool DatabaseTable::_isRowCompatible_(
1092 const typename DatabaseTable::template Row< DBTranslatedValue >& row) const {
1093 // check that the size of the row corresponds to that of the translators
1094 const std::size_t row_size = row.size();
1095 if (row_size != _translators_.size()) return false;
1096
1097 const auto& translators = _translators_.translators();
1098 for (std::size_t i = std::size_t(0); i < row_size; ++i) {
1099 switch (translators[i]->getValType()) {
1100 case DBTranslatedValueType::DISCRETE :
1101 if ((row[i].discr_val >= translators[i]->domainSize())
1102 && (row[i].discr_val != std::numeric_limits< std::size_t >::max()))
1103 return false;
1104 break;
1105
1106 case DBTranslatedValueType::CONTINUOUS : {
1107 const IContinuousVariable& var
1108 = static_cast< const IContinuousVariable& >(*(translators[i]->variable()));
1109 if (((var.lowerBoundAsDouble() > (double)row[i].cont_val)
1110 || (var.upperBoundAsDouble() < (double)row[i].cont_val))
1111 && (row[i].cont_val != std::numeric_limits< float >::max()))
1112 return false;
1113 break;
1114 }
1115
1116 default : GUM_ERROR(NotImplementedYet, "Translated value type not supported yet")
1117 }
1118 }
1119
1120 return true;
1121 }
1122
1123 // insert a new DBRow of DBCells at the end of the database
1124 void DatabaseTable::insertRow(const typename DatabaseTable::template Row< DBCell >& new_row) {
1125 GUM_ERROR(NotImplementedYet, "not implemented yet")
1126 }
1127
1128 // insert a new DBRow of DBCells at the end of the database
1129 void DatabaseTable::insertRow(typename DatabaseTable::template Row< DBCell >&& new_row) {
1130 GUM_ERROR(NotImplementedYet, "not implemented yet")
1131 }
1132
1134 void DatabaseTable::insertRows(
1135 typename DatabaseTable::template Matrix< DBTranslatedValue >&& rows,
1136 const typename DatabaseTable::template DBVector< IsMissing >& rows_have_missing_vals) {
1137 // check that the new rows values are compatible with the values of
1138 // the variables stored within the translators
1139 for (const auto& new_row: rows) {
1140 if (!_isRowCompatible_(new_row)) {
1141 if (new_row.size() != _translators_.size()) {
1143 "The new row has " << new_row.size()
1144 << " elements whereas the database table has "
1145 << _translators_.size() << " columns")
1146 } else {
1147 GUM_ERROR(InvalidArgument, "the new row is not compatible with the current translators")
1148 }
1149 }
1150 }
1151
1152 IDatabaseTable< DBTranslatedValue >::insertRows(std::move(rows), rows_have_missing_vals);
1153 }
1154
1156 void DatabaseTable::insertRows(
1157 const typename DatabaseTable::template Matrix< DBTranslatedValue >& new_rows,
1158 const typename DatabaseTable::template DBVector< IsMissing >& rows_have_missing_vals) {
1159 // check that the new rows values are compatible with the values of
1160 // the variables stored within the translators
1161 for (const auto& new_row: new_rows) {
1162 if (!_isRowCompatible_(new_row)) {
1163 if (new_row.size() != _translators_.size()) {
1165 "The new row has " << new_row.size()
1166 << " elements whereas the database table has "
1167 << _translators_.size() << " columns")
1168 } else {
1169 GUM_ERROR(InvalidArgument, "the new row is not compatible with the current translators")
1170 }
1171 }
1172 }
1173
1174 IDatabaseTable< DBTranslatedValue >::insertRows(new_rows, rows_have_missing_vals);
1175 }
1176
1178 void DatabaseTable::insertRows(typename DatabaseTable::template Matrix< DBCell >&& new_rows) {
1179 GUM_ERROR(NotImplementedYet, "not implemented yet")
1180 }
1181
1183 void
1184 DatabaseTable::insertRows(const typename DatabaseTable::template Matrix< DBCell >& new_rows) {
1185 GUM_ERROR(NotImplementedYet, "not implemented yet")
1186 }
1187
1189 void DatabaseTable::clear() {
1190 _translators_.clear();
1191 _ignored_cols_.clear();
1192 IDatabaseTable< DBTranslatedValue >::clear();
1193 }
1194
1195 std::size_t DatabaseTable::_getKthIndex_(const std::size_t k, const bool k_is_input_col) const {
1196 if (k_is_input_col) {
1197 const std::size_t nb_trans = _translators_.size();
1198 for (std::size_t i = std::size_t(0); i < nb_trans; ++i) {
1199 if (_translators_.inputColumn(i) == k) { return i; }
1200 }
1201 return nb_trans + 1;
1202 } else {
1203 return k;
1204 }
1205 }
1206
1207 void DatabaseTable::insertRow(typename DatabaseTable::template Row< DBTranslatedValue >&& new_row,
1208 const typename DatabaseTable::IsMissing contains_missing_data) {
1209 // check that the new rows values are compatible with the values of
1210 // the variables stored within the translators
1211 if (!_isRowCompatible_(new_row)) {
1212 if (new_row.size() != _translators_.size()) {
1214 "The new row has " << new_row.size()
1215 << " elements whereas the database table has "
1216 << _translators_.size() << " columns")
1217 } else {
1218 GUM_ERROR(InvalidArgument, "the new row is not compatible with the current translators")
1219 }
1220 }
1221
1222 IDatabaseTable< DBTranslatedValue >::insertRow(std::move(new_row), contains_missing_data);
1223 }
1224
1225 void DatabaseTable::insertRow(
1226 const typename DatabaseTable::template Row< DBTranslatedValue >& new_row,
1227 const typename DatabaseTable::IsMissing contains_missing_data) {
1228 // check that the new rows values are compatible with the values of
1229 // the variables stored within the translators
1230 if (!_isRowCompatible_(new_row)) {
1231 if (new_row.size() != _translators_.size()) {
1233 "The new row has " << new_row.size()
1234 << " elements whereas the database table has "
1235 << _translators_.size() << " columns")
1236 } else {
1237 GUM_ERROR(InvalidArgument, "the new row is not compatible with the current translators")
1238 }
1239 }
1240
1241 IDatabaseTable< DBTranslatedValue >::insertRow(new_row, contains_missing_data);
1242 }
1243} // namespace gum::learning
1244
1245#endif /* DOXYGEN_SHOULD_SKIP_THIS */
Exception: at least one argument passed to a function is not what was expected.
Error: The database contains some missing values.
Exception : there is something wrong with an implementation.
Exception : operation not allowed.
Exception : out of bound.
Exception : problem with size.
Exception : a looked-for element could not be found.
the class for packing together the translators used to preprocess the datasets
DatabaseTable(const MissingValType &missing_symbols, const DBTranslatorSet &translators=DBTranslatorSet())
default constructor
std::vector< std::string > MissingValType
The common class for the tabular database tables.
The class representing a tabular database stored in RAM.
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
include the inlined functions if necessary
Definition CSVParser.h:55
STL namespace.
The union class for storing the translated values in learning databases.