Skip to content

Commit dabda67

Browse files
committed
Add chunk
1 parent 7bc3812 commit dabda67

File tree

11 files changed

+261
-0
lines changed

11 files changed

+261
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ To use library with ES6 modules make sure that you specify `"type": "module"` in
115115
| all | Yes | [Sync](tests/unittests/tests/All.ts), [Async](tests/unittests/tests/AllAsync.ts)
116116
| any | Yes | [Sync](tests/unittests/tests/Any.ts), [Async](tests/unittests/tests/AnyAsync.ts)
117117
| average | Yes | [Sync](tests/unittests/tests/Average.ts), [Async](tests/unittests/tests/AverageAsync.ts)
118+
| chunk | No | [Sync](tests/unittests/tests/Chunk.ts)
118119
| concatenate | No | [Sync](tests/unittests/tests/Concatenate.ts) | Equivalent to `.Concat` but renamed to avoid conflict with JS
119120
| contains | Yes | [Sync](tests/unittests/tests/Contains.ts), [Async](tests/unittests/tests/ContainsAsync.ts)
120121
| count | Yes | [Sync](tests/unittests/tests/Count.ts), [Async](tests/unittests/tests/CountAsync.ts)

src/async/_private/chunk.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { BasicAsyncEnumerable } from "../BasicAsyncEnumerable"
2+
import { ArgumentOutOfRangeException } from "../../shared"
3+
import type { IAsyncEnumerable } from "../../types"
4+
5+
export const chunk = <TSource>(source: AsyncIterable<TSource>, size: number): IAsyncEnumerable<TSource[]> => {
6+
if (size < 1) {
7+
throw new ArgumentOutOfRangeException("index")
8+
}
9+
10+
async function* iterator() {
11+
let yieldChunk = []
12+
for await (const value of source) {
13+
yieldChunk.push(value)
14+
15+
if (yieldChunk.length === size) {
16+
yield yieldChunk
17+
yieldChunk = []
18+
}
19+
}
20+
21+
if (yieldChunk.length) {
22+
yield yieldChunk
23+
}
24+
}
25+
26+
return new BasicAsyncEnumerable(iterator)
27+
}

src/initializer/bindLinq.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { asAsync } from "./../sync/_private/asAsync"
99
import { asParallel } from "./../sync/_private/asParallel"
1010
import { average } from "./../sync/_private/average"
1111
import { averageAsync } from "./../sync/_private/averageAsync"
12+
import { chunk } from "./../sync/_private/chunk"
1213
import { concatenate } from "../sync/_private/concatenate"
1314
import { contains } from "./../sync/_private/contains"
1415
import { containsAsync } from "./../sync/_private/containsAsync"
@@ -111,6 +112,7 @@ export const bindLinq = <T, Y extends Iterable<T>>(object: IPrototype<Y>) => {
111112
bind(asParallel, "asParallel")
112113
bind(average, "average")
113114
bind(averageAsync, "averageAsync")
115+
bind(chunk, "chunk")
114116
bind(concatenate, "concatenate")
115117
bind(contains, "contains")
116118
bind(containsAsync, "containsAsync")

src/initializer/bindLinqAsync.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { anyAsync } from "./../async/_private/anyAsync"
88
import { asParallel } from "./../async/_private/asParallel"
99
import { average } from "./../async/_private/average"
1010
import { averageAsync } from "./../async/_private/averageAsync"
11+
import { chunk } from "./../async/_private/chunk"
1112
import { concatenate } from "../async/_private/concatenate"
1213
import { contains } from "./../async/_private/contains"
1314
import { containsAsync } from "./../async/_private/containsAsync"
@@ -106,6 +107,7 @@ export const bindLinqAsync = <T, Y extends AsyncIterable<T>>(object: IPrototype<
106107
bind(asParallel, "asParallel")
107108
bind(average, "average")
108109
bind(averageAsync, "averageAsync")
110+
bind(chunk, "chunk")
109111
bind(concatenate, "concatenate")
110112
bind(contains, "contains")
111113
bind(containsAsync, "containsAsync")

src/initializer/bindLinqParallel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { anyAsync } from "./../parallel/_private/anyAsync"
88
import { asAsync } from "./../parallel/_private/asAsync"
99
import { average } from "./../parallel/_private/average"
1010
import { averageAsync } from "./../parallel/_private/averageAsync"
11+
import { chunk } from "./../parallel/_private/chunk"
1112
import { concatenate } from "../parallel/_private/concatenate"
1213
import { contains } from "./../parallel/_private/contains"
1314
import { containsAsync } from "./../parallel/_private/containsAsync"
@@ -105,6 +106,7 @@ export const bindLinqParallel = <T, Y extends AsyncIterable<T>>(object: IPrototy
105106
bind(asAsync, "asAsync")
106107
bind(average, "average")
107108
bind(averageAsync, "averageAsync")
109+
bind(chunk, "chunk")
108110
bind(concatenate, "concatenate")
109111
bind(contains, "contains")
110112
bind(containsAsync, "containsAsync")

src/parallel/_private/chunk.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { BasicParallelEnumerable } from "../BasicParallelEnumerable"
2+
import { ArgumentOutOfRangeException } from "../../shared"
3+
import { IParallelEnumerable, ParallelGeneratorType, TypedData } from "../../types"
4+
5+
export const chunk = <TSource>(source: IParallelEnumerable<TSource>, size: number): IParallelEnumerable<TSource[]> => {
6+
if (size < 1) {
7+
throw new ArgumentOutOfRangeException("index")
8+
}
9+
10+
let dataFunc: TypedData<TSource[]>
11+
12+
switch (source.dataFunc.type) {
13+
case ParallelGeneratorType.ArrayOfPromises:
14+
const arrayOfPromises = source.dataFunc.generator
15+
dataFunc = {
16+
type: ParallelGeneratorType.ArrayOfPromises,
17+
generator: () => {
18+
const chunks: Promise<TSource[]>[] = []
19+
let yieldChunk = []
20+
for (const promise of arrayOfPromises()) {
21+
yieldChunk.push(promise)
22+
23+
if (yieldChunk.length === size) {
24+
chunks.push(Promise.all(yieldChunk))
25+
yieldChunk = []
26+
}
27+
}
28+
29+
if (yieldChunk.length) {
30+
chunks.push(Promise.all(yieldChunk))
31+
}
32+
33+
return chunks
34+
}
35+
}
36+
break
37+
case ParallelGeneratorType.PromiseOfPromises:
38+
const promiseOfPromises = source.dataFunc.generator
39+
dataFunc = {
40+
type: ParallelGeneratorType.PromiseOfPromises,
41+
generator: async () => {
42+
const chunks: Promise<TSource[]>[] = []
43+
let yieldChunk = []
44+
for (const promise of await promiseOfPromises()) {
45+
yieldChunk.push(promise)
46+
47+
if (yieldChunk.length === size) {
48+
chunks.push(Promise.all(yieldChunk))
49+
yieldChunk = []
50+
}
51+
}
52+
53+
if (yieldChunk.length) {
54+
chunks.push(Promise.all(yieldChunk))
55+
}
56+
57+
return chunks
58+
}
59+
}
60+
break
61+
case ParallelGeneratorType.PromiseToArray:
62+
const promiseToArray = source.dataFunc.generator
63+
dataFunc = {
64+
type: ParallelGeneratorType.PromiseToArray,
65+
generator: async () => {
66+
const chunks: TSource[][] = []
67+
let yieldChunk = []
68+
for (const value of await promiseToArray()) {
69+
yieldChunk.push(value)
70+
71+
if (yieldChunk.length === size) {
72+
chunks.push(yieldChunk)
73+
yieldChunk = []
74+
}
75+
}
76+
77+
if (yieldChunk.length) {
78+
chunks.push(yieldChunk)
79+
}
80+
81+
return chunks
82+
}
83+
}
84+
break
85+
}
86+
87+
return new BasicParallelEnumerable(dataFunc)
88+
}

src/sync/_private/chunk.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { BasicEnumerable } from "../BasicEnumerable"
2+
import { ArgumentOutOfRangeException } from "../../shared"
3+
import type { IEnumerable } from "../../types"
4+
5+
export const chunk = <TSource>(source: Iterable<TSource>, size: number): IEnumerable<TSource[]> => {
6+
if (size < 1) {
7+
throw new ArgumentOutOfRangeException("index")
8+
}
9+
10+
function* iterator() {
11+
let yieldChunk = []
12+
for (const value of source) {
13+
yieldChunk.push(value)
14+
15+
if (yieldChunk.length === size) {
16+
yield yieldChunk
17+
yieldChunk = []
18+
}
19+
}
20+
21+
if (yieldChunk.length) {
22+
yield yieldChunk
23+
}
24+
}
25+
26+
return new BasicEnumerable(iterator)
27+
}

src/types/IAsyncEnumerable.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export interface IAsyncEnumerable<TSource> extends IAsyncParallel<TSource> {
1818
* @returns Parallel Enumerable of source
1919
*/
2020
asParallel(): IParallelEnumerable<TSource>
21+
/**
22+
* Splits the elements of a sequence into chunks of size at most size.
23+
* @param size The maximum size of each chunk.
24+
* @returns An IAsyncEnumerable<T> that contains the elements the input sequence split into chunks of size size.
25+
*/
26+
chunk(size: number): IAsyncEnumerable<TSource[]>
2127
/**
2228
* Concatenates two async sequences.
2329
* @param second The sequence to concatenate to the first sequence.

src/types/IEnumerable.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ export interface IEnumerable<TSource> extends Iterable<TSource> {
103103
* @returns The average of the sequence of values.
104104
*/
105105
averageAsync(selector: (x: TSource) => Promise<number>): Promise<number>
106+
/**
107+
* Splits the elements of a sequence into chunks of size at most size.
108+
* @param size The maximum size of each chunk.
109+
* @returns An IEnumerable<T> that contains the elements the input sequence split into chunks of size size.
110+
*/
111+
chunk(size: number): IEnumerable<TSource[]>
106112
/**
107113
* Concatenates two sequences.
108114
* @param second The sequence to concatenate to the first sequence.

src/types/IParallelEnumerable.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ export interface IParallelEnumerable<TSource> extends IAsyncParallel<TSource> {
2525
* @returns An IAsyncEnumerable<T>
2626
*/
2727
asAsync(): IAsyncEnumerable<TSource>
28+
/**
29+
* Splits the elements of a sequence into chunks of size at most size.
30+
* @param size The maximum size of each chunk.
31+
* @returns An IParallelEnumerable<T> that contains the elements the input sequence split into chunks of size size.
32+
*/
33+
chunk(size: number): IParallelEnumerable<TSource[]>
2834
/**
2935
* Concatenates two async sequences.
3036
* @param second The async sequence to concatenate to the first sequence.

tests/unittests/tests/Chunk.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { ArgumentOutOfRangeException } from "linq-to-typescript"
2+
import { asAsync, itAsync, itEnumerable, itParallel } from "../TestHelpers"
3+
4+
describe("chunk", () => {
5+
itEnumerable("Empty", (asEnumerable) => {
6+
const values = asEnumerable([]).chunk(5).toArray()
7+
expect(values).toEqual([])
8+
})
9+
10+
itAsync("Empty", async () => {
11+
const values = await asAsync([]).chunk(5).toArray()
12+
expect(values).toEqual([])
13+
})
14+
15+
itParallel("Empty", async (asParallel) => {
16+
const values = await asParallel([]).chunk(5).toArray()
17+
expect(values).toEqual([])
18+
})
19+
20+
itEnumerable("Size 0", (asEnumerable) => {
21+
const values = [1, 2, 3, 4, 5]
22+
expect(() => asEnumerable(values).chunk(0)).toThrow(ArgumentOutOfRangeException)
23+
})
24+
25+
itAsync("Size 0", async () => {
26+
const values = [1, 2, 3, 4, 5]
27+
expect(() => asAsync(values).chunk(0)).toThrow(ArgumentOutOfRangeException)
28+
})
29+
30+
itParallel("Size 0", async (asParallel) => {
31+
const values = [1, 2, 3, 4, 5]
32+
expect(() => asParallel(values).chunk(0)).toThrow(ArgumentOutOfRangeException)
33+
})
34+
35+
itEnumerable("Size 1", (asEnumerable) => {
36+
const values = [1, 2, 3, 4, 5]
37+
const newValues = asEnumerable(values).chunk(1).toArray()
38+
expect(newValues.length).toBe(values.length)
39+
expect(newValues).toEqual([ [ 1 ], [ 2 ], [ 3 ], [ 4 ], [ 5 ] ])
40+
})
41+
42+
itAsync("Size 1", async () => {
43+
const values = [1, 2, 3, 4, 5]
44+
const newValues = await asAsync(values).chunk(1).toArray()
45+
expect(newValues).toEqual([ [ 1 ], [ 2 ], [ 3 ], [ 4 ], [ 5 ] ])
46+
})
47+
48+
itParallel("Size 1", async (asParallel) => {
49+
const values = [1, 2, 3, 4, 5]
50+
const newValues = await asParallel(values).chunk(1).toArray()
51+
expect(newValues).toEqual([ [ 1 ], [ 2 ], [ 3 ], [ 4 ], [ 5 ] ])
52+
})
53+
54+
itEnumerable("Size 2 Odd", (asEnumerable) => {
55+
const values = [1, 2, 3, 4, 5]
56+
const newValues = asEnumerable(values).chunk(2).toArray()
57+
58+
expect(newValues).toEqual([ [1, 2], [3, 4], [5] ])
59+
})
60+
61+
itAsync("Size 2 Odd", async () => {
62+
const values = [1, 2, 3, 4, 5]
63+
const newValues = await asAsync(values).chunk(2).toArray()
64+
expect(newValues).toEqual([ [1, 2], [3, 4], [5] ])
65+
})
66+
67+
itParallel("Size 2 Odd", async (asParallel) => {
68+
const values = [1, 2, 3, 4, 5]
69+
const newValues = await asParallel(values).chunk(2).toArray()
70+
expect(newValues).toEqual([ [1, 2], [3, 4], [5] ])
71+
})
72+
73+
itEnumerable("Size 2 Even", (asEnumerable) => {
74+
const values = [1, 2, 3, 4, 5, 6]
75+
const newValues = asEnumerable(values).chunk(2).toArray()
76+
77+
expect(newValues).toEqual([ [1, 2], [3, 4], [5, 6] ])
78+
79+
})
80+
81+
itAsync("Size 2 Even", async () => {
82+
const values = [1, 2, 3, 4, 5, 6]
83+
const newValues = await asAsync(values).chunk(2).toArray()
84+
85+
expect(newValues).toEqual([ [1, 2], [3, 4], [5, 6] ])
86+
})
87+
88+
itParallel("Size 2 Even", async (asParallel) => {
89+
const values = [1, 2, 3, 4, 5, 6]
90+
const newValues = await asParallel(values).chunk(2).toArray()
91+
92+
expect(newValues).toEqual([ [1, 2], [3, 4], [5, 6] ])
93+
})
94+
})

0 commit comments

Comments
 (0)