@pranavpatel.ca/algo-kombin
Version:
KombiN is a library for bijectively mapping all ordered pairs from two finite sets into a single linear index. It orders pairs by ascending weight (sum of indices) using a three-region zig-zag algorithm, enabling O(1) bidirectional lookups between pairs a
193 lines (192 loc) • 7.73 kB
JavaScript
"use strict";
// Copyright (c) 2020 Pranavkumar Patel. All rights reserved. Licensed under the MIT license.
Object.defineProperty(exports, "__esModule", { value: true });
exports.Table = void 0;
/**
* Table provides O(1) bijective mapping between ordered pairs from two finite sets and a unique linear index.
* All possible combination pairs are sorted by their weight (sum of indices) in ascending order.
* Supports both zero-based and one-based indexing.
*/
class Table {
/**
* Initializes a new instance of the Table class.
* @param lengthOfA Number of elements in the first set.
* @param lengthOfB Number of elements in the second set.
* @param zeroBasedIndex True for zero-based indexing, false for one-based indexing.
* @throws Error if lengthOfA or lengthOfB is 0 or less.
*/
constructor(lengthOfA, lengthOfB, zeroBasedIndex) {
if (lengthOfA < 1 || lengthOfB < 1) {
throw new Error('Length of both sets must be greater than 0.');
}
this.LengthOfA = lengthOfA;
this.LengthOfB = lengthOfB;
this.ZeroBasedIndex = zeroBasedIndex;
this.MaxIndexRange1 = this.MaxIndexRange2 = this.MaxIndexRange3 = 0;
this.MaxSumRange1 = this.MaxSumRange2 = this.MaxSumRange3 = 0;
this.Abstract();
}
/** Length of Set A */
LengthOfA;
/** Length of Set B */
LengthOfB;
/** Lower length of both sets */
LowerLength;
/** Maximum Sum for Range 1 */
MaxSumRange1;
/** Maximum Sum for Range 2 */
MaxSumRange2;
/** Maximum Sum for Range 3 */
MaxSumRange3;
/** Maximum Index for Range 1 */
MaxIndexRange1;
/** Maximum Index for Range 2 */
MaxIndexRange2;
/** Maximum Index for Range 3 */
MaxIndexRange3;
/** Zero based indexing or not */
ZeroBasedIndex;
/**
* Computes and sets internal values used for index and pair calculations.
*/
Abstract() {
this.LowerLength = this.LengthOfA < this.LengthOfB ? this.LengthOfA : this.LengthOfB;
const higherLength = this.LengthOfA > this.LengthOfB ? this.LengthOfA : this.LengthOfB;
const difference = higherLength - this.LowerLength;
const product = higherLength * this.LowerLength;
const sum = higherLength + this.LowerLength;
this.MaxSumRange1 = this.LowerLength + 1;
if (difference == 0) {
this.MaxIndexRange1 = (product * this.MaxSumRange1) / sum;
}
else if (difference == 1) {
this.MaxIndexRange1 = product / 2;
}
else if (difference >= 2) {
this.MaxSumRange2 = higherLength;
this.MaxIndexRange1 = (product - this.LowerLength * (sum - 1 - 2 * this.LowerLength)) / 2;
this.MaxIndexRange2 = (product + this.LowerLength * (sum - 1 - 2 * this.LowerLength)) / 2;
}
if (product >= 2) {
this.MaxSumRange3 = sum;
this.MaxIndexRange3 = product;
}
}
/**
* Returns the unique index value for the given combination pair (ai, bi).
* @param ai Element index of set A.
* @param bi Element index of set B.
* @returns Index value for the given combination pair.
* @throws Error if ai or bi has invalid value.
*/
GetIndexOfElements(ai, bi) {
if (this.ZeroBasedIndex) {
if (ai < 0 || bi < 0) {
throw new Error('Both element index values must be 0 or more.');
}
ai++;
bi++;
}
else if (ai < 1 || bi < 1) {
throw new Error('Both element index values must be 1 or more.');
}
let index, previousIndex;
const sum = ai + bi;
if (sum <= this.MaxSumRange1) {
previousIndex = sum - 2;
index =
(previousIndex % 2 == 0
? (previousIndex / 2) * (previousIndex + 1)
: ((previousIndex - 1) / 2) * previousIndex + previousIndex) + ai;
}
else if (sum <= this.MaxSumRange2) {
index =
this.MaxIndexRange1 +
(sum - (this.MaxSumRange1 + 1)) * this.LowerLength +
(this.LengthOfA < this.LengthOfB ? ai : this.LengthOfB + 1 - bi);
}
else if (sum <= this.MaxSumRange3) {
previousIndex = this.MaxSumRange3 - sum + 1;
index =
this.MaxIndexRange3 -
(previousIndex % 2 == 0
? (previousIndex / 2) * (previousIndex + 1)
: ((previousIndex - 1) / 2) * previousIndex + previousIndex) +
(this.MaxIndexRange3 < 2 ? ai : this.LengthOfB + 1 - bi);
}
else {
throw new Error(`Sum of both the element index values must not be greater than ${this.MaxSumRange3}`);
}
if (this.ZeroBasedIndex) {
index--;
}
return index;
}
/**
* Returns the combination pair corresponding to the given index value.
* @param index Index value of the combination pair.
* @returns Tuple [ai, bi] representing the combination pair.
* @throws Error if index has invalid value.
*/
GetElementsAtIndex(index) {
if (this.ZeroBasedIndex) {
if (index < 0) {
throw new Error('Index value must be 0 or more.');
}
index++;
}
else if (index < 1) {
throw new Error('Index value must be 1 or more.');
}
let ai, bi;
let previousIndex, sum;
if (index <= this.MaxIndexRange1) {
sum = Math.ceil((Math.sqrt(index * 8 + 1) + 1) / 2);
ai = index - ((sum - 1) * (sum - 2)) / 2;
bi = sum - ai;
}
else if (index <= this.MaxIndexRange2) {
sum =
this.MaxSumRange1 +
Math.floor((index - this.MaxIndexRange1) / this.LowerLength) -
((index - this.MaxIndexRange1) % this.LowerLength == 0 ? 1 : 0) +
1;
previousIndex = this.MaxIndexRange1 + (sum - 1 - this.MaxSumRange1) * this.LowerLength;
if (this.LengthOfA >= this.LengthOfB) {
bi = this.LengthOfB + 1 - (index - previousIndex);
ai = sum - bi;
}
else {
ai = index - previousIndex;
bi = sum - ai;
}
}
else if (index <= this.MaxIndexRange3) {
const generic_maxSumRange3 = this.MaxSumRange3 - (this.MaxSumRange2 == 0 ? this.MaxSumRange1 : this.MaxSumRange2);
const generic_index = index - (this.MaxIndexRange2 == 0 ? this.MaxIndexRange1 : this.MaxIndexRange2);
const b = 2 * generic_maxSumRange3 + 1;
const generic_Sum = Math.ceil((b - Math.sqrt(b * b - 8 * generic_index)) / 2);
sum = (this.MaxSumRange2 == 0 ? this.MaxSumRange1 : this.MaxSumRange2) + generic_Sum;
previousIndex =
(this.MaxIndexRange2 == 0 ? this.MaxIndexRange1 : this.MaxIndexRange2) +
(generic_Sum == 1 ? 0 : ((generic_Sum - 1) * (b - generic_Sum + 1)) / 2);
if (this.MaxIndexRange3 >= 2) {
bi = this.LengthOfB + 1 - (index - previousIndex);
ai = sum - bi;
}
else {
ai = index - previousIndex;
bi = sum - ai;
}
}
else {
throw new Error(`Index value must not be greater than ${this.MaxIndexRange3}`);
}
if (this.ZeroBasedIndex) {
ai--;
bi--;
}
return [ai, bi];
}
}
exports.Table = Table;