UNPKG

hyperformula

Version:

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

143 lines (141 loc) 5.34 kB
"use strict"; exports.__esModule = true; exports.compare = compare; exports.findLastMatchingIndex = findLastMatchingIndex; exports.findLastOccurrenceInOrderedArray = findLastOccurrenceInOrderedArray; exports.findLastOccurrenceInOrderedRange = findLastOccurrenceInOrderedRange; var _Cell = require("../Cell"); var _InterpreterValue = require("./InterpreterValue"); /** * @license * Copyright (c) 2025 Handsoncode. All rights reserved. */ const NOT_FOUND = -1; /* * Searches for the searchKey in a sorted 1-D range. * * Options: * - searchCoordinate - must be set to either 'row' or 'col' to indicate the dimension of the search, * - orderingDirection - must be set to either 'asc' or 'desc' to indicate the ordering direction for the search range, * - ifNoMatch - must be set to 'returnLowerBound', 'returnUpperBound' or 'returnNotFound' * * If the search range contains duplicates, returns the last matching value. If no value found in the range satisfies the above, returns -1. * * Note: this function does not normalize input strings. */ function findLastOccurrenceInOrderedRange(searchKey, range, { searchCoordinate, orderingDirection, ifNoMatch }, dependencyGraph) { const start = range.start[searchCoordinate]; const end = searchCoordinate === 'col' ? range.effectiveEndColumn(dependencyGraph) : range.effectiveEndRow(dependencyGraph); const getValueFromIndexFn = searchCoordinate === 'col' ? index => (0, _InterpreterValue.getRawValue)(dependencyGraph.getCellValue((0, _Cell.simpleCellAddress)(range.sheet, index, range.start.row))) : index => (0, _InterpreterValue.getRawValue)(dependencyGraph.getCellValue((0, _Cell.simpleCellAddress)(range.sheet, range.start.col, index))); const compareFn = orderingDirection === 'asc' ? (left, right) => compare(left, right) : (left, right) => -compare(left, right); const foundIndex = findLastMatchingIndex(index => compareFn(searchKey, getValueFromIndexFn(index)) >= 0, start, end); const foundValue = getValueFromIndexFn(foundIndex); if (foundValue === searchKey) { return foundIndex - start; } if (ifNoMatch === 'returnLowerBound') { if (foundIndex === NOT_FOUND) { return orderingDirection === 'asc' ? NOT_FOUND : 0; } if (typeof foundValue !== typeof searchKey) { return NOT_FOUND; } // here: foundValue !== searchKey if (orderingDirection === 'asc') { return foundIndex - start; } // orderingDirection === 'desc' const nextIndex = foundIndex + 1; return nextIndex <= end ? nextIndex - start : NOT_FOUND; } if (ifNoMatch === 'returnUpperBound') { if (foundIndex === NOT_FOUND) { return orderingDirection === 'asc' ? 0 : NOT_FOUND; } if (typeof foundValue !== typeof searchKey) { return NOT_FOUND; } // here: foundValue !== searchKey if (orderingDirection === 'desc') { return foundIndex - start; } // orderingDirection === 'asc' const nextIndex = foundIndex + 1; return nextIndex <= end ? nextIndex - start : NOT_FOUND; } // ifNoMatch === 'returnNotFound' return NOT_FOUND; } /* * Searches for the searchKey in a sorted array. * Param orderingDirection must be set to either 'asc' or 'desc' to indicate the ordering direction of the array. * * Semantics: * - If orderingDirection === 'asc', searches for the lower bound for the searchKey value. * - If orderingDirection === 'desc', searches for the upper bound for the searchKey value. * - If the array contains duplicates, returns the last matching value. * - If no value in the range satisfies the above, returns -1. */ function findLastOccurrenceInOrderedArray(searchKey, array, orderingDirection = 'asc') { const predicate = orderingDirection === 'asc' ? index => compare(searchKey, array[index]) >= 0 : index => -compare(searchKey, array[index]) >= 0; return findLastMatchingIndex(predicate, 0, array.length - 1); } /* * Returns: * - the last element in the range for which predicate === true or, * - value -1 if predicate === false for all elements. * Assumption: All elements for which predicate === true are before the elements for which predicate === false. */ function findLastMatchingIndex(predicate, startRange, endRange) { let start = startRange; let end = endRange; while (start < end) { const pivot = Math.ceil((start + end) / 2); if (predicate(pivot)) { start = pivot; } else { end = pivot - 1; } } if (start === end && predicate(start)) { return start; } return NOT_FOUND; } /* * numbers < strings < false < true */ function compare(left, right) { if (typeof left === typeof right) { if (left === _InterpreterValue.EmptyValue) { return 0; } return left < right ? -1 : left > right ? 1 : 0; } if (left === _InterpreterValue.EmptyValue) { return -1; } if (right === _InterpreterValue.EmptyValue) { return 1; } if (right instanceof _Cell.CellError) { return -1; } if (typeof left === 'number' && typeof right === 'string') { return -1; } if (typeof left === 'number' && typeof right === 'boolean') { return -1; } if (typeof left === 'string' && typeof right === 'number') { return 1; } if (typeof left === 'string' && typeof right === 'boolean') { return -1; } return 1; }