Skip to content

Instantly share code, notes, and snippets.

@webstrand
Last active December 6, 2024 03:30
Show Gist options
  • Save webstrand/a09b6ec6b041b91f8e9543c2ff45f31a to your computer and use it in GitHub Desktop.
Save webstrand/a09b6ec6b041b91f8e9543c2ff45f31a to your computer and use it in GitHub Desktop.
function ordgroupIterable<reference, key>(
refs: Accessor<Iterable<reference>>,
keyFn: (ref: reference) => key
): Accessor<ReadonlyMap<key, Accessor<readonly reference[]>>> {
const signals = createMemo((keyset: Map<key, Signal<readonly reference[]>>) => {
const exists = new Set();
let changed = false;
// Check for group creations
const ord: key[] = [];
for(const ref of refs()) {
const key = keyFn(ref);
exists.add(key);
if(!changed) ord.push(key);
if(keyset.has(key)) {
// group already exists, no work necessary
}
else {
changed = true;
keyset.set(key, createSignal<readonly reference[]>([], { equals: arrayEq }));
}
}
// Check for group deletions
const oldord: key[] = [];
for(const key of keyset.keys()) {
if(exists.has(key)) {
if(!changed) oldord.push(key);
}
else {
changed = true;
keyset.delete(key);
}
}
// Check that the keyset order has not changed
if(!changed) {
for(let i = 0; i < oldord.length; i++) {
if(ord[i] !== oldord[i]) {
changed = true;
break;
}
}
}
// We only create a new reference and trigger an update when the keyset changes.
return changed ? new Map(ord.map(key => [key, keyset.get(key)!])) : keyset;
}, new Map());
// Impure work of updating the signals
createComputed((prevGroups: ReadonlyMap<key, Signal<readonly reference[]>> | undefined) => {
const groups = signals();
// Fill new and existing groups with data
const work = Map.groupBy(refs(), keyFn);
for(const { 0: key, 1: { 1: set }} of groups) {
if(!work.has(key)) {
console.warn("Attempted to fill an unknown group");
continue;
}
set(work.get(key)!);
}
// Clean up any previous groups that no longer exist
if(prevGroups)
for(const { 0: key, 1: { 1: set } } of prevGroups) {
if(groups.has(key)) continue;
set([]);
}
return groups;
})
return createMemo(() => new Map(Array.from(signals(), ({ 0: key, 1: { 0: get }}) => [key, get])));
}
function arrayEq(a: readonly unknown[], b: readonly unknown[]): boolean {
if(a.length !== b.length) return false;
for(let i = 0; i < a.length; i++) {
if(a[i] !== b[i]) return false;
}
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment