Files
SpeedScope/src/lib/utils.ts
Jamie Wong a22ab9301f Add tests for redux store (#131)
This adds some baseline tests for the redux store, and also fixes some bugs I found along the way.

Bugs fixed:
- Changing selection after something has been selected within the sandwich view now works
- Table sort state is now preserved when switching between profiles

Fixes #124
2018-08-13 10:25:01 -07:00

170 lines
4.2 KiB
TypeScript

export function lastOf<T>(ts: T[]): T | null {
return ts[ts.length - 1] || null
}
export function sortBy<T>(ts: T[], key: (t: T) => number | string): void {
function comparator(a: T, b: T) {
return key(a) < key(b) ? -1 : 1
}
ts.sort(comparator)
}
export function getOrInsert<K, V>(map: Map<K, V>, k: K, fallback: (k: K) => V): V {
if (!map.has(k)) map.set(k, fallback(k))
return map.get(k)!
}
export function getOrElse<K, V>(map: Map<K, V>, k: K, fallback: (k: K) => V): V {
if (!map.has(k)) return fallback(k)
return map.get(k)!
}
export function getOrThrow<K, V>(map: Map<K, V>, k: K): V {
if (!map.has(k)) {
throw new Error(`Expected key ${k}`)
}
return map.get(k)!
}
// Intended to be used to de-duplicate objects based on a key property. This
// allows value comparisons to be done efficiently and for the returned objects
// to be used intuitively in Map objects.
//
// Example usage:
//
// export class Frame {
// private constructor(readonly file: string, readonly name: string) {}
// get key() { return `${this.file}:${this.name}` }
// static getOrInsert(set: KeyedSet<Frame>, file: string, name: string) {
// return set.getOrInsert(set, new Frame(file, name))
// }
// }
//
export interface HasKey {
readonly key: string | number
}
export class KeyedSet<T extends HasKey> implements Iterable<T> {
private map = new Map<string | number, T>()
getOrInsert(t: T): T {
const key = t.key
const existing = this.map.get(key)
if (existing) return existing
this.map.set(key, t)
return t
}
forEach(fn: (t: T) => void) {
this.map.forEach(fn)
}
[Symbol.iterator]() {
return this.map.values()
}
}
export function* itMap<T, U>(it: Iterable<T>, f: (t: T) => U): Iterable<U> {
for (let t of it) {
yield f(t)
}
}
export function itForEach<T>(it: Iterable<T>, f: (t: T) => void): void {
for (let t of it) {
f(t)
}
}
export function itReduce<T, U>(it: Iterable<T>, f: (a: U, b: T) => U, init: U): U {
let accum: U = init
for (let t of it) {
accum = f(accum, t)
}
return accum
}
export function zeroPad(s: string, width: number) {
return new Array(Math.max(width - s.length, 0) + 1).join('0') + s
}
export function formatPercent(percent: number) {
let formattedPercent = `${percent.toFixed(0)}%`
if (percent === 100) formattedPercent = '100%'
else if (percent > 99) formattedPercent = '>99%'
else if (percent < 0.01) formattedPercent = '<0.01%'
else if (percent < 1) formattedPercent = `${percent.toFixed(2)}%`
else if (percent < 10) formattedPercent = `${percent.toFixed(1)}%`
return formattedPercent
}
export function fract(x: number) {
return x - Math.floor(x)
}
export function triangle(x: number) {
return 2.0 * Math.abs(fract(x) - 0.5) - 1.0
}
export function binarySearch(
lo: number,
hi: number,
f: (val: number) => number,
target: number,
targetRangeSize = 1,
): [number, number] {
console.assert(!isNaN(targetRangeSize) && !isNaN(target))
while (true) {
if (hi - lo <= targetRangeSize) return [lo, hi]
const mid = (hi + lo) / 2
const val = f(mid)
if (val < target) lo = mid
else hi = mid
}
}
export function noop(...args: any[]) {}
export function objectsHaveShallowEquality<T extends object>(a: T, b: T): boolean {
for (let key in a) {
if (a[key] !== b[key]) return false
}
for (let key in b) {
if (a[key] !== b[key]) return false
}
return true
}
export function memoizeByShallowEquality<T extends object, U>(cb: (t: T) => U): (t: T) => U {
let last: {args: T; result: U} | null = null
return (args: T) => {
let result: U
if (last == null) {
result = cb(args)
last = {args, result}
return result
} else if (objectsHaveShallowEquality(last.args, args)) {
return last.result
} else {
last.args = args
last.result = cb(args)
return last.result
}
}
}
export function memoizeByReference<T, U>(cb: (t: T) => U): (t: T) => U {
let last: {args: T; result: U} | null = null
return (args: T) => {
let result: U
if (last == null) {
result = cb(args)
last = {args, result}
return result
} else if (last.args === args) {
return last.result
} else {
last.args = args
last.result = cb(args)
return last.result
}
}
}