Skip to content

Commit

Permalink
Added new method betweenLexoRanks which allows to generate multiple b…
Browse files Browse the repository at this point in the history
…etween lexoRank based on user input
  • Loading branch information
mananrshah committed Oct 16, 2024
1 parent 1468280 commit 4d2755f
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ const any1LexoRank = LexoRank.min();
const any2LexoRank = any1LexoRank.genNext().genNext();
// calculate between
const betweenLexoRank = any1LexoRank.between(any2LexoRank);
// calculate between lexoRanks
const betweenLexoRanks = any1LexoRank.betweenLexoRanks(any2LexoRank, 5);
```

## Related projects
Expand Down
103 changes: 103 additions & 0 deletions __tests__/lexoRank.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,107 @@ describe('LexoRank', () => {
const between = prevRank.between(nextRank);
expect(between.toString()).toEqual(expected);
});

it('find the nearest power', () => {
expect(LexoRank.nearestPowerOfTwo(1)).toEqual(1);
expect(LexoRank.nearestPowerOfTwo(5)).toEqual(3);
expect(LexoRank.nearestPowerOfTwo(6)).toEqual(3);
expect(LexoRank.nearestPowerOfTwo(8)).toEqual(4);
expect(LexoRank.nearestPowerOfTwo(15)).toEqual(4);
expect(LexoRank.nearestPowerOfTwo(31)).toEqual(5);
expect(LexoRank.nearestPowerOfTwo(63)).toEqual(6);
expect(LexoRank.nearestPowerOfTwo(124)).toEqual(7);
expect(LexoRank.nearestPowerOfTwo(127)).toEqual(7);
});

it('find the betweenLexoRanks for 1 lexoRanks to generate', () => {
const minRank = LexoRank.min();
const maxRank = LexoRank.max();
const lexoRanks: LexoRank[] = minRank.betweenLexoRanks(maxRank, 1);
expect(lexoRanks.length).toEqual(1);
expect(lexoRanks[0].toString()).toEqual('0|hzzzzz:');
});

it('find the betweenLexoRanks for 2 lexoRanks to generate', () => {
const minRank = LexoRank.min();
const maxRank = LexoRank.max();
const lexoRanks: LexoRank[] = minRank.betweenLexoRanks(maxRank, 2);
expect(lexoRanks.length).toEqual(2);
expect(lexoRanks[0].toString()).toEqual('0|8zzzzz:');
expect(lexoRanks[1].toString()).toEqual('0|hzzzzz:');
expect(checkSortedInAscendingOrder(lexoRanks)).toEqual(true);
});

it('find the betweenLexoRanks for 2 lexoRanks when one lexoRank is long in length', () => {
const maxRank = LexoRank.parse('0|i00006:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzr');
const minRank = LexoRank.parse('0|i00006:zzr');
const lexoRanks: LexoRank[] = minRank.betweenLexoRanks(maxRank, 2);
expect(lexoRanks.length).toEqual(2);
expect(lexoRanks[0].toString()).toEqual('0|i00006:zzt');
expect(lexoRanks[1].toString()).toEqual('0|i00006:zzv');
expect(checkSortedInAscendingOrder(lexoRanks)).toEqual(true);
});

it('find the betweenLexoRanks for 3 lexoRanks to generate', () => {
const minRank = LexoRank.min();
const maxRank = LexoRank.max();
const lexoRanks: LexoRank[] = minRank.betweenLexoRanks(maxRank, 3);
expect(lexoRanks.length).toEqual(3);
expect(lexoRanks[0].toString()).toEqual('0|8zzzzz:');
expect(lexoRanks[1].toString()).toEqual('0|hzzzzz:');
expect(lexoRanks[2].toString()).toEqual('0|qzzzzz:');
expect(checkSortedInAscendingOrder(lexoRanks)).toEqual(true);
});

it('find the betweenLexoRanks for 4 lexoRanks to generate', () => {
const minRank = LexoRank.min();
const maxRank = LexoRank.max();
const lexoRanks: LexoRank[] = minRank.betweenLexoRanks(maxRank, 4);
expect(lexoRanks.length).toEqual(4);
expect(lexoRanks[0].toString()).toEqual('0|4hzzzz:');
expect(lexoRanks[1].toString()).toEqual('0|8zzzzz:');
expect(lexoRanks[2].toString()).toEqual('0|dhzzzz:');
expect(lexoRanks[3].toString()).toEqual('0|hzzzzz:');
expect(checkSortedInAscendingOrder(lexoRanks)).toEqual(true);
});

it('find the betweenLexoRanks for 7 lexoRanks to generate', () => {
const minRank = LexoRank.min();
const maxRank = LexoRank.max();
const lexoRanks: LexoRank[] = minRank.betweenLexoRanks(maxRank, 7);
expect(lexoRanks.length).toEqual(7);
expect(lexoRanks[0].toString()).toEqual('0|4hzzzz:');
expect(lexoRanks[1].toString()).toEqual('0|8zzzzz:');
expect(lexoRanks[2].toString()).toEqual('0|dhzzzz:');
expect(lexoRanks[3].toString()).toEqual('0|hzzzzz:');
expect(lexoRanks[4].toString()).toEqual('0|mhzzzz:');
expect(lexoRanks[5].toString()).toEqual('0|qzzzzz:');
expect(lexoRanks[6].toString()).toEqual('0|vhzzzz:');
expect(checkSortedInAscendingOrder(lexoRanks)).toEqual(true);
});

it('find the betweenLexoRanks for 8 lexoRanks when one lexoRank is long in length', () => {
const maxRank = LexoRank.parse('0|i00006:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzr');
const minRank = LexoRank.parse('0|i00006:zzr');
const lexoRanks: LexoRank[] = minRank.betweenLexoRanks(maxRank, 8);
expect(lexoRanks.length).toEqual(8);
expect(lexoRanks[0].toString()).toEqual('0|i00006:zzri');
expect(lexoRanks[1].toString()).toEqual('0|i00006:zzs');
expect(lexoRanks[2].toString()).toEqual('0|i00006:zzsi');
expect(lexoRanks[3].toString()).toEqual('0|i00006:zzt');
expect(lexoRanks[4].toString()).toEqual('0|i00006:zzti');
expect(lexoRanks[5].toString()).toEqual('0|i00006:zzu');
expect(lexoRanks[6].toString()).toEqual('0|i00006:zzui');
expect(lexoRanks[7].toString()).toEqual('0|i00006:zzv');
expect(checkSortedInAscendingOrder(lexoRanks)).toEqual(true);
});

function checkSortedInAscendingOrder(arr) {
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]) {
return false;
}
}
return true;
}
});
76 changes: 76 additions & 0 deletions src/lexoRank/lexoRank.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,69 @@ export class LexoRank {
return new LexoRank(this.bucket, LexoRank.between(this.decimal, other.decimal));
}

public betweenLexoRanks(other: LexoRank, ranksToGenerate: number): LexoRank[] {

if (!this.bucket.equals(other.bucket)) {
throw new Error('Between works only within the same bucket');
}

if (ranksToGenerate == 0) {
return [];
}

let newLeft: LexoRank = this;
let newRight: LexoRank = other;

const cmp = this.decimal.compareTo(other.decimal);
if (cmp > 0) {
newLeft = other;
newRight = this;
}

if (cmp === 0) {
throw new Error(
'Try to rank between issues with same rank this=' +
this +
' other=' +
other +
' this.decimal=' +
this.decimal +
' other.decimal=' +
other.decimal
);
}

if (ranksToGenerate == 1) {
return [new LexoRank(newLeft.bucket, LexoRank.between(newLeft.decimal, newRight.decimal))];
}

const binaryDepth: number = LexoRank.nearestPowerOfTwo(ranksToGenerate);

const lexoRanks: LexoRank[] = [];

LexoRank.prepareLexoRanks(newLeft, newRight, 1, binaryDepth, lexoRanks);

return lexoRanks.slice(0, ranksToGenerate);
}

private static prepareLexoRanks(left: LexoRank, right: LexoRank, currentDepth: number, maxDepth: number, lexoRanks: LexoRank[]) {
// find the midpoint for this operation
const rankAtThisLevel: LexoRank = left.between(right);

if (currentDepth < maxDepth) {
// recursive into the left subtree
LexoRank.prepareLexoRanks(left, rankAtThisLevel, currentDepth + 1, maxDepth, lexoRanks);
}

// insert the LexoRank at this level
lexoRanks.push(rankAtThisLevel);

if (currentDepth < maxDepth) {
// recursive into the right subtree
LexoRank.prepareLexoRanks(rankAtThisLevel, right, currentDepth + 1, maxDepth, lexoRanks);
}
}

public getBucket(): LexoRankBucket {
return this.bucket;
}
Expand Down Expand Up @@ -366,4 +429,17 @@ export class LexoRank {

return this.value.localeCompare(other.value);
}

// Visible for unit test
public static nearestPowerOfTwo(noOfRanks: number): number {
if (noOfRanks < 1) return 0;

let power = 1;
while (Math.pow(2, power) - 1 < noOfRanks) {
power++;
}

return power;
}

}

0 comments on commit 4d2755f

Please sign in to comment.