#include #include #include #include "../shared/MiscSupport.h" #include "GridInstanceData.h" namespace GridInstanceData { ///////////////////////////////////////////////////////////////////// // Grid ///////////////////////////////////////////////////////////////////// bool Grid::tooHigh(int row, int col) const { const int nr = nextRow(); if (row < nr) return false; else if (row > nr) return true; else return col >= nextCol(); } int Grid::maxRowForColumn(int column) const { if (column < nextCol()) // The last row is a partial row, and this column can be found in that // partial row. return nextRow(); else return nextRow() - 1; } double Grid::get(int row, int column) const { return _cells.getAt(row * _width + column); } double Grid::invalid() { return std::numeric_limits< double >::quiet_NaN(); } double Grid::reference(int row, int column) const { if ((row < 0) || (column < 0) || (column >= _width)) return invalid(); double result; if (tooHigh(row, column)) result = invalid(); else result = get(row, column); return result; } double Grid::sum(int firstRow, int lastRow, int column, bool skipBadValues) const { if (lastRow + 1 == firstRow) // This is the way to say 0 rows. return 0; else if (lastRow + 1 < firstRow) return invalid(); double accumulator = 0.0; if (skipBadValues) { if (firstRow < 0) firstRow = 0; lastRow = std::min(lastRow, maxRowForColumn(column)); for (int i = firstRow; i <= lastRow; i++) { const double value = get(i, column); if (std::isfinite(value)) accumulator += value; } } else { if (firstRow < 0) return invalid(); if (tooHigh(lastRow, column)) return invalid(); for (int i = firstRow; i <= lastRow; i++) { const double value = get(i, column); if (!std::isfinite(value)) return invalid(); accumulator += value; } } return accumulator; } double Grid::count(int firstRow, int lastRow, int column) const { if (lastRow + 1 == firstRow) // This is the way to say 0 rows. return 0; else if (lastRow + 1 < firstRow) return invalid(); // skipBadColumns is not an input. This code works as if that was always // true. if (firstRow < 0) firstRow = 0; lastRow = std::min(lastRow, maxRowForColumn(column)); int count = 0; for (int i = firstRow; i <= lastRow; i++) { if (tooHigh(i, column)) break; const double value = get(i, column); if (std::isfinite(value)) count++; } return count; } double Grid::average(int firstRow, int lastRow, int column, bool skipBadValues) const { if (lastRow + 1 < firstRow) return invalid(); int count = 0; double accumulator = 0.0; if (skipBadValues) { if (firstRow < 0) firstRow = 0; lastRow = std::min(lastRow, maxRowForColumn(column)); for (int i = firstRow; i <= lastRow; i++) { const double value = get(i, column); if (std::isfinite(value)) { accumulator += value; count++; } } } else { if (firstRow < 0) return invalid(); if (tooHigh(lastRow, column)) return invalid(); for (int i = firstRow; i <= lastRow; i++) { const double value = get(i, column); if (!std::isfinite(value)) return invalid(); accumulator += value; } count = lastRow - firstRow + 1; } if (count == 0) return invalid(); else return accumulator / count; } double Grid::max(int firstRow, int lastRow, int column, bool skipBadValues) const { if (lastRow + 1 < firstRow) return invalid(); double accumulator = - std::numeric_limits< double >::infinity(); if (skipBadValues) { if (firstRow < 0) firstRow = 0; lastRow = std::min(lastRow, maxRowForColumn(column)); for (int i = firstRow; i <= lastRow; i++) { const double value = get(i, column); if (std::isfinite(value)) if (value > accumulator) accumulator = value; } } else { if (firstRow < 0) return invalid(); if (tooHigh(lastRow, column)) return invalid(); for (int i = firstRow; i <= lastRow; i++) { const double value = get(i, column); if (!std::isfinite(value)) return invalid(); if (value > accumulator) accumulator = value; } } return accumulator; } double Grid::min(int firstRow, int lastRow, int column, bool skipBadValues) const { if (lastRow + 1 < firstRow) return invalid(); double accumulator = std::numeric_limits< double >::infinity(); if (skipBadValues) { if (firstRow < 0) firstRow = 0; lastRow = std::min(lastRow, maxRowForColumn(column)); for (int i = firstRow; i <= lastRow; i++) { const double value = get(i, column); if (std::isfinite(value)) if (value < accumulator) accumulator = value; } } else { if (firstRow < 0) return invalid(); if (tooHigh(lastRow, column)) return invalid(); for (int i = firstRow; i <= lastRow; i++) { const double value = get(i, column); if (!std::isfinite(value)) return invalid(); if (value < accumulator) accumulator = value; } } return accumulator; } void Grid::getValuesAll(std::vector< double > &result, int firstRow, int lastRow, int column) const { result.clear(); if ((firstRow >= 0) && (!tooHigh(lastRow, column)) && (firstRow <= lastRow)) { result.reserve(lastRow - firstRow + 1); for (int i = firstRow; i <= lastRow; i++) { const double value = get(i, column); if (!std::isfinite(value)) { result.clear(); return; } result.push_back(value); } } } int Grid::nextCol() const { return _cells.getCount() % _width; } int Grid::nextRow() const { return _cells.getCount() / _width; } int Grid::partialRowCount() const { return (_cells.getCount() + _width - 1) / _width; } ///////////////////////////////////////////////////////////////////// // PackableValues::DebugDump ///////////////////////////////////////////////////////////////////// void PackableValues::DebugDump::getValues(int row, int col, double &committed, double &inProgress) const { ValuePair pair = getPropertyDefault(_values, {row, col}); committed = pair.committed; inProgress = pair.inProgress; } void PackableValues::DebugDump::addCommitted(int row, int col, double value) { _values[{row, col}].committed = value; possibleRowIndex.insert(row); } void PackableValues::DebugDump::addInProgress(int row, int col, double value) { _values[{row, col}].inProgress = value; possibleRowIndex.insert(row); } void PackableValues::DebugDump::addValue(int row, int col, double value, Use whichValue) { if (whichValue == Use::Committed) addCommitted(row, col, value); else addInProgress(row, col, value); } void PackableValues::DebugDump::clear() { _values.clear(); possibleRowIndex.clear(); } ///////////////////////////////////////////////////////////////////// // PackableValues ///////////////////////////////////////////////////////////////////// PackedToPossible::IList const & PackableValues::getIndex(Use use) const { return (use == Use::Committed) ?_packedToPossible.committed(): _packedToPossible.inProgress(); } int PackableValues::packedToPossible(int packed, Use use) const { if (!_packed) // The packed and possible versions are the same. return packed; else if (packed < 0) // Assume there were no holidays or other missing data before we started // looking. We have to real way to know, but this is probably the best // approximation. return packed; else { auto &index = getIndex(use); const int count = index.getCount(); if (packed < count) // We know the exact value. return index.getAt(packed); else if (count == 0) // We have no information. As before, assume no holidays or other // missing data. return packed; else { // Start from the last entry we have, and assume no missing data after // that. So the returned value should be >= the input. const int offset = count - 1; return index.getAt(offset) + (packed - offset); } } } int PackableValues::possibleToPacked(int possible, Use use, Round round) const { if (!_packed) // The packed and possible versions are the same. return possible; auto &index = getIndex(use); int lower = 0; // index >= lower int higher = index.getCount(); // index < upper. while (true) { if (lower >= higher) { // Not found. assert(lower == higher); // The value should exist between index lower and index lower-1. switch (round) { case Round::Up: return lower; case Round::Down: return lower - 1; case Round::Fail: return NOT_FOUND; } } const int middle = (higher + lower) / 2; const int value = index.getAt(middle); if (value == possible) // Exact match. return middle; if (value < possible) // Look to the right lower = middle + 1; else // Look to the left higher = middle; } } void PackableValues::store(double value) { Grid grid(_cells.inProgress(), _width); const bool startingNewRow = !grid.nextCol(); if (_packed && startingNewRow) // Add it to our index. _packedToPossible.append(_completedRowCountInProgress); _cells.append(value); const bool justFinishedARow = !grid.nextCol(); if (justFinishedARow) _completedRowCountInProgress++; } void PackableValues::skipRow() { assert(_packed && !Grid(_cells.inProgress(), _width).nextCol()); _completedRowCountInProgress++; } void PackableValues::deleteExceptPacked(int rowCount) { _cells.deleteExcept(rowCount * _width); _packedToPossible.deleteExcept(rowCount); // Note that the actual row count might not be the same as rowCount. The // actual row count will never be negative. And the actual row count won't // grow because of a delete request. recomputeCompletedRowCount(); } void PackableValues::recomputeCompletedRowCount() { // Recompute this field after a deleted operation. const int rowCount = _cells.inProgress().getCount() / _width; if (!_packed) _completedRowCountInProgress = rowCount; else { // Go all the way back to the last row with real data. This might remove // more blank rows than expected. That should not be a problem. Note // that calling deleteExpectPacked(n) can have an affect even if we had // fewer than n rows to start with. It might remove blank rows. That // should not be a problem. if (rowCount == 0) _completedRowCountInProgress = 0; else { PackedToPossible::IList const &index = _packedToPossible.inProgress(); // What possible row does our last complete packed row look at? // subtract one to convert from a row count to a row index, then add // one to convert back. _completedRowCountInProgress = index.getAt(rowCount - 1) + 1; } } } void PackableValues::deleteExceptPossible(int rowCount) { deleteExceptPacked(possibleToPacked(rowCount, Use::InProgress, Round::Up)); } void PackableValues::commit() { _cells.commit(); _packedToPossible.commit(); _completedRowCountCommitted = _completedRowCountInProgress; } void PackableValues::rollBack() { _cells.rollBack(); _packedToPossible.rollBack(); _completedRowCountInProgress = _completedRowCountCommitted; } void PackableValues::debugDump(DebugDump &dump, Use use, Grid grid) const { const int partialRowCount = grid.partialRowCount(); for (int packedRow = 0; packedRow < partialRowCount; packedRow++) { const int possibleRow = packedToPossible(packedRow, use); for (int col = 0; col < _width; col++) dump.addValue(possibleRow, col, grid.reference(packedRow, col), use); } } void PackableValues::debugDump(DebugDump &dump) const { dump.clear(); dump.width = _width; debugDump(dump, Use::InProgress, inProgress()); debugDump(dump, Use::Committed, committed()); } };