Skip to content

Commit

Permalink
Add: reindex function (#2)
Browse files Browse the repository at this point in the history
* Fix: badge

* Add: reindex function

* Test

* Add: keyToIndexCache
  • Loading branch information
eight04 committed Mar 29, 2023
1 parent e358c18 commit ad54d48
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
===========

[![.github/workflows/build.yml](https://github.com/eight04/store/actions/workflows/build.yml/badge.svg)](https://github.com/eight04/store/actions/workflows/build.yml)
[![codecov](https://codecov.io/gh/eight04/store/branch/master/graph/badge.svg)](https://codecov.io/gh/eight04/store)
[![Coverage Status](https://coveralls.io/repos/github/eight04/store/badge.svg?branch=main)](https://coveralls.io/github/eight04/store?branch=main)

A reactive store library designed to work with large collection. Inspired by svelte/store.

Expand Down
66 changes: 66 additions & 0 deletions index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -624,3 +624,69 @@ export function sort<Item>(
}
}


/**
* Reindex a collection into a new set
*/
export function reindex<C extends KeyedCollection<any, any>, Index>(
$c: C,
indexFn: (item: ItemFromCollection<C>) => Index
): SetStore<readonly [Index, ItemFromCollection<C>[]]> {
const $s = new SetStore<readonly [Index, ItemFromCollection<C>[]]>(t => t[0]);
const keyToIndexCache = new Map<ReturnType<typeof $c.key>, Index>();
onChange({
added: [...$c.get()],
updated: [],
removed: [],
ts: $c.ts
});
$c.on("change", onChange);
$s.addCleanup(() => $c.off("change", onChange));
return $s;

function onChange({added, updated, removed, ts}: CollectionDelta<ItemFromCollection<C>>) {
const modified = new Map<Index, ItemFromCollection<C>[]>();

for (const item of [...updated, ...removed]) {
// FIXME: updated items will be move to the last. is that intended?
const key = $c.key(item);
const index = keyToIndexCache.get(key)!;
if (!modified.has(index)) {
modified.set(index, $s.map.get(index)![1]);
}
modified.set(index, modified.get(index)!.filter(i => key !== $c.key(i)))
keyToIndexCache.delete(key);
}

for (const item of [...updated, ...added]) {
const index = indexFn(item);
const key = $c.key(item);
if (!modified.has(index)) {
modified.set(index, $s.map.get(index)?.[1] || []);
}
modified.set(index, [...modified.get(index)!, item]);
keyToIndexCache.set(key, index);
}

const toAdd = [];
const toUpdate = [];
const toRemove = [];

for (const [index, value] of modified.entries()) {
if (!$s.map.has(index)) {
toAdd.push([index, value] as const);
} else if (value.length) {
toUpdate.push([index, value] as const);
} else {
// FIXME: value is always an empty array here. which should be set to removed items
toRemove.push([index, value] as const);
}
}

$s.set({
added: toAdd,
updated: toUpdate,
removed: toRemove
}, ts);
}
}
66 changes: 66 additions & 0 deletions test/reindex.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* eslint-env mocha */
import * as assert from "node:assert";
import {ArrayStore, reindex} from "../index.mjs";

class Item {
id: number;
value: number;
constructor(id: any, value: any) {
this.id = id;
this.value = value;
}
}

function cmp(a: Item, b: Item) {
return a.value - b.value;
}

describe("reindex", () => {
it("basic", () => {
const $s = new ArrayStore<Item>(i => i.id, cmp);
const added = [new Item(1, 2), new Item(2, 1)];
$s.set({ added });
const $r = reindex($s, i => i.value);
assert.deepStrictEqual($r.get(), new Set([
[1, [ new Item(2, 1)]],
[2, [ new Item(1, 2)]]
]));

$s.set({ removed: [new Item(1, 2)] });
assert.deepStrictEqual($r.get(), new Set([
[1, [ new Item(2, 1)]],
]));

$s.set({ updated: [new Item(2, 3)] });
assert.deepStrictEqual($r.get(), new Set([
[3, [ new Item(2, 3)]],
]));
});

it("duplicated index", () => {
const $s = new ArrayStore<Item>(i => i.id, cmp);
const $r = reindex($s, i => i.value);
const added = [new Item(1, 2), new Item(2, 1), new Item(3, 1)];
$s.set({ added });
assert.deepStrictEqual($r.get(), new Set([
[1, [ new Item(2, 1), new Item(3, 1) ]],
[2, [ new Item(1, 2)]]
]));
});

it("index doesn't match when removed", () => {
const $s = new ArrayStore<Item>(i => i.id, cmp);
const added = [new Item(1, 2), new Item(2, 1)];
$s.set({ added });
const $r = reindex($s, i => i.value);
assert.deepStrictEqual($r.get(), new Set([
[1, [ new Item(2, 1)]],
[2, [ new Item(1, 2)]]
]));

$s.set({ removed: [new Item(1, 3)] });
assert.deepStrictEqual($r.get(), new Set([
[1, [ new Item(2, 1)]],
]));
})
});

0 comments on commit ad54d48

Please sign in to comment.