hyperformula
Version:
HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas
143 lines (141 loc) • 5.34 kB
JavaScript
;
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;
}