Skip to content

Commit d7a5b46

Browse files
committed
[Last] add naive extensions implementation.
1 parent 9fbcc85 commit d7a5b46

File tree

8 files changed

+593
-0
lines changed

8 files changed

+593
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
## LastOnArray
2+
3+
### Source
4+
[LastOnArray.cs](../../src/StructLinq.Benchmark/LastOnArray.cs)
5+
6+
### Results:
7+
``` ini
8+
9+
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.19041
10+
Intel Core i7-8750H CPU 2.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
11+
.NET Core SDK=5.0.100-preview.7.20366.6
12+
[Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
13+
DefaultJob : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
14+
15+
16+
```
17+
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
18+
|-------------------- |------------:|---------:|---------:|------:|--------:|-------:|------:|------:|----------:|
19+
| Linq | 28.54 ns | 0.331 ns | 0.310 ns | 1.00 | 0.00 | - | - | - | - |
20+
| EnumerableLinq | 28.71 ns | 0.352 ns | 0.329 ns | 1.01 | 0.02 | - | - | - | - |
21+
| StructLinq | 1,303.06 ns | 7.074 ns | 5.907 ns | 45.62 | 0.54 | 0.0057 | - | - | 32 B |
22+
| StructLinqZeroAlloc | 1,245.69 ns | 6.048 ns | 5.361 ns | 43.61 | 0.45 | - | - | - | - |
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using BenchmarkDotNet.Attributes;
4+
5+
namespace StructLinq.Benchmark
6+
{
7+
[MemoryDiagnoser]
8+
public class LastOnArray
9+
{
10+
private const int Count = 1000;
11+
private readonly int[] array;
12+
private readonly IEnumerable<int> enumerable;
13+
public LastOnArray()
14+
{
15+
array = Enumerable.ToArray(Enumerable.Range(0, Count));
16+
enumerable = Enumerable.ToArray(Enumerable.Range(0, Count));
17+
}
18+
19+
[Benchmark(Baseline = true)]
20+
public int Linq() => array.Last();
21+
22+
[Benchmark]
23+
public int EnumerableLinq() => enumerable.Last();
24+
25+
26+
[Benchmark]
27+
public int StructLinq() => array.ToStructEnumerable().Last();
28+
29+
[Benchmark]
30+
public int StructLinqZeroAlloc() => array.ToStructEnumerable().Last( x => x);
31+
}
32+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Linq;
2+
using FluentAssertions;
3+
using Xunit;
4+
5+
namespace StructLinq.Tests
6+
{
7+
public class LastOrDefaultTests
8+
{
9+
[Fact]
10+
public void ShouldReturnLastElement()
11+
{
12+
var array = Enumerable.Range(0, 10)
13+
.ToArray()
14+
.ToStructEnumerable()
15+
.LastOrDefault()
16+
.Should()
17+
.Be(9);
18+
}
19+
20+
[Fact]
21+
public void ShouldReturnLastElementZeroAlloc()
22+
{
23+
var array = Enumerable.Range(0, 10)
24+
.ToArray()
25+
.ToStructEnumerable()
26+
.LastOrDefault(x => x)
27+
.Should()
28+
.Be(9);
29+
}
30+
31+
[Fact]
32+
public void ShouldReturnDefault()
33+
{
34+
StructEnumerable.Empty<int>().LastOrDefault().Should().Be(default);
35+
}
36+
37+
[Fact]
38+
public void ShouldReturnDefaultZeroAlloc()
39+
{
40+
StructEnumerable.Empty<int>().LastOrDefault(x => x).Should().Be(default);
41+
}
42+
43+
44+
[Fact]
45+
public void ShouldReturnLastElementWithFunc()
46+
{
47+
var array = Enumerable.Range(0, 10)
48+
.ToArray()
49+
.ToStructEnumerable()
50+
.LastOrDefault(x => x > 5)
51+
.Should()
52+
.Be(9);
53+
}
54+
55+
[Fact]
56+
public void ShouldReturnLastElementWithFuncZeroAlloc()
57+
{
58+
var array = Enumerable.Range(0, 10)
59+
.ToArray()
60+
.ToStructEnumerable()
61+
.LastOrDefault(x => x > 5, x => x)
62+
.Should()
63+
.Be(9);
64+
}
65+
}
66+
}

src/StructLinq.Tests/LastTests.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using System.Linq;
3+
using FluentAssertions;
4+
using Xunit;
5+
6+
namespace StructLinq.Tests
7+
{
8+
public class LastTests
9+
{
10+
[Fact]
11+
public void ShouldReturnLastElement()
12+
{
13+
var array = Enumerable.Range(0, 10)
14+
.ToArray()
15+
.ToStructEnumerable()
16+
.Last()
17+
.Should()
18+
.Be(9);
19+
}
20+
21+
[Fact]
22+
public void ShouldReturnLastElementZeroAlloc()
23+
{
24+
var array = Enumerable.Range(0, 10)
25+
.ToArray()
26+
.ToStructEnumerable()
27+
.Last(x => x)
28+
.Should()
29+
.Be(9);
30+
}
31+
32+
[Fact]
33+
public void ShouldThrowException()
34+
{
35+
Assert.Throws<Exception>(() => StructEnumerable.Empty<int>().Last());
36+
}
37+
38+
[Fact]
39+
public void ShouldThrowExceptionZeroAlloc()
40+
{
41+
Assert.Throws<Exception>(() => StructEnumerable.Empty<int>().Last(x => x));
42+
}
43+
44+
45+
[Fact]
46+
public void ShouldReturnLastElementWithFunc()
47+
{
48+
var array = Enumerable.Range(0, 10)
49+
.ToArray()
50+
.ToStructEnumerable()
51+
.Last(x => x > 5)
52+
.Should()
53+
.Be(9);
54+
}
55+
56+
[Fact]
57+
public void ShouldReturnLastElementWithFuncZeroAlloc()
58+
{
59+
var array = Enumerable.Range(0, 10)
60+
.ToArray()
61+
.ToStructEnumerable()
62+
.Last(x => x > 5, x => x)
63+
.Should()
64+
.Be(9);
65+
}
66+
}
67+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using System.Linq;
3+
using FluentAssertions;
4+
using Xunit;
5+
6+
namespace StructLinq.Tests
7+
{
8+
public class RefLastTests
9+
{
10+
[Fact]
11+
public void ShouldReturnLastElement()
12+
{
13+
var array = Enumerable.Range(0, 10)
14+
.ToArray()
15+
.ToRefStructEnumerable()
16+
.Last()
17+
.Should()
18+
.Be(9);
19+
}
20+
21+
[Fact]
22+
public void ShouldReturnLastElementZeroAlloc()
23+
{
24+
var array = Enumerable.Range(0, 10)
25+
.ToArray()
26+
.ToRefStructEnumerable()
27+
.Last(x => x)
28+
.Should()
29+
.Be(9);
30+
}
31+
32+
[Fact]
33+
public void ShouldThrowException()
34+
{
35+
Assert.Throws<Exception>(() => StructEnumerable.Empty<int>().ToArray().ToRefStructEnumerable().Last());
36+
}
37+
38+
[Fact]
39+
public void ShouldThrowExceptionZeroAlloc()
40+
{
41+
Assert.Throws<Exception>(() => StructEnumerable.Empty<int>().ToArray().ToRefStructEnumerable().Last(x => x));
42+
}
43+
44+
45+
[Fact]
46+
public void ShouldReturnLastElementWithFunc()
47+
{
48+
var array = Enumerable.Range(0, 10)
49+
.ToArray()
50+
.ToRefStructEnumerable()
51+
.Last(x => x > 5)
52+
.Should()
53+
.Be(9);
54+
}
55+
56+
[Fact]
57+
public void ShouldReturnLastElementWithFuncZeroAlloc()
58+
{
59+
var array = Enumerable.Range(0, 10)
60+
.ToArray()
61+
.ToRefStructEnumerable()
62+
.Last(x => x > 5, x => x)
63+
.Should()
64+
.Be(9);
65+
}
66+
}
67+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// ReSharper disable once CheckNamespace
2+
3+
using System;
4+
using System.Buffers;
5+
using System.Runtime.CompilerServices;
6+
7+
// ReSharper disable once CheckNamespace
8+
namespace StructLinq
9+
{
10+
public static partial class StructEnumerable
11+
{
12+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
13+
private static ref T RefInnerLast<T, TEnumerator>(TEnumerator enumerator)
14+
where TEnumerator : struct, IRefStructEnumerator<T>
15+
{
16+
if (enumerator.MoveNext())
17+
{
18+
var rent = ArrayPool<T>.Shared.Rent(1);
19+
do
20+
{
21+
ref var elt = ref rent[0];
22+
ref var current = ref enumerator.Current;
23+
elt = current;
24+
}
25+
while (enumerator.MoveNext());
26+
enumerator.Dispose();
27+
ref var result = ref rent[0];
28+
ArrayPool<T>.Shared.Return(rent);
29+
return ref result;
30+
}
31+
enumerator.Dispose();
32+
throw new Exception("No Elements");
33+
}
34+
35+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
36+
private static ref T RefInnerLast<T, TEnumerator>(TEnumerator enumerator, Func<T, bool> predicate)
37+
where TEnumerator : struct, IRefStructEnumerator<T>
38+
{
39+
var rent = ArrayPool<T>.Shared.Rent(1);
40+
while (enumerator.MoveNext())
41+
{
42+
ref var current = ref enumerator.Current;
43+
if (predicate(current))
44+
{
45+
ref var elt = ref rent[0];
46+
elt = current;
47+
}
48+
}
49+
enumerator.Dispose();
50+
ref var result = ref rent[0];
51+
ArrayPool<T>.Shared.Return(rent);
52+
return ref result;
53+
}
54+
55+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
56+
private static ref T RefInnerLast<T, TEnumerator, TFunc>(TEnumerator enumerator, ref TFunc predicate)
57+
where TEnumerator : struct, IRefStructEnumerator<T>
58+
where TFunc : struct, IInFunction<T, bool>
59+
{
60+
while (enumerator.MoveNext())
61+
{
62+
ref var current = ref enumerator.Current;
63+
if (predicate.Eval(in current))
64+
{
65+
enumerator.Dispose();
66+
return ref current;
67+
}
68+
}
69+
enumerator.Dispose();
70+
throw new Exception("No Match");
71+
}
72+
73+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
74+
public static ref T Last<T, TEnumerable, TEnumerator>(this TEnumerable enumerable, Func<TEnumerable, IRefStructEnumerable<T, TEnumerator>> _)
75+
where TEnumerator : struct, IRefStructEnumerator<T>
76+
where TEnumerable : IRefStructEnumerable<T, TEnumerator>
77+
{
78+
var enumerator = enumerable.GetEnumerator();
79+
return ref RefInnerLast<T, TEnumerator>(enumerator);
80+
}
81+
82+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
83+
public static ref T Last<T, TEnumerator>(this IRefStructEnumerable<T, TEnumerator> enumerable)
84+
where TEnumerator : struct, IRefStructEnumerator<T>
85+
{
86+
var enumerator = enumerable.GetEnumerator();
87+
return ref RefInnerLast<T, TEnumerator>(enumerator);
88+
}
89+
90+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
91+
public static ref T Last<T, TEnumerable, TEnumerator>(this TEnumerable enumerable, Func<T, bool> predicate, Func<TEnumerable, IRefStructEnumerable<T, TEnumerator>> _)
92+
where TEnumerator : struct, IRefStructEnumerator<T>
93+
where TEnumerable : IRefStructEnumerable<T, TEnumerator>
94+
{
95+
var enumerator = enumerable.GetEnumerator();
96+
return ref RefInnerLast(enumerator, predicate);
97+
}
98+
99+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
100+
public static ref T Last<T, TEnumerator>(this IRefStructEnumerable<T, TEnumerator> enumerable, Func<T, bool> predicate)
101+
where TEnumerator : struct, IRefStructEnumerator<T>
102+
{
103+
var enumerator = enumerable.GetEnumerator();
104+
return ref RefInnerLast<T, TEnumerator>(enumerator, predicate);
105+
}
106+
107+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
108+
public static ref T Last<T, TEnumerable, TEnumerator, TFunc>(this TEnumerable enumerable, ref TFunc predicate, Func<TEnumerable, IRefStructEnumerable<T, TEnumerator>> _)
109+
where TEnumerator : struct, IRefStructEnumerator<T>
110+
where TEnumerable : IRefStructEnumerable<T, TEnumerator>
111+
where TFunc : struct, IInFunction<T, bool>
112+
{
113+
var enumerator = enumerable.GetEnumerator();
114+
return ref RefInnerLast<T, TEnumerator, TFunc>(enumerator, ref predicate);
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)