Skip to content

Commit 233f2a4

Browse files
authored
[SelectMany] add extensions. (#106)
* [SelectMany] WIP * [SelectMany] WIP 2 * [SelectMany] Add tests and benchmark.
1 parent b449db6 commit 233f2a4

File tree

11 files changed

+544
-1
lines changed

11 files changed

+544
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
## SelectManyOnArray
2+
3+
### Source
4+
[SelectManyOnArray.cs](../../src/StructLinq.Benchmark/SelectManyOnArray.cs)
5+
6+
### Results:
7+
``` ini
8+
9+
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19043
10+
Intel Core i7-8750H CPU 2.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
11+
.NET Core SDK=5.0.301
12+
[Host] : .NET Core 5.0.7 (CoreCLR 5.0.721.25508, CoreFX 5.0.721.25508), X64 RyuJIT
13+
DefaultJob : .NET Core 5.0.7 (CoreCLR 5.0.721.25508, CoreFX 5.0.721.25508), X64 RyuJIT
14+
15+
16+
```
17+
| Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
18+
|---------------------------------------- |-----------:|---------:|---------:|------:|-------:|------:|------:|----------:|
19+
| LINQ | 4,341.2 μs | 21.54 μs | 19.10 μs | 1.00 | - | - | - | 32064 B |
20+
| StructLINQ | 1,977.8 μs | 7.98 μs | 6.66 μs | 0.46 | 3.9063 | - | - | 32000 B |
21+
| StructLINQWhereReturnIsStructEnumerable | 879.2 μs | 2.63 μs | 2.46 μs | 0.20 | - | - | - | 32 B |
22+
| StructLINQWithFunction | 883.2 μs | 3.12 μs | 2.77 μs | 0.20 | - | - | - | - |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## SumOnSelectMany
2+
3+
### Source
4+
[SumOnSelectMany.cs](../../src/StructLinq.Benchmark/SumOnSelectMany.cs)
5+
6+
### Results:
7+
``` ini
8+
9+
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19043
10+
Intel Core i7-8750H CPU 2.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
11+
.NET Core SDK=5.0.301
12+
[Host] : .NET Core 5.0.7 (CoreCLR 5.0.721.25508, CoreFX 5.0.721.25508), X64 RyuJIT
13+
DefaultJob : .NET Core 5.0.7 (CoreCLR 5.0.721.25508, CoreFX 5.0.721.25508), X64 RyuJIT
14+
15+
16+
```
17+
| Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
18+
|---------------------------------------- |-----------:|---------:|---------:|------:|-------:|------:|------:|----------:|
19+
| LINQ | 4,219.9 μs | 34.79 μs | 29.05 μs | 1.00 | - | - | - | 32064 B |
20+
| StructLINQ | 1,862.5 μs | 6.40 μs | 5.99 μs | 0.44 | 5.8594 | - | - | 32032 B |
21+
| StructLINQWhereReturnIsStructEnumerable | 622.8 μs | 2.54 μs | 2.12 μs | 0.15 | - | - | - | 32 B |
22+
| StructLINQWithFunction | 712.0 μs | 3.99 μs | 3.73 μs | 0.17 | - | - | - | - |
23+
| StructLINQWithFunctionWithForeach | 817.3 μs | 7.96 μs | 7.44 μs | 0.19 | - | - | - | 1 B |

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ Following extensions are available for :
152152
- `Repeat`
153153
- `Reverse`([zero allocation](Documents/BenchmarksResults/Reverse.md))
154154
- `Select`
155+
- `SelectMany`
155156
- `Skip`
156157
- `SkipWhile`
157158
- `Sum`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System.Linq;
2+
using System.Runtime.CompilerServices;
3+
using BenchmarkDotNet.Attributes;
4+
using StructLinq.Array;
5+
6+
7+
namespace StructLinq.Benchmark
8+
{
9+
[MemoryDiagnoser]
10+
public class SelectManyOnArray
11+
{
12+
private int[][] array;
13+
private const int Count = 1000;
14+
15+
public SelectManyOnArray()
16+
{
17+
array = Enumerable.Range(0, Count)
18+
.Select(x => Enumerable.Range(0, x).ToArray())
19+
.ToArray();
20+
}
21+
22+
[Benchmark(Baseline = true)]
23+
public int LINQ()
24+
{
25+
var sum = 0;
26+
foreach (var i in array.SelectMany(x=> x))
27+
{
28+
sum += i;
29+
}
30+
31+
return sum;
32+
}
33+
34+
[Benchmark]
35+
public int StructLINQ()
36+
{
37+
var sum = 0;
38+
foreach (var i in array.ToStructEnumerable().SelectMany(x=> x))
39+
{
40+
sum += i;
41+
}
42+
43+
return sum;
44+
}
45+
46+
[Benchmark]
47+
public int StructLINQWhereReturnIsStructEnumerable()
48+
{
49+
var sum = 0;
50+
foreach (var i in array.ToStructEnumerable().SelectMany(x=> x.ToStructEnumerable(), _ => _, _ => _))
51+
{
52+
sum += i;
53+
}
54+
55+
return sum;
56+
}
57+
58+
59+
[Benchmark]
60+
public int StructLINQWithFunction()
61+
{
62+
var sum = 0;
63+
var func = new SelectManyFunction();
64+
foreach (var i in array.ToStructEnumerable().SelectMany(func, x=>x, x=> x, x=> x))
65+
{
66+
sum += i;
67+
}
68+
69+
return sum;
70+
}
71+
72+
internal struct SelectManyFunction : IFunction<int[], ArrayEnumerable<int>>
73+
{
74+
75+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
76+
public ArrayEnumerable<int> Eval(int[] element)
77+
{
78+
return element.ToStructEnumerable();
79+
}
80+
}
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.Linq;
2+
using System.Runtime.CompilerServices;
3+
using BenchmarkDotNet.Attributes;
4+
using StructLinq.Array;
5+
6+
namespace StructLinq.Benchmark
7+
{
8+
[MemoryDiagnoser]
9+
public class SumOnSelectMany
10+
{
11+
private int[][] array;
12+
private const int Count = 1000;
13+
14+
public SumOnSelectMany()
15+
{
16+
array = Enumerable.Range(0, Count)
17+
.Select(x => Enumerable.Range(0, x).ToArray())
18+
.ToArray();
19+
}
20+
21+
[Benchmark(Baseline = true)]
22+
public int LINQ()
23+
{
24+
return array.SelectMany(x => x).Sum();
25+
}
26+
27+
[Benchmark]
28+
public int StructLINQ()
29+
{
30+
return array.ToStructEnumerable().SelectMany(x => x).Sum();
31+
}
32+
33+
[Benchmark]
34+
public int StructLINQWhereReturnIsStructEnumerable()
35+
{
36+
return array.ToStructEnumerable().SelectMany(x => x.ToStructEnumerable(), _ => _, _ => _).Sum(x => x);
37+
}
38+
39+
40+
[Benchmark]
41+
public int StructLINQWithFunction()
42+
{
43+
var func = new SelectManyFunction();
44+
return array.ToStructEnumerable().SelectMany(func, x => x, x => x, x => x).Sum(x => x);
45+
}
46+
47+
[Benchmark]
48+
public int StructLINQWithFunctionWithForeach()
49+
{
50+
var sum = 0;
51+
var func = new SelectManyFunction();
52+
foreach (var i in array.ToStructEnumerable().SelectMany(func, x=>x, x=> x, x=> x))
53+
{
54+
sum += i;
55+
}
56+
57+
return sum;
58+
}
59+
60+
internal struct SelectManyFunction : IFunction<int[], ArrayEnumerable<int>>
61+
{
62+
63+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
64+
public ArrayEnumerable<int> Eval(int[] element)
65+
{
66+
return element.ToStructEnumerable();
67+
}
68+
}
69+
}
70+
}
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using StructLinq.Array;
5+
using StructLinq.IEnumerable;
6+
using StructLinq.SelectMany;
7+
using Xunit;
8+
9+
namespace StructLinq.Tests
10+
{
11+
public class SelectManyTests : AbstractEnumerableTests<int,
12+
SelectManyEnumerable<int[], IStructEnumerable<int[], ArrayStructEnumerator<int[]>>, ArrayStructEnumerator<int[]>, int, StructEnumerableFromIEnumerable<int>, GenericEnumerator<int>, FuncEnumerable<int[], int>>,
13+
SelectManyEnumerator<int[], ArrayStructEnumerator<int[]>, int, StructEnumerableFromIEnumerable<int>,
14+
GenericEnumerator<int>, FuncEnumerable<int[], int>>>
15+
{
16+
protected override SelectManyEnumerable<int[], IStructEnumerable<int[], ArrayStructEnumerator<int[]>>, ArrayStructEnumerator<int[]>, int, StructEnumerableFromIEnumerable<int>, GenericEnumerator<int>, FuncEnumerable<int[], int>> Build(int size)
17+
{
18+
var n = 3;
19+
var blockSize = size / n;
20+
var list = new List<int[]>();
21+
var currentBlockSize = blockSize;
22+
var currentGlobalSize = 0;
23+
for (int i = 0; i < n; i++)
24+
{
25+
if (currentBlockSize == 0)
26+
break;
27+
list.Add(Enumerable.Range(0, currentBlockSize).ToArray());
28+
currentGlobalSize += currentBlockSize;
29+
currentBlockSize = Math.Min(blockSize, size - currentGlobalSize);
30+
}
31+
32+
if (list.Count == 0)
33+
list.Add(Enumerable.Range(0, size).ToArray());
34+
else
35+
{
36+
if (currentGlobalSize != size)
37+
list.Add(Enumerable.Range(0, size - currentGlobalSize).ToArray());
38+
}
39+
40+
return list.ToArray().ToStructEnumerable().SelectMany(x => x);
41+
}
42+
43+
44+
[Theory]
45+
[InlineData(0, 0)]
46+
[InlineData(20, 3)]
47+
[InlineData(10, 2)]
48+
[InlineData(10, 11)]
49+
public void ShouldSameAsLinqToArray(int size, int blockSize)
50+
{
51+
var list = new List<int[]>();
52+
var currentBlockSize = blockSize;
53+
var currentGlobalSize = 0;
54+
var n = blockSize == 0 ? 0 : size / blockSize;
55+
for (int i = 0; i < n; i++)
56+
{
57+
if (currentBlockSize == 0)
58+
break;
59+
list.Add(Enumerable.Range(0, currentBlockSize).ToArray());
60+
currentGlobalSize += currentBlockSize;
61+
currentBlockSize = Math.Min(blockSize, size - currentGlobalSize);
62+
}
63+
64+
if (list.Count == 0)
65+
list.Add(Enumerable.Range(0, size).ToArray());
66+
67+
var arrayOfArray = list.ToArray();
68+
69+
var expected = arrayOfArray.SelectMany(x => x).ToArray();
70+
var values = arrayOfArray.ToStructEnumerable().SelectMany(x => x).ToArray();
71+
Assert.Equal(expected, values);
72+
}
73+
74+
[Theory]
75+
[InlineData(0, 0)]
76+
[InlineData(20, 3)]
77+
[InlineData(10, 2)]
78+
[InlineData(10, 11)]
79+
public void ShouldSameAsLinq(int size, int blockSize)
80+
{
81+
var list = new List<int[]>();
82+
var currentBlockSize = blockSize;
83+
var currentGlobalSize = 0;
84+
var n = blockSize == 0 ? 0 : size / blockSize;
85+
for (int i = 0; i < n; i++)
86+
{
87+
if (currentBlockSize == 0)
88+
break;
89+
list.Add(Enumerable.Range(0, currentBlockSize).ToArray());
90+
currentGlobalSize += currentBlockSize;
91+
currentBlockSize = Math.Min(blockSize, size - currentGlobalSize);
92+
}
93+
94+
if (list.Count == 0)
95+
list.Add(Enumerable.Range(0, size).ToArray());
96+
97+
var arrayOfArray = list.ToArray();
98+
var expected = arrayOfArray.SelectMany(x => x).ToArray();
99+
var listValues = new List<int>();
100+
foreach (var i in arrayOfArray.ToStructEnumerable().SelectMany(x => x))
101+
{
102+
listValues.Add(i);
103+
}
104+
var values = listValues.ToArray();
105+
Assert.Equal(expected, values);
106+
}
107+
108+
[Fact]
109+
public void ShouldIgnoreEmptyArray()
110+
{
111+
var list = new List<int[]>();
112+
list.Add(Enumerable.Range(0, 10).ToArray());
113+
list.Add(Enumerable.Range(0, 0).ToArray());
114+
list.Add(Enumerable.Range(-1, 10).ToArray());
115+
116+
var arrayOfArray = list.ToArray();
117+
var expected = arrayOfArray.SelectMany(x => x).ToArray();
118+
var listValues = new List<int>();
119+
foreach (var i in arrayOfArray.ToStructEnumerable().SelectMany(x => x))
120+
{
121+
listValues.Add(i);
122+
}
123+
var values = listValues.ToArray();
124+
Assert.Equal(expected, values);
125+
}
126+
127+
}
128+
}

src/StructLinq/IEnumerable/StructEnumerable.IEnumerable.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public static partial class StructEnumerable
1010
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1111
public static StructEnumerableFromIEnumerable<T> ToStructEnumerable<T>(this IEnumerable<T> enumerable)
1212
{
13-
return new StructEnumerableFromIEnumerable<T>(enumerable);
13+
return new(enumerable);
1414
}
1515
}
1616
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Runtime.CompilerServices;
4+
using StructLinq.IEnumerable;
5+
6+
namespace StructLinq.SelectMany
7+
{
8+
public readonly struct FuncEnumerable<TSource, TResult> : IFunction<TSource, StructEnumerableFromIEnumerable<TResult>>
9+
{
10+
private readonly Func<TSource, IEnumerable<TResult>> func;
11+
12+
public FuncEnumerable(Func<TSource, IEnumerable<TResult>> func)
13+
{
14+
this.func = func;
15+
}
16+
17+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
18+
public StructEnumerableFromIEnumerable<TResult> Eval(TSource element)
19+
{
20+
return func(element).ToStructEnumerable();
21+
}
22+
}
23+
24+
public readonly struct FuncEnumerable<TSource, TResult, TResultEnumerable, TResultEnumerator> : IFunction<TSource, TResultEnumerable>
25+
where TResultEnumerable : IStructEnumerable<TResult, TResultEnumerator>
26+
where TResultEnumerator : struct, IStructEnumerator<TResult>
27+
{
28+
private readonly Func<TSource, TResultEnumerable> func;
29+
30+
public FuncEnumerable(Func<TSource, TResultEnumerable> func)
31+
{
32+
this.func = func;
33+
}
34+
35+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
36+
public TResultEnumerable Eval(TSource element)
37+
{
38+
return func(element);
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)