Skip to content

Commit 9599346

Browse files
authored
Add an Iterator.prototype.tally TypeScript goody (#376)
closes #109 inspired by Ruby's [tally](https://rubydoc.info/stdlib/core/Enumerable:tally) and Python's [Counter](https://docs.python.org/3/library/collections.html#collections.Counter)
1 parent 996bc2c commit 9599346

File tree

4 files changed

+244
-0
lines changed

4 files changed

+244
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { describe, expect, it } from "@jest/globals";
2+
3+
import "../Iterator.prototype.filter";
4+
import "../Iterator.prototype.map";
5+
6+
import "./index";
7+
8+
describe("Iterator.prototype.tally", () => {
9+
it("tallies an Array's values()", () => {
10+
const array = [1, 2, 4, -1, -2, 4, 1, 2, 1];
11+
expect(array.values().tally()).toStrictEqual(
12+
new Map([
13+
[1, 3],
14+
[2, 2],
15+
[4, 2],
16+
[-1, 1],
17+
[-2, 1],
18+
]),
19+
);
20+
});
21+
22+
it("tallies a Set's values()", () => {
23+
const set = new Set("hello");
24+
expect(set.values().tally()).toStrictEqual(
25+
new Map([
26+
["h", 1],
27+
["e", 1],
28+
["l", 1],
29+
["o", 1],
30+
]),
31+
);
32+
});
33+
34+
it("can tally a Generator object", () => {
35+
const factory = function* (): Generator<string, void, void> {
36+
yield "🍉";
37+
yield "🍇";
38+
yield "🍇";
39+
yield "🍉";
40+
yield "🍇";
41+
yield "🍉";
42+
yield "🍓";
43+
yield "🍓";
44+
};
45+
46+
expect(factory().tally()).toStrictEqual(
47+
new Map([
48+
["🍉", 3],
49+
["🍇", 3],
50+
["🍓", 2],
51+
]),
52+
);
53+
});
54+
55+
it.each([
56+
[].values(),
57+
new Set().values(),
58+
new Map().values(),
59+
(function* () {})(),
60+
])(
61+
"returns an empty tally when called on an empty iterator",
62+
<T>(iterator: IterableIterator<T>) => {
63+
expect(iterator.tally()).toStrictEqual(new Map());
64+
},
65+
);
66+
67+
it("can chain with other iterator methods", () => {
68+
const array = [4, 1, 2, 5, 3];
69+
expect(
70+
array
71+
.values()
72+
.map((x) => x ** 2)
73+
.filter((x) => x % 2 === 0)
74+
.tally(),
75+
).toStrictEqual(
76+
new Map([
77+
[16, 1],
78+
[4, 1],
79+
]),
80+
);
81+
});
82+
83+
it("can tally falsy values", () => {
84+
const array = [
85+
NaN,
86+
undefined,
87+
null,
88+
0n,
89+
false,
90+
false,
91+
false,
92+
null,
93+
"",
94+
"",
95+
undefined,
96+
NaN,
97+
undefined,
98+
0,
99+
0,
100+
];
101+
expect(array.values().tally()).toStrictEqual(
102+
new Map<unknown, number>([
103+
[NaN, 2],
104+
[undefined, 3],
105+
[null, 2],
106+
[0n, 1],
107+
[false, 3],
108+
["", 2],
109+
[0, 2],
110+
]),
111+
);
112+
});
113+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import "../Iterator.prototype.toIterable";
2+
import { iteratorPrototype } from "../Iterator.prototype";
3+
4+
declare global {
5+
interface Iterator<T> {
6+
tally(this: Iterator<T>): Map<T, number>;
7+
}
8+
}
9+
10+
iteratorPrototype.tally = function <T>(this: Iterator<T>): Map<T, number> {
11+
const tally = new Map<T, number>();
12+
13+
for (const element of this.toIterable()) {
14+
tally.set(element, (tally.get(element) ?? 0) + 1);
15+
}
16+
17+
return tally;
18+
};

workspaces/adventure-pack/src/app/__tests__/__snapshots__/equip-test.ts.snap

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1902,6 +1902,37 @@ iteratorPrototype.take ??= function (limit) {
19021902
/////////////////////////// END ADVENTURE PACK CODE ////////////////////////////"
19031903
`;
19041904

1905+
exports[`App can equip single goody: JavaScript Iterator.prototype.tally 1`] = `
1906+
"////////////////////////// BEGIN ADVENTURE PACK CODE ///////////////////////////
1907+
// Adventure Pack commit fake-commit-hash
1908+
// Running at: https://example.com/
1909+
1910+
Function.returnThis = function () {
1911+
return this;
1912+
};
1913+
1914+
const iteratorPrototype = Object.getPrototypeOf(
1915+
Object.getPrototypeOf([].values()),
1916+
);
1917+
1918+
iteratorPrototype.toIterable = function () {
1919+
this[Symbol.iterator] ??= Function.returnThis;
1920+
return this;
1921+
};
1922+
1923+
iteratorPrototype.tally = function () {
1924+
const tally = new Map();
1925+
1926+
for (const element of this.toIterable()) {
1927+
tally.set(element, (tally.get(element) ?? 0) + 1);
1928+
}
1929+
1930+
return tally;
1931+
};
1932+
1933+
/////////////////////////// END ADVENTURE PACK CODE ////////////////////////////"
1934+
`;
1935+
19051936
exports[`App can equip single goody: JavaScript Iterator.prototype.toArray 1`] = `
19061937
"////////////////////////// BEGIN ADVENTURE PACK CODE ///////////////////////////
19071938
// Adventure Pack commit fake-commit-hash
@@ -4018,6 +4049,52 @@ iteratorPrototype.take ??= function <T>(
40184049
/////////////////////////// END ADVENTURE PACK CODE ////////////////////////////"
40194050
`;
40204051

4052+
exports[`App can equip single goody: TypeScript Iterator.prototype.tally 1`] = `
4053+
"////////////////////////// BEGIN ADVENTURE PACK CODE ///////////////////////////
4054+
// Adventure Pack commit fake-commit-hash
4055+
// Running at: https://example.com/
4056+
4057+
declare global {
4058+
interface FunctionConstructor {
4059+
returnThis<T>(this: T): T;
4060+
}
4061+
4062+
interface Iterator<T> {
4063+
tally(this: Iterator<T>): Map<T, number>;
4064+
4065+
toIterable(this: Iterator<T>): IterableIterator<T>;
4066+
}
4067+
}
4068+
4069+
Function.returnThis = function <T>(this: T): T {
4070+
return this;
4071+
};
4072+
4073+
const iteratorPrototype = Object.getPrototypeOf(
4074+
Object.getPrototypeOf([].values()),
4075+
) as Iterator<unknown, unknown, unknown>;
4076+
4077+
iteratorPrototype.toIterable = function <T>(
4078+
this: Iterator<T>,
4079+
): IterableIterator<T> {
4080+
(this as unknown as Record<symbol, unknown>)[Symbol.iterator] ??=
4081+
Function.returnThis;
4082+
return this as unknown as IterableIterator<T>;
4083+
};
4084+
4085+
iteratorPrototype.tally = function <T>(this: Iterator<T>): Map<T, number> {
4086+
const tally = new Map<T, number>();
4087+
4088+
for (const element of this.toIterable()) {
4089+
tally.set(element, (tally.get(element) ?? 0) + 1);
4090+
}
4091+
4092+
return tally;
4093+
};
4094+
4095+
/////////////////////////// END ADVENTURE PACK CODE ////////////////////////////"
4096+
`;
4097+
40214098
exports[`App can equip single goody: TypeScript Iterator.prototype.toArray 1`] = `
40224099
"////////////////////////// BEGIN ADVENTURE PACK CODE ///////////////////////////
40234100
// Adventure Pack commit fake-commit-hash

workspaces/adventure-pack/src/app/__tests__/__snapshots__/render-test.ts.snap

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,21 @@ iteratorPrototype.take ??= function (limit) {
10641064
};"
10651065
`;
10661066
1067+
exports[`App can render goody: JavaScript Iterator.prototype.tally 1`] = `
1068+
"import "Iterator.prototype";
1069+
import "Iterator.prototype.toIterable";
1070+
1071+
iteratorPrototype.tally = function () {
1072+
const tally = new Map();
1073+
1074+
for (const element of this.toIterable()) {
1075+
tally.set(element, (tally.get(element) ?? 0) + 1);
1076+
}
1077+
1078+
return tally;
1079+
};"
1080+
`;
1081+
10671082
exports[`App can render goody: JavaScript Iterator.prototype.toArray 1`] = `
10681083
"import "Iterator.prototype";
10691084
import "Iterator.prototype.toIterable";
@@ -2305,6 +2320,27 @@ iteratorPrototype.take ??= function <T>(
23052320
};"
23062321
`;
23072322
2323+
exports[`App can render goody: TypeScript Iterator.prototype.tally 1`] = `
2324+
"import "Iterator.prototype";
2325+
import "Iterator.prototype.toIterable";
2326+
2327+
declare global {
2328+
interface Iterator<T> {
2329+
tally(this: Iterator<T>): Map<T, number>;
2330+
}
2331+
}
2332+
2333+
iteratorPrototype.tally = function <T>(this: Iterator<T>): Map<T, number> {
2334+
const tally = new Map<T, number>();
2335+
2336+
for (const element of this.toIterable()) {
2337+
tally.set(element, (tally.get(element) ?? 0) + 1);
2338+
}
2339+
2340+
return tally;
2341+
};"
2342+
`;
2343+
23082344
exports[`App can render goody: TypeScript Iterator.prototype.toArray 1`] = `
23092345
"import "Iterator.prototype";
23102346
import "Iterator.prototype.toIterable";

0 commit comments

Comments
 (0)