Skip to content

Instantly share code, notes, and snippets.

@wzulfikar
Last active August 30, 2022 01:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wzulfikar/e6014c85b9bcbe6ec152880e6942cd3d to your computer and use it in GitHub Desktop.
Save wzulfikar/e6014c85b9bcbe6ec152880e6942cd3d to your computer and use it in GitHub Desktop.
Lexorank example in TS
import sortBy from "lodash/sortBy"; // https://github.com/kvandake/lexorank-ts
import { LexoRank } from "lexorank";
const cards = [
{ id: 1, rank: "", flag: "0" }, // flag 0: first card, flag 1: last card. We use flag so we don't have to recalculate first and last card.
{ id: 2, rank: "", flag: "" },
{ id: 3, rank: "", flag: "1" },
];
function parseRank(rank: string) {
const lexorank = LexoRank.parse(rank);
return {
prev: () => lexorank.genPrev().toString(),
next: () => lexorank.genNext().toString(),
};
}
let stepper = 0;
function printOrder(title: string, cards: any) {
console.log(
`${++stepper}. ${title}:`,
sortBy(cards, (card) => card.rank)
.filter((card) => card.rank != "")
.map((card) => `${card.id}-${card.rank}`)
);
}
console.log(
"- initial order:",
cards.map((card) => card.id)
);
console.log(
"min/max/middle:",
LexoRank.min().toString(),
LexoRank.max().toString(),
LexoRank.middle().toString()
);
// Create first rank (eg. adding first card). We need to use `.middle`
// because it's possible that a new card will be moved to a position before l1.
const l1 = LexoRank.middle();
cards[0].rank = l1.toString();
printOrder("Create first rank", cards); // [1]
// Create second rank (eg. appending card)
const l2 = l1.genNext();
cards[1].rank = l2.toString(); // current order: [1, 2]
printOrder("Create second rank", cards); // [1, 2]
// Create third rank
const l3 = l2.genNext();
cards[2].rank = l3.toString();
printOrder("Create third rank", cards); // [1, 2, 3]
// Move l3 to between l1 and l2
cards[2].rank = l1.between(l2).toString();
printOrder("Move l3 to between l1 and l2", cards); // [1, 3, 2]
// You can't move card to "in between" using `genNext` as it will move to the end of order.
// Example: move l1 between l3 and l2 using `l3.genNext()`
cards[0].rank = l3.genNext().toString();
printOrder("Move l1 using `l3.genNext`", cards); // [3, 2, 1]
// Let's move l1 back to before l3. But now let's use `parse` to simulate
// parsing rank from database.
cards[0].rank = LexoRank.parse(cards[2].rank).genPrev().toString();
cards[0].flag = "0"; // We know that l1 is now the first card, hence we assign the flag
printOrder("Move l1 before l3", cards); // [1, 3, 2]
// And move l3 to the end of list (after l2). Now we'll use our own `parseRank` function.
cards[2].rank = parseRank(cards[1].rank).next();
cards[2].flag = "1";
printOrder("Move l3 to end of list", cards); // [1, 2, 3]
// Create new card and put at the beginning of list
const firstCard = cards.find((card) => card.flag == "0")!;
firstCard.flag = ""; // Remove flag because we know it'll no longer be the first card
const newCard = { id: 4, rank: parseRank(firstCard.rank).prev(), flag: "0" };
cards.push(newCard);
printOrder("Create new card and put at the beginning of list", cards); // [4, 1, 2, 3]
// Summary
// 1. Store in db: rank, flag
// 2. When reordering card, only affected card need to update the rank
// 3. When creating new card (hence appending it to the list), two values need to update:
// - Assign "last card" flag to the new card
// - Remove "last card" flag from the old card (if any)
// 4. When moving card to first order, two values need to update:
// - Assign "first card" flag to the moved card
// - Remove "first card" flag from the old card
// 5. Finding first and last card in a list can be O(1) because they're indexable
@wzulfikar
Copy link
Author

Output:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment