Skip to content

Commit 449c9dc

Browse files
committed
done Sequence/InfiniteSequence impl
1 parent 100b58a commit 449c9dc

File tree

6 files changed

+485
-36
lines changed

6 files changed

+485
-36
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ var seq = source
2828
foreach (var item in seq) { }
2929
```
3030

31-
* **99% compatibility** with .NET 10's LINQ (including new `Shuffle`, `RightJoin`, `LeftJoin` operators)
31+
* **99% compatibility** with .NET 10's LINQ (including new `Shuffle`, `RightJoin`, `LeftJoin`, `Sequence`, `InfiniteSequence` operators)
3232
* **Zero allocation** for method chains through struct-based Enumerable via `ValueEnumerable`
3333
* **LINQ to Span** to full support LINQ operations on `Span<T>` using .NET 9/C# 13's `allows ref struct`
3434
* **LINQ to Tree** to extend tree-structured objects (built-in support for FileSystem, JSON, GameObject)
@@ -164,7 +164,9 @@ Since `ZLinq` is not `IEnumerable<T>`, it cannot be passed to `String.Join`. `Jo
164164

165165
Range
166166
---
167-
`Range` is not only compatible with System.Linq's `Range(int start, int count)` but also has many additional overloads such as `System.Range` and `DateTime`.
167+
In .NET 10, `Enumerable.Sequence` and `Enumerable.InfiniteSequence` have been added, improving the expressiveness of Range operations. ZLinq also implements these, so they can be used. However, since they require `INumber<T>` and `IAdditionalOperator<T>`, they are limited to .NET 8 and above.
168+
169+
ZLinq extends more overloads of `ValueEnumerable.Range` than standard LINQ does. This Range extension can be used on all platforms (including .NET Standard 2.0).
168170

169171
```csharp
170172
// 95, 96, 97, 98, 99

sandbox/ConsoleApp/Program.cs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,9 @@
2525
// [assembly: ZLinqDropIn("ZLinq", DropInGenerateTypes.Everything)]
2626

2727

28-
var xs = Enumerable.Range(1, 100).ToArray();
29-
var a = xs.AsValueEnumerable().ToImmutableArray();
30-
31-
var ys = ValueEnumerable.Range(1, 100);
32-
var b = ys.ToImmutableArray();
33-
34-
// RangeIterator: IList<int>, IReadOnlyList<int>
35-
var zs = Enumerable.Range(1, 100).AsValueEnumerable();
36-
var c = zs.ToImmutableArray();
37-
38-
var zzs = Enumerable.Range(1, 100).Where(_ => true).AsValueEnumerable();
39-
var d = zzs.ToImmutableArray();
40-
41-
Console.WriteLine(a.SequenceEqual(b));
42-
Console.WriteLine(a.SequenceEqual(c));
43-
Console.WriteLine(a.SequenceEqual(d));
28+
var huga = ValueEnumerable.Sequence(100, 100, 0).ToArray();
4429

30+
foreach (var item in huga)
31+
{
32+
Console.WriteLine(item);
33+
}

src/ZLinq/Linq/InfiniteSequence.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ namespace ZLinq
99
{
1010
public static partial class ValueEnumerable
1111
{
12-
// TODO: impl
1312
public static ValueEnumerable<FromInfiniteSequence<T>, T> InfiniteSequence<T>(T start, T step)
1413
where T : IAdditionOperators<T, T, T>
1514
{
16-
throw new NotImplementedException();
15+
if (start is null) Throws.Null(nameof(start));
16+
if (step is null) Throws.Null(nameof(step));
17+
18+
return new(new(start, step));
1719
}
1820
}
1921
}
@@ -23,11 +25,14 @@ namespace ZLinq.Linq
2325
[StructLayout(LayoutKind.Auto)]
2426
[EditorBrowsable(EditorBrowsableState.Never)]
2527
public struct FromInfiniteSequence<T>(T start, T step) : IValueEnumerator<T>
28+
where T : IAdditionOperators<T, T, T>
2629
{
30+
bool calledGetNext;
31+
2732
public bool TryGetNonEnumeratedCount(out int count)
2833
{
29-
// TODO:
30-
throw new NotImplementedException();
34+
count = 0;
35+
return false;
3136
}
3237

3338
public bool TryGetSpan(out ReadOnlySpan<T> span)
@@ -38,13 +43,20 @@ public bool TryGetSpan(out ReadOnlySpan<T> span)
3843

3944
public bool TryCopyTo(scoped Span<T> destination, Index offset)
4045
{
41-
// TODO: we can use fill-incremental?
42-
throw new NotImplementedException();
46+
return false;
4347
}
4448

4549
public bool TryGetNext(out T current)
4650
{
47-
throw new NotImplementedException();
51+
if (!calledGetNext)
52+
{
53+
calledGetNext = true;
54+
current = start;
55+
return true;
56+
}
57+
58+
current = start += step;
59+
return true;
4860
}
4961

5062
public void Dispose()

src/ZLinq/Linq/Sequence.cs

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,40 @@ public static ValueEnumerable<FromSequence<T>, T> Sequence<T>(T start, T endIncl
1818

1919
if (step > T.Zero)
2020
{
21-
// TODO: impl increment pattern
22-
// Enumerable.Sequence has known primitive + 1 step has use Range(FillIncremental) optimization
21+
// Enumerable.Sequence has known primitive + 1 step has use Range(FillIncremental) optimization but currently we don't do it.
2322
// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Linq/src/System/Linq/Sequence.cs
23+
24+
if (endInclusive < start)
25+
{
26+
Throws.ArgumentOutOfRange(nameof(endInclusive));
27+
}
28+
29+
30+
// increment pattern
31+
return new(new(start, endInclusive, step, isIncrement: true));
2432
}
2533
else if (step < T.Zero)
2634
{
27-
// TODO: impl decrement pattern
35+
if (endInclusive > start)
36+
{
37+
Throws.ArgumentOutOfRange(nameof(endInclusive));
38+
}
39+
40+
// decrement pattern
41+
return new(new(start, endInclusive, step, isIncrement: false));
2842
}
2943
else
3044
{
31-
// TODO: impl repeat one pattern
32-
}
45+
// step == 0
46+
47+
if (start != endInclusive)
48+
{
49+
Throws.ArgumentOutOfRange(nameof(step));
50+
}
3351

34-
throw new NotImplementedException();
52+
// repeat one?
53+
return new(new(start, endInclusive, step, isIncrement: true));
54+
}
3555
}
3656
}
3757
}
@@ -41,11 +61,14 @@ namespace ZLinq.Linq
4161
[StructLayout(LayoutKind.Auto)]
4262
[EditorBrowsable(EditorBrowsableState.Never)]
4363
public struct FromSequence<T>(T start, T endInclusive, T step, bool isIncrement) : IValueEnumerator<T>
64+
where T : INumber<T>
4465
{
66+
bool calledGetNext;
67+
4568
public bool TryGetNonEnumeratedCount(out int count)
4669
{
47-
// TODO: can calculate count?
48-
throw new NotImplementedException();
70+
count = 0;
71+
return false;
4972
}
5073

5174
public bool TryGetSpan(out ReadOnlySpan<T> span)
@@ -56,13 +79,56 @@ public bool TryGetSpan(out ReadOnlySpan<T> span)
5679

5780
public bool TryCopyTo(scoped Span<T> destination, Index offset)
5881
{
59-
// TODO: we can use fill-incremental?
60-
throw new NotImplementedException();
82+
return false;
6183
}
6284

6385
public bool TryGetNext(out T current)
6486
{
65-
throw new NotImplementedException();
87+
if (!calledGetNext)
88+
{
89+
calledGetNext = true;
90+
current = start;
91+
return true;
92+
}
93+
94+
if (isIncrement)
95+
{
96+
var next = start + step;
97+
98+
if (next >= endInclusive || next <= start)
99+
{
100+
if (next == endInclusive && start != next)
101+
{
102+
current = start = next;
103+
return true;
104+
}
105+
106+
current = default!;
107+
return false;
108+
}
109+
110+
current = start = next;
111+
return true;
112+
}
113+
else
114+
{
115+
var next = start + step;
116+
117+
if (next <= endInclusive || next >= start)
118+
{
119+
if (next == endInclusive && start != next)
120+
{
121+
current = start = next;
122+
return true;
123+
}
124+
125+
current = default!;
126+
return false;
127+
}
128+
129+
current = start = next;
130+
return true;
131+
}
66132
}
67133

68134
public void Dispose()
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#if NET8_0_OR_GREATER
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Numerics;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
namespace ZLinq.Tests.Linq;
11+
12+
public class InfiniteSequenceTests
13+
{
14+
[Fact]
15+
public void NullArguments_Throws()
16+
{
17+
Assert.Throws<ArgumentNullException>("start", () => ValueEnumerable.InfiniteSequence((ReferenceAddable)null!, new()));
18+
Assert.Throws<ArgumentNullException>("step", () => ValueEnumerable.InfiniteSequence(new(), (ReferenceAddable)null!));
19+
}
20+
21+
[Fact]
22+
public void InfiniteSequence_AllZeroes_MatchesExpectedOutput()
23+
{
24+
Assert.Equal(Enumerable.Repeat(0, 10), ValueEnumerable.InfiniteSequence(0, 0).Take(10).ToArray());
25+
Assert.Equal(Enumerable.Repeat(0, 10).Select(i => (char)i), ValueEnumerable.InfiniteSequence((char)0, (char)0).Take(10).ToArray());
26+
Assert.Equal(Enumerable.Repeat(0, 10).Select(i => (BigInteger)i), ValueEnumerable.InfiniteSequence(BigInteger.Zero, BigInteger.Zero).Take(10).ToArray());
27+
Assert.Equal(Enumerable.Repeat(0, 10).Select(i => (float)i), ValueEnumerable.InfiniteSequence((float)0, 0).Take(10).ToArray());
28+
}
29+
30+
[Fact]
31+
public void InfiniteSequence_ProducesExpectedSequence()
32+
{
33+
Validate<sbyte>(0, 1);
34+
Validate<sbyte>(sbyte.MaxValue - 3, 2);
35+
Validate<sbyte>(sbyte.MinValue, sbyte.MaxValue / 2);
36+
37+
Validate<int>(0, 1);
38+
Validate<int>(4, -3);
39+
Validate<int>(int.MaxValue - 3, 2);
40+
Validate<int>(int.MinValue, int.MaxValue / 2);
41+
42+
Validate<long>(0L, 1L);
43+
Validate<long>(-4L, -3L);
44+
Validate<long>(long.MaxValue - 3L, 2L);
45+
Validate<long>(long.MinValue, long.MaxValue / 2L);
46+
47+
Validate<float>(0f, 1f);
48+
Validate<float>(0f, -1f);
49+
Validate<float>(float.MaxValue, 1f);
50+
Validate<float>(float.MinValue, float.MaxValue / 2f);
51+
52+
Validate<BigInteger>(new BigInteger(long.MaxValue) * 3, (BigInteger)12345);
53+
Validate<BigInteger>(new BigInteger(long.MaxValue) * 3, (BigInteger)(-12345));
54+
55+
void Validate<T>(T start, T step) where T : INumber<T>
56+
{
57+
var sequence = ValueEnumerable.InfiniteSequence(start, step);
58+
59+
for (int trial = 0; trial < 2; trial++)
60+
{
61+
using var e = sequence.GetEnumerator();
62+
63+
T expected = start;
64+
for (int i = 0; i < 10; i++)
65+
{
66+
Assert.True(e.MoveNext());
67+
Assert.Equal(expected, e.Current);
68+
69+
expected += step;
70+
}
71+
}
72+
}
73+
}
74+
75+
private sealed class ReferenceAddable : IAdditionOperators<ReferenceAddable, ReferenceAddable, ReferenceAddable>
76+
{
77+
public static ReferenceAddable operator +(ReferenceAddable left, ReferenceAddable right) => left;
78+
public static ReferenceAddable operator checked +(ReferenceAddable left, ReferenceAddable right) => left;
79+
}
80+
}
81+
82+
#endif

0 commit comments

Comments
 (0)