Skip to content

Commit

Permalink
Merge pull request #63 from jokester/stressline
Browse files Browse the repository at this point in the history
stress: utils like lodash / underscore , but smaller
  • Loading branch information
jokester authored Apr 20, 2024
2 parents 97560f0 + 326cf45 commit 2725740
Show file tree
Hide file tree
Showing 22 changed files with 142 additions and 373 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@

---

Common TypeScript code I used in multiple app.
Simply and generic TypeScript utils.

![Check](https://github.com/jokester/ts-commonutil/workflows/Check/badge.svg)
[![codecov](https://codecov.io/gh/jokester/ts-commonutil/graph/badge.svg?token=95f53H027x)](https://codecov.io/gh/jokester/ts-commonutil)
[![npm version](https://badge.fury.io/js/%40jokester%2Fts-commonutil.svg)](https://badge.fury.io/js/%40jokester%2Fts-commonutil)

## How to Use

```
yarn add @jokester/ts-commonutil
install `@jokester/ts-commonutil` from NPM.

```
## Not included but my go-to libraries

- LRU: [lru-cache](https://www.npmjs.com/package/lru-cache)
- fp: [fp-ts](https://www.npmjs.com/package/fp-ts)
- React hooks
- [foxact](https://foxact.skk.moe/)

## Content

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
16 changes: 16 additions & 0 deletions src/collection/default-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,19 @@ export class DefaultMap<K, V> extends Map<K, V> {
return this.get(k)!;
}
}

export class WeakDefaultMap<K extends object, V> extends WeakMap<K, V> {
constructor(
private readonly createDefault: (k: K) => V,
entries?: readonly [K, V][],
) {
super(entries);
}

getOrCreate(k: K): V {
if (!this.has(k)) {
this.set(k, this.createDefault(k));
}
return this.get(k)!;
}
}
8 changes: 8 additions & 0 deletions src/collection/iterables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,11 @@ export const Iterables = {

type GeneralIterable<T> = Iterable<T> | AsyncIterable<T>;
type MaybePromise<T> = T | PromiseLike<T>;

export function toMap<T, K>(items: Iterable<T>, keyer: (t: T) => K): Map<K, T> {
const ret = new Map<K, T>();
for (const i of items) {
ret.set(keyer(i), i);
}
return ret;
}
11 changes: 0 additions & 11 deletions src/collection/maps.ts

This file was deleted.

20 changes: 10 additions & 10 deletions src/collection/min-heap.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { MinHeap } from './min-heap';
import { NumericOrder } from '../algebra/total-ordered';
import * as fpts from 'fp-ts';

describe('MinHeap', () => {
it('insert elements', () => {
const testee = new MinHeap(NumericOrder);
const testee = new MinHeap(fpts.number.Ord);
expect(testee.slice()).toEqual([]);

expect(testee.insert(5).slice()).toEqual([5]);
Expand All @@ -16,7 +16,7 @@ describe('MinHeap', () => {
});

it('removes element', () => {
const testee = new MinHeap(NumericOrder).insertMany(5, 2, 1, 6, 0);
const testee = new MinHeap(fpts.number.Ord).insertMany(5, 2, 1, 6, 0);

expect(testee.remove()).toEqual(0);
expect(testee.slice()).toEqual([1, 5, 2, 6]);
Expand All @@ -30,15 +30,15 @@ describe('MinHeap', () => {
});

it('removes element - 2', () => {
const testee = new MinHeap(NumericOrder).insert(0).insert(2).insert(3).insert(100).insert(200).insert(4);
const testee = new MinHeap(fpts.number.Ord).insert(0).insert(2).insert(3).insert(100).insert(200).insert(4);

expect(testee.slice()).toEqual([0, 2, 3, 100, 200, 4]);
expect(testee.remove()).toEqual(0);
expect(testee.slice()).toEqual([2, 4, 3, 100, 200]);
});

it('throws when remove from or peek an empty && strict heap', () => {
const testee = new MinHeap(NumericOrder, true);
const testee = new MinHeap(fpts.number.Ord, true);
expect(testee.slice()).toEqual([]);

expect(() => testee.remove()).toThrow(/nothing to remove/);
Expand All @@ -47,25 +47,25 @@ describe('MinHeap', () => {
});

it('can be initialized with initialTree', () => {
const testee = new MinHeap(NumericOrder, false, /* illegal tree */ [1, 0]);
const testee = new MinHeap(fpts.number.Ord, false, /* illegal tree */ [1, 0]);

expect(() => new MinHeap(NumericOrder, true, [1, 0])).toThrow(/assertInvariants/);
expect(() => new MinHeap(fpts.number.Ord, true, [1, 0])).toThrow(/assertInvariants/);
});

it('can be cloned', () => {
const testee = new MinHeap(NumericOrder, false, /* illegal tree */ [1, 0]);
const testee = new MinHeap(fpts.number.Ord, false, /* illegal tree */ [1, 0]);

expect(testee.clone().slice()).toEqual([1, 0]);
});

it('can be shrinked', () => {
const testee = new MinHeap(NumericOrder).insertMany(5, 4, 3, 2, 1, -1);
const testee = new MinHeap(fpts.number.Ord).insertMany(5, 4, 3, 2, 1, -1);

expect(testee.shrink(3).removeMany(3)).toEqual([-1, 1, 2]);
});

it('can shrink to a given upperlimit', () => {
const testee = new MinHeap(NumericOrder).insertMany(5, 4, 3, 2, 1, -1);
const testee = new MinHeap(fpts.number.Ord).insertMany(5, 4, 3, 2, 1, -1);

expect(testee.clone().shrinkUntil(4).removeMany(100)).toEqual([-1, 1, 2, 3]);

Expand Down
21 changes: 13 additions & 8 deletions src/collection/min-heap.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { TotalOrdered } from '../algebra/total-ordered';
import { positions } from './btree';
import { Ord } from 'fp-ts/Ord';

function isBefore<T>(ord: Ord<T>, a: T, b: T): boolean {
return ord.compare(a, b) < 0;
}

export class MinHeap<T> {
private readonly tree: T[] = [];
slice = this.tree.slice.bind(this.tree);

constructor(
private readonly order: TotalOrdered<T>,
private readonly order: Ord<T>,
private readonly strict = false,
initialTree?: T[],
) {
Expand All @@ -27,7 +31,8 @@ export class MinHeap<T> {
j = positions.parent(i);
this.tree[i] = value;

while (i && this.order.before(value /* i.e. this.tree[i] */, this.tree[j])) {
while (i && isBefore(this.order, value /* i.e. this.tree[i] */, this.tree[j])) {
// pop up new value
this.tree[i] = this.tree[j];
this.tree[j] = value;

Expand Down Expand Up @@ -61,20 +66,20 @@ export class MinHeap<T> {
*/
if (r < this.tree.length) {
// when it has 2 children
if (this.order.before(this.tree[l], this.tree[r]) && this.order.before(this.tree[l], v)) {
if (isBefore(this.order, this.tree[l], this.tree[r]) && isBefore(this.order, this.tree[l], v)) {
// swap [i] and [l] and continue
this.tree[i] = this.tree[l];
this.tree[l] = v;
i = l;
} else if (this.order.before(this.tree[r], this.tree[l]) && this.order.before(this.tree[r], v)) {
} else if (isBefore(this.order, this.tree[r], this.tree[l]) && isBefore(this.order, this.tree[r], v)) {
// swap [i] and [l] and continue
this.tree[i] = this.tree[r];
this.tree[r] = v;
i = r;
} else {
break; // v is already before all children
}
} else if (l < this.tree.length && this.order.before(this.tree[l], v)) {
} else if (l < this.tree.length && isBefore(this.order, this.tree[l], v)) {
this.tree[i] = this.tree[l];
this.tree[l] = v;
break; // there cannot be next level
Expand Down Expand Up @@ -119,7 +124,7 @@ export class MinHeap<T> {
const afterShrink: T[] = [];
while (
this.tree.length &&
(this.order.before(this.tree[0], v) || (inclusive && this.order.equal(this.tree[0], v)))
(isBefore(this.order, this.tree[0], v) || (inclusive && !this.order.compare(this.tree[0], v)))
) {
afterShrink.push(this.remove()!);
}
Expand All @@ -130,7 +135,7 @@ export class MinHeap<T> {
private assertInvariants() {
for (let i = 1; i < this.tree.length; i++) {
const p = positions.parent(i);
if (!this.order.before(this.tree[p], this.tree[i])) {
if (!isBefore(this.order, this.tree[p], this.tree[i])) {
throw new Error(`MinHeap#assertInvariants(): expected this.tree[${p} to be ordered before this.tree[${i}]`);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/collection/multiset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ describe(Multiset, () => {
testee.setCount('abc', 0);
testee.setCount('abc', 1);
testee.setCount('abc', 1);
testee.setCount('abc', 0, false);
expect(testee.maxCount()).toEqual(1);
testee.setCount('abc', 0);
expect(testee.maxCount()).toBe(1);
expect(testee.getCount('abc')).toEqual(0);
expect(testee.getCount('abd')).toEqual(1);
expect(testee.findByCount(0)).toEqual(['abc']);
Expand Down
4 changes: 2 additions & 2 deletions src/collection/multiset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export class Multiset<T> {
private map = new DefaultMap</* count */ number, /* objects */ Set<T>>((k) => new Set());
private countMap = new Map</* object*/ T, /* count */ number>();

setCount(obj: T, count: number, removeOnZeroFreq = true): void {
setCount(obj: T, count: number): void {
const existedCount = this.countMap.get(obj);

if (existedCount === count) {
Expand All @@ -16,7 +16,7 @@ export class Multiset<T> {

const existedSet = this.map.get(existedCount)!;
existedSet.delete(obj);
if (!existedSet.size && removeOnZeroFreq) {
if (!existedSet.size) {
this.map.delete(existedCount);
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/collection/segment-tree.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Monoid } from '../algebra/monoid';
import { Monoid } from 'fp-ts/lib/Monoid';
import { positions, powOf2 } from './btree';

/**
Expand Down Expand Up @@ -35,7 +35,7 @@ export class SegmentTree<T> {
const sums: T[] = (this.sums = []);

for (let i = 0; i < lenSums; i++) {
sums[i] = this.monoid.id;
sums[i] = this.monoid.empty;
}

// FIXME: set
Expand Down
2 changes: 1 addition & 1 deletion src/concurrency/resource-pool.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe(ResourcePool.name, () => {
testee.use(() => wait(0.5e3));
const becameEmpty = await testee.wait({ freeCount: 2 });
expect(becameEmpty).toBeTruthy();
expect(Date.now() - start).toBeGreaterThan(0.5e3);
expect(Date.now() - start).toBeGreaterThanOrEqual(0.5e3);
});

it('returns false when other tasks running', async () => {
Expand Down
23 changes: 23 additions & 0 deletions src/stress/chunk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function* chunk<T>(elements: Iterable<T>, chunkSize: number): Iterable<T[]> {
let currentChunk: T[] = [];
for (const e of elements) {
currentChunk.push(e);
if (currentChunk.length >= chunkSize) {
yield currentChunk;
currentChunk = [];
}
}
if (currentChunk.length) {
yield currentChunk;
}
}

export function* chunkArray<T>(elements: readonly T[], chunkSize: number): Iterable<T[]> {
for (let s = 0; ; s += chunkSize) {
const chunk = elements.slice(s, s + chunkSize);
if (!chunk.length) {
break;
}
yield chunk;
}
}
11 changes: 11 additions & 0 deletions src/stress/groupBy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { groupBy } from './groupBy';

describe(groupBy, () => {
it('groups value with provided keyer()', () => {
expect(groupBy([], () => 0)).toEqual({});
expect(groupBy(new Set([1, 2, 4]), (v) => v % 2)).toEqual({
1: [1],
0: [2, 4],
});
});
});
16 changes: 16 additions & 0 deletions src/stress/groupBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DefaultMap } from '../collection/default-map';

export function groupBy<T, K extends string | number | symbol>(
values: Iterable<T>,
keyer: (value: T) => K,
): Record<K, T[]> {
return Object.fromEntries(groupByAsMap(values, keyer)) as Record<K, T[]>;
}

export function groupByAsMap<T, K>(values: Iterable<T>, keyer: (value: T) => K): ReadonlyMap<K, T[]> {
const map = new DefaultMap<K, T[]>(() => []);
for (const v of values) {
map.getOrCreate(keyer(v)).push(v);
}
return map;
}
12 changes: 12 additions & 0 deletions src/stress/sortBy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { sortBy } from './sortBy';

describe('orderBy', () => {
it('sorts value DESC by "natural" JS ordering', () => {
expect(sortBy([2, 3, 1], (v) => v, false)).toEqual([3, 2, 1]);
expect(sortBy([1, 1, 2], (v) => v, false)).toEqual([2, 1, 1]);
});
it('sorts value ASC by "natural" JS ordering', () => {
expect(sortBy([2, 3, 1], (v) => v)).toEqual([1, 2, 3]);
expect(sortBy([1, 1, 2], (v) => v)).toEqual([1, 1, 2]);
});
});
18 changes: 18 additions & 0 deletions src/stress/sortBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* (for more complicated case, consider fp-ts Ord<T> typeclass)
* @param values
* @param key (if it should be cached, caller should use a cached impl)
* @param asc
*/
export function sortBy<T, O>(values: T[], key: (v: T) => O, asc = true): T[] {
const indexes = values.map((v, i) => ({ v, i }));
return indexes.sort((a, b) => (asc ? compare(a.v, b.v) : -compare(a.v, b.v))).map((_) => _.v);
}

function compare(a: any, b: any): number {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else return 0;
}
Loading

0 comments on commit 2725740

Please sign in to comment.