Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Perf - Try improving suggestion performance. (#1639)
* fix: Perf - Use PairingHeap for SortedQueue * dev: Try using an A* algorithm to find suggestions.
- Loading branch information
Showing
12 changed files
with
1,226 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { PairingHeap } from './PairingHeap'; | ||
|
||
describe('PairingHeap', () => { | ||
test('Basic add and remove', () => { | ||
const compare = new Intl.Collator().compare; | ||
const values = ['one', 'two', 'three', 'four', 'five', 'six', 'seven']; | ||
const sorted = values.concat().sort(compare); | ||
const heap = new PairingHeap(compare); | ||
values.forEach((v) => heap.add(v)); | ||
expect(heap.length).toBe(values.length); | ||
const result = [...heap]; | ||
expect(result).toEqual(sorted); | ||
expect(heap.length).toBe(0); | ||
}); | ||
|
||
interface Person { | ||
name: string; | ||
} | ||
|
||
test('FIFO for latest', () => { | ||
const compareStr = new Intl.Collator().compare; | ||
const compare = (a: Person, b: Person) => compareStr(a.name, b.name); | ||
const names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July']; | ||
const people: Person[] = names.map((name) => ({ name })); | ||
const sorted = people.concat().sort(compare); | ||
const heap = new PairingHeap(compare); | ||
|
||
heap.add(people[0]); | ||
expect(heap.dequeue()).toBe(people[0]); | ||
expect(heap.length).toBe(0); | ||
expect(heap.peek()).toBeUndefined(); | ||
expect(heap.dequeue()).toBeUndefined(); | ||
|
||
heap.concat(people); | ||
expect(heap.dequeue()).toBe(sorted[0]); | ||
expect(heap.dequeue()).toBe(sorted[1]); | ||
heap.concat(people); | ||
expect(heap.dequeue()).toBe(sorted[0]); | ||
expect(heap.dequeue()).toBe(sorted[1]); | ||
expect(heap.peek()).toBe(sorted[2]); | ||
expect(heap.dequeue()).toBe(sorted[2]); | ||
expect(heap.dequeue()).toBe(sorted[2]); | ||
expect(heap.peek()).toBe(sorted[3]); | ||
|
||
heap.add(sorted[0]); | ||
expect(heap.peek()).toBe(sorted[0]); | ||
const copy = { ...sorted[0] }; | ||
// Make sure we get back the open we added. | ||
expect(heap.add(copy).peek()).toBe(copy); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
export interface PairHeapNode<T> { | ||
/** Value */ | ||
v: T; | ||
/** Siblings */ | ||
s: PairHeapNode<T> | undefined; | ||
/** Children */ | ||
c: PairHeapNode<T> | undefined; | ||
} | ||
|
||
export type CompareFn<T> = (a: T, b: T) => number; | ||
|
||
export class PairingHeap<T> implements IterableIterator<T> { | ||
private _heap: PairHeapNode<T> | undefined; | ||
private _size = 0; | ||
|
||
constructor(readonly compare: CompareFn<T>) {} | ||
|
||
add(v: T): this { | ||
this._heap = insert(this.compare, this._heap, v); | ||
++this._size; | ||
return this; | ||
} | ||
|
||
dequeue(): T | undefined { | ||
const n = this.next(); | ||
if (n.done) return undefined; | ||
return n.value; | ||
} | ||
|
||
concat(i: Iterable<T>): this { | ||
for (const v of i) { | ||
this.add(v); | ||
} | ||
return this; | ||
} | ||
|
||
next(): IteratorResult<T> { | ||
if (!this._heap) { | ||
return { value: undefined, done: true }; | ||
} | ||
const value = this._heap.v; | ||
--this._size; | ||
this._heap = removeHead(this.compare, this._heap); | ||
return { value }; | ||
} | ||
|
||
peek(): T | undefined { | ||
return this._heap?.v; | ||
} | ||
|
||
[Symbol.iterator](): IterableIterator<T> { | ||
return this; | ||
} | ||
|
||
get length(): number { | ||
return this._size; | ||
} | ||
} | ||
|
||
function removeHead<T>(compare: CompareFn<T>, heap: PairHeapNode<T> | undefined): PairHeapNode<T> | undefined { | ||
if (!heap || !heap.c) return undefined; | ||
return mergeSiblings(compare, heap.c); | ||
} | ||
|
||
function insert<T>(compare: CompareFn<T>, heap: PairHeapNode<T> | undefined, v: T): PairHeapNode<T> { | ||
const n: PairHeapNode<T> = { | ||
v, | ||
s: undefined, | ||
c: undefined, | ||
}; | ||
|
||
if (!heap || compare(v, heap.v) <= 0) { | ||
n.c = heap; | ||
return n; | ||
} | ||
|
||
n.s = heap.c; | ||
heap.c = n; | ||
return heap; | ||
} | ||
|
||
function merge<T>(compare: CompareFn<T>, a: PairHeapNode<T>, b: PairHeapNode<T>): PairHeapNode<T> { | ||
if (compare(a.v, b.v) <= 0) { | ||
a.s = undefined; | ||
b.s = a.c; | ||
a.c = b; | ||
return a; | ||
} | ||
b.s = undefined; | ||
a.s = b.c; | ||
b.c = a; | ||
return b; | ||
} | ||
|
||
function mergeSiblings<T>(compare: CompareFn<T>, n: PairHeapNode<T>): PairHeapNode<T> { | ||
if (!n.s) return n; | ||
const s = n.s; | ||
const ss = s.s; | ||
const m = merge(compare, n, s); | ||
return ss ? merge(compare, m, mergeSiblings(compare, ss)) : m; | ||
} | ||
|
||
export const heapMethods = { | ||
insert, | ||
merge, | ||
mergeSiblings, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { PairingHeap } from './PairingHeap'; | ||
|
||
describe('PairingHeap', () => { | ||
test('Basic add and remove', () => { | ||
const compare = new Intl.Collator().compare; | ||
const values = ['one', 'two', 'three', 'four', 'five', 'six', 'seven']; | ||
const sorted = values.concat().sort(compare); | ||
const heap = new PairingHeap(compare); | ||
values.forEach((v) => heap.add(v)); | ||
expect(heap.length).toBe(values.length); | ||
const result = [...heap]; | ||
expect(result).toEqual(sorted); | ||
expect(heap.length).toBe(0); | ||
}); | ||
|
||
interface Person { | ||
name: string; | ||
} | ||
|
||
test('FIFO for latest', () => { | ||
const compareStr = new Intl.Collator().compare; | ||
const compare = (a: Person, b: Person) => compareStr(a.name, b.name); | ||
const names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July']; | ||
const people: Person[] = names.map((name) => ({ name })); | ||
const sorted = people.concat().sort(compare); | ||
const heap = new PairingHeap(compare); | ||
|
||
heap.add(people[0]); | ||
expect(heap.dequeue()).toBe(people[0]); | ||
expect(heap.length).toBe(0); | ||
expect(heap.peek()).toBeUndefined(); | ||
expect(heap.dequeue()).toBeUndefined(); | ||
|
||
heap.concat(people); | ||
expect(heap.dequeue()).toBe(sorted[0]); | ||
expect(heap.dequeue()).toBe(sorted[1]); | ||
heap.concat(people); | ||
expect(heap.dequeue()).toBe(sorted[0]); | ||
expect(heap.dequeue()).toBe(sorted[1]); | ||
expect(heap.peek()).toBe(sorted[2]); | ||
expect(heap.dequeue()).toBe(sorted[2]); | ||
expect(heap.dequeue()).toBe(sorted[2]); | ||
expect(heap.peek()).toBe(sorted[3]); | ||
|
||
heap.add(sorted[0]); | ||
expect(heap.peek()).toBe(sorted[0]); | ||
const copy = { ...sorted[0] }; | ||
// Make sure we get back the open we added. | ||
expect(heap.add(copy).peek()).toBe(copy); | ||
}); | ||
}); |
Oops, something went wrong.