store events in SortedMap to avoid sort on render
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Some checks reported errors
continuous-integration/drone/push Build encountered an error
This commit is contained in:
parent
47d92fe171
commit
5d259cee95
@ -53,7 +53,6 @@ const Timeline = (props: TimelineProps) => {
|
||||
return followDistance === props.followDistance;
|
||||
};
|
||||
const a = [...nts.filter(a => a.kind !== EventKind.LiveEvent)];
|
||||
props.noSort || a.sort((a, b) => b.created_at - a.created_at);
|
||||
return a
|
||||
?.filter(a => (props.postsOnly ? !a.tags.some(b => b[0] === "e") : true))
|
||||
.filter(a => (props.ignoreModeration || !isEventMuted(a)) && checkFollowDistance(a));
|
||||
|
@ -43,8 +43,7 @@ export function ThreadContextWrapper({ link, children }: { link: NostrLink; chil
|
||||
const chains = new Map<u256, Array<TaggedNostrEvent>>();
|
||||
if (feed.thread) {
|
||||
feed.thread
|
||||
?.sort((a, b) => b.created_at - a.created_at)
|
||||
.filter(a => !isBlocked(a.pubkey))
|
||||
?.filter(a => !isBlocked(a.pubkey))
|
||||
.forEach(v => {
|
||||
const replyTo = replyChainKey(v);
|
||||
if (replyTo) {
|
||||
|
85
packages/shared/src/SortedMap/SortedMap.test.ts
Normal file
85
packages/shared/src/SortedMap/SortedMap.test.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import RBSortedMap from "../../../../tests/RBSortedMap.ts";
|
||||
|
||||
import SortedMap from "./SortedMap.tsx";
|
||||
|
||||
function runTestsForMap(MapConstructor: any, mapName: string) {
|
||||
describe(mapName, () => {
|
||||
it("should maintain order based on keys when no custom comparator is provided", () => {
|
||||
const map = new MapConstructor();
|
||||
map.set(5, "five");
|
||||
map.set(3, "three");
|
||||
map.set(8, "eight");
|
||||
|
||||
const first = map.first();
|
||||
const last = map.last();
|
||||
|
||||
expect(first).toEqual([3, "three"]);
|
||||
expect(last).toEqual([8, "eight"]);
|
||||
});
|
||||
|
||||
it("should maintain order based on custom comparator", () => {
|
||||
const comparator = (a: [string, number], b: [string, number]) => a[1] - b[1];
|
||||
const map = new MapConstructor(undefined, comparator);
|
||||
|
||||
map.set("a", 5);
|
||||
map.set("b", 3);
|
||||
map.set("c", 8);
|
||||
|
||||
const first = map.first();
|
||||
const last = map.last();
|
||||
|
||||
expect(first).toEqual(["b", 3]);
|
||||
expect(last).toEqual(["c", 8]);
|
||||
});
|
||||
|
||||
it("should get correct value by key", () => {
|
||||
const map = new MapConstructor();
|
||||
map.set(5, "five");
|
||||
|
||||
const value = map.get(5);
|
||||
|
||||
expect(value).toBe("five");
|
||||
});
|
||||
|
||||
it("should delete entry by key", () => {
|
||||
const map = new MapConstructor();
|
||||
map.set(5, "five");
|
||||
expect(map.has(5)).toBe(true);
|
||||
|
||||
map.delete(5);
|
||||
expect(map.has(5)).toBe(false);
|
||||
});
|
||||
|
||||
it("should iterate in order", () => {
|
||||
const map = new MapConstructor();
|
||||
map.set(5, "five");
|
||||
map.set(3, "three");
|
||||
map.set(8, "eight");
|
||||
|
||||
const entries: [number, string][] = [];
|
||||
for (const entry of map.entries()) {
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
expect(entries).toEqual([
|
||||
[3, "three"],
|
||||
[5, "five"],
|
||||
[8, "eight"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("should give correct size", () => {
|
||||
const map = new MapConstructor();
|
||||
map.set(5, "five");
|
||||
map.set(3, "three");
|
||||
|
||||
expect(map.size).toBe(2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Run the tests for both map implementations.
|
||||
runTestsForMap(SortedMap, "SortedMap");
|
||||
runTestsForMap(RBSortedMap, "RBSortedMap");
|
150
packages/shared/src/SortedMap/SortedMap.tsx
Normal file
150
packages/shared/src/SortedMap/SortedMap.tsx
Normal file
@ -0,0 +1,150 @@
|
||||
type Comparator<K, V> = (a: [K, V], b: [K, V]) => number;
|
||||
|
||||
export class SortedMap<K, V> {
|
||||
private map: Map<K, V>;
|
||||
private sortedKeys: K[];
|
||||
private compare: Comparator<K, V>;
|
||||
|
||||
constructor(initialEntries?: Iterable<readonly [K, V]>, compare?: string | Comparator<K, V>) {
|
||||
this.map = new Map(initialEntries || []);
|
||||
|
||||
if (compare) {
|
||||
if (typeof compare === "string") {
|
||||
this.compare = (a, b) => (a[1][compare] > b[1][compare] ? 1 : a[1][compare] < b[1][compare] ? -1 : 0);
|
||||
} else {
|
||||
this.compare = compare;
|
||||
}
|
||||
} else {
|
||||
this.compare = (a, b) => (a[0] > b[0] ? 1 : a[0] < b[0] ? -1 : 0);
|
||||
}
|
||||
|
||||
this.sortedKeys = initialEntries ? [...this.map.entries()].sort(this.compare).map(([key]) => key) : [];
|
||||
}
|
||||
|
||||
private binarySearch(key: K, value: V): number {
|
||||
let left = 0;
|
||||
let right = this.sortedKeys.length;
|
||||
while (left < right) {
|
||||
const mid = (left + right) >> 1;
|
||||
const midKey = this.sortedKeys[mid];
|
||||
const midValue = this.map.get(midKey) as V;
|
||||
|
||||
if (this.compare([key, value], [midKey, midValue]) < 0) {
|
||||
right = mid;
|
||||
} else {
|
||||
left = mid + 1;
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
set(key: K, value: V) {
|
||||
const exists = this.map.has(key);
|
||||
this.map.set(key, value);
|
||||
|
||||
if (exists) {
|
||||
const index = this.sortedKeys.indexOf(key);
|
||||
if (index !== -1) {
|
||||
this.sortedKeys.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const insertAt = this.binarySearch(key, value);
|
||||
this.sortedKeys.splice(insertAt, 0, key);
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
return this.map.get(key);
|
||||
}
|
||||
|
||||
last(): [K, V] | undefined {
|
||||
if (this.sortedKeys.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const key = this.sortedKeys[this.sortedKeys.length - 1];
|
||||
return [key, this.map.get(key) as V];
|
||||
}
|
||||
|
||||
first(): [K, V] | undefined {
|
||||
if (this.sortedKeys.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const key = this.sortedKeys[0];
|
||||
return [key, this.map.get(key) as V];
|
||||
}
|
||||
|
||||
*[Symbol.iterator](): Iterator<[K, V]> {
|
||||
for (const key of this.sortedKeys) {
|
||||
yield [key, this.map.get(key) as V];
|
||||
}
|
||||
}
|
||||
|
||||
*reverse(): Iterator<[K, V]> {
|
||||
for (let i = this.sortedKeys.length - 1; i >= 0; i--) {
|
||||
const key = this.sortedKeys[i];
|
||||
yield [key, this.map.get(key) as V];
|
||||
}
|
||||
}
|
||||
|
||||
*keys(): IterableIterator<K> {
|
||||
for (const key of this.sortedKeys) {
|
||||
yield key;
|
||||
}
|
||||
}
|
||||
|
||||
*values(): IterableIterator<V> {
|
||||
for (const key of this.sortedKeys) {
|
||||
yield this.map.get(key) as V;
|
||||
}
|
||||
}
|
||||
|
||||
*entries(): IterableIterator<[K, V]> {
|
||||
for (const key of this.sortedKeys) {
|
||||
yield [key, this.map.get(key) as V];
|
||||
}
|
||||
}
|
||||
|
||||
*range(
|
||||
options: {
|
||||
gte?: K;
|
||||
lte?: K;
|
||||
direction?: "asc" | "desc";
|
||||
} = {},
|
||||
): IterableIterator<[K, V]> {
|
||||
const { gte, lte, direction = "asc" } = options;
|
||||
|
||||
const startIndex = gte ? this.binarySearch(gte, this.map.get(gte) as V) : 0;
|
||||
const endIndex = lte ? this.binarySearch(lte, this.map.get(lte) as V) : this.sortedKeys.length;
|
||||
|
||||
if (direction === "asc") {
|
||||
for (let i = startIndex; i < endIndex; i++) {
|
||||
const key = this.sortedKeys[i];
|
||||
yield [key, this.map.get(key) as V];
|
||||
}
|
||||
} else {
|
||||
for (let i = endIndex - 1; i >= startIndex; i--) {
|
||||
const key = this.sortedKeys[i];
|
||||
yield [key, this.map.get(key) as V];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
has(key: K): boolean {
|
||||
return this.map.has(key);
|
||||
}
|
||||
|
||||
delete(key: K): boolean {
|
||||
if (this.map.delete(key)) {
|
||||
const index = this.sortedKeys.indexOf(key);
|
||||
if (index !== -1) {
|
||||
this.sortedKeys.splice(index, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.map.size;
|
||||
}
|
||||
}
|
@ -5,3 +5,4 @@ export * from "./work-queue";
|
||||
export * from "./feed-cache";
|
||||
export * from "./invoices";
|
||||
export * from "./dexie-like";
|
||||
export * from "./SortedMap/SortedMap";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { appendDedupe } from "@snort/shared";
|
||||
import { appendDedupe, SortedMap } from "@snort/shared";
|
||||
import { EventExt, EventType, TaggedNostrEvent, u256 } from ".";
|
||||
import { findTag } from "./utils";
|
||||
|
||||
@ -196,7 +196,7 @@ export class FlatNoteStore extends HookedNoteStore<Array<TaggedNostrEvent>> {
|
||||
*/
|
||||
export class KeyedReplaceableNoteStore extends HookedNoteStore<Array<TaggedNostrEvent>> {
|
||||
#keyFn: (ev: TaggedNostrEvent) => string;
|
||||
#events: Map<string, TaggedNostrEvent> = new Map();
|
||||
#events: SortedMap<string, TaggedNostrEvent> = new SortedMap([], (a, b) => b[1].created_at - a[1].created_at);
|
||||
|
||||
constructor(fn: (ev: TaggedNostrEvent) => string) {
|
||||
super();
|
||||
|
Loading…
Reference in New Issue
Block a user