UNPKG

hyperformula

Version:

HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas

150 lines (148 loc) 7.05 kB
"use strict"; exports.__esModule = true; exports.CriterionFunctionCompute = exports.Condition = void 0; var _Cell = require("../Cell"); var _errorMessage = require("../error-message"); var _generatorUtils = require("../generatorUtils"); var _InterpreterValue = require("./InterpreterValue"); /** * @license * Copyright (c) 2025 Handsoncode. All rights reserved. */ const findSmallerRangeForMany = (dependencyGraph, conditionRanges, valuesRange) => { if (valuesRange.end.row > valuesRange.start.row) { const valuesRangeEndRowLess = (0, _Cell.simpleCellAddress)(valuesRange.end.sheet, valuesRange.end.col, valuesRange.end.row - 1); const rowLessVertex = dependencyGraph.getRange(valuesRange.start, valuesRangeEndRowLess); if (rowLessVertex !== undefined) { return { smallerRangeVertex: rowLessVertex, restValuesRange: valuesRange.withStart((0, _Cell.simpleCellAddress)(valuesRange.start.sheet, valuesRange.start.col, valuesRange.end.row)), restConditionRanges: conditionRanges.map(conditionRange => conditionRange.withStart((0, _Cell.simpleCellAddress)(conditionRange.start.sheet, conditionRange.start.col, conditionRange.end.row))) }; } } return { restValuesRange: valuesRange, restConditionRanges: conditionRanges }; }; class CriterionFunctionCompute { constructor(interpreter, cacheKey, reduceInitialValue, composeFunction, mapFunction) { this.interpreter = interpreter; this.cacheKey = cacheKey; this.reduceInitialValue = reduceInitialValue; this.composeFunction = composeFunction; this.mapFunction = mapFunction; this.dependencyGraph = this.interpreter.dependencyGraph; } compute(simpleValuesRange, conditions) { for (const condition of conditions) { if (!condition.conditionRange.sameDimensionsAs(simpleValuesRange)) { return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.EqualLength); } } const valuesRangeVertex = this.tryToGetRangeVertexForRangeValue(simpleValuesRange); const conditionsVertices = conditions.map(c => this.tryToGetRangeVertexForRangeValue(c.conditionRange)); if (valuesRangeVertex && conditionsVertices.every(e => e !== undefined)) { const fullCriterionString = conditions.map(c => c.criterionPackage.raw).join(','); const cachedResult = this.findAlreadyComputedValueInCache(valuesRangeVertex, this.cacheKey(conditions), fullCriterionString); if (cachedResult !== undefined) { this.interpreter.stats.incrementCriterionFunctionFullCacheUsed(); return cachedResult; } const cache = this.buildNewCriterionCache(this.cacheKey(conditions), conditions.map(c => c.conditionRange.range), simpleValuesRange.range); if (!cache.has(fullCriterionString)) { cache.set(fullCriterionString, [this.evaluateRangeValue(simpleValuesRange, conditions), conditions.map(condition => condition.criterionPackage.lambda)]); } valuesRangeVertex.setCriterionFunctionValues(this.cacheKey(conditions), cache); conditionsVertices.forEach(range => { if (range !== undefined) { range.addDependentCacheRange(valuesRangeVertex); } }); return cache.get(fullCriterionString)[0]; } else { return this.evaluateRangeValue(simpleValuesRange, conditions); } } tryToGetRangeVertexForRangeValue(rangeValue) { const maybeRange = rangeValue.range; if (maybeRange === undefined) { return undefined; } else { return this.dependencyGraph.getRange(maybeRange.start, maybeRange.end); } } reduceFunction(iterable) { let acc = this.reduceInitialValue; for (const val of iterable) { acc = this.composeFunction(acc, val); } return acc; } findAlreadyComputedValueInCache(rangeVertex, cacheKey, criterionString) { return rangeVertex.getCriterionFunctionValue(cacheKey, criterionString); } evaluateRangeValue(simpleValuesRange, conditions) { const criterionLambdas = conditions.map(condition => condition.criterionPackage.lambda); const values = Array.from(simpleValuesRange.valuesFromTopLeftCorner()).map(this.mapFunction)[Symbol.iterator](); const conditionsIterators = conditions.map(condition => condition.conditionRange.iterateValuesFromTopLeftCorner()); const filteredValues = ifFilter(criterionLambdas, conditionsIterators, values); return this.reduceFunction(filteredValues); } buildNewCriterionCache(cacheKey, simpleConditionRanges, simpleValuesRange) { const currentRangeVertex = this.dependencyGraph.getRange(simpleValuesRange.start, simpleValuesRange.end); const { smallerRangeVertex, restConditionRanges, restValuesRange } = findSmallerRangeForMany(this.dependencyGraph, simpleConditionRanges, simpleValuesRange); let smallerCache; if (smallerRangeVertex !== undefined && this.dependencyGraph.existsEdge(smallerRangeVertex, currentRangeVertex)) { smallerCache = smallerRangeVertex.getCriterionFunctionValues(cacheKey); } else { smallerCache = new Map(); } const newCache = new Map(); smallerCache.forEach(([value, criterionLambdas], key) => { const filteredValues = ifFilter(criterionLambdas, restConditionRanges.map(rcr => getRangeValues(this.dependencyGraph, rcr)), Array.from(getRangeValues(this.dependencyGraph, restValuesRange)).map(this.mapFunction)[Symbol.iterator]()); const newCacheValue = this.composeFunction(value, this.reduceFunction(filteredValues)); this.interpreter.stats.incrementCriterionFunctionPartialCacheUsed(); newCache.set(key, [newCacheValue, criterionLambdas]); }); return newCache; } } exports.CriterionFunctionCompute = CriterionFunctionCompute; class Condition { constructor(conditionRange, criterionPackage) { this.conditionRange = conditionRange; this.criterionPackage = criterionPackage; } } exports.Condition = Condition; function* getRangeValues(dependencyGraph, cellRange) { for (const cellFromRange of cellRange.addresses(dependencyGraph)) { yield (0, _InterpreterValue.getRawValue)(dependencyGraph.getScalarValue(cellFromRange)); } } function* ifFilter(criterionLambdas, conditionalIterables, computableIterable) { for (const computable of computableIterable) { const conditionalSplits = conditionalIterables.map(conditionalIterable => (0, _generatorUtils.split)(conditionalIterable)); if (!conditionalSplits.every(cs => Object.prototype.hasOwnProperty.call(cs, 'value'))) { return; } const conditionalFirsts = conditionalSplits.map(cs => (0, _InterpreterValue.getRawValue)(cs.value)); if (zip(conditionalFirsts, criterionLambdas).every(([conditionalFirst, criterionLambda]) => criterionLambda(conditionalFirst))) { yield computable; } conditionalIterables = conditionalSplits.map(cs => cs.rest); } } function zip(arr1, arr2) { const result = []; for (let i = 0; i < Math.min(arr1.length, arr2.length); i++) { result.push([arr1[i], arr2[i]]); } return result; }