Skip to content

Commit d9203de

Browse files
committed
fix state copy on Take(Range)
1 parent 9bad9fe commit d9203de

File tree

5 files changed

+106
-59
lines changed

5 files changed

+106
-59
lines changed

sandbox/ConsoleApp/Program.cs

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,41 +30,37 @@
3030
//var ok = string.Join(',', tako);
3131

3232

33+
IEnumerable<int> source = ForceNotCollection([1, 2, 3, 4, 5]);
3334

35+
var values = source.AsValueEnumerable().Take(^5..3); // 1,2,3
36+
var a = values.ToArray();
37+
var b = values.ToArray();
3438

3539

36-
IEnumerable<double?> source = [double.MaxValue, null, double.MaxValue];
37-
var expected = double.PositiveInfinity;
38-
39-
40-
41-
42-
var a = source.Sum();
43-
var b = source.Sum(x => x);
44-
45-
46-
47-
48-
40+
static IEnumerable<T> ForceNotCollection<T>(IEnumerable<T> source)
41+
{
42+
foreach (T item in source)
43+
yield return item;
44+
}
4945

50-
var srcFiles = new DirectoryInfo("../../../../../src/ZLinq/Linq/").GetFiles();
51-
var tstFiles = new DirectoryInfo("../../../../../tests/ZLinq.Tests/Linq/").GetFiles();
46+
//var srcFiles = new DirectoryInfo("../../../../../src/ZLinq/Linq/").GetFiles();
47+
//var tstFiles = new DirectoryInfo("../../../../../tests/ZLinq.Tests/Linq/").GetFiles();
5248

53-
var grouping = srcFiles.AsValueEnumerable()
54-
.LeftJoin(tstFiles,
55-
x => x.Name,
56-
x => x.Name.Replace("Test", ""),
57-
(outer, inner) => new { Name = outer.Name, IsTested = inner != null })
58-
.GroupBy(x => x.IsTested);
49+
//var grouping = srcFiles.AsValueEnumerable()
50+
// .LeftJoin(tstFiles,
51+
// x => x.Name,
52+
// x => x.Name.Replace("Test", ""),
53+
// (outer, inner) => new { Name = outer.Name, IsTested = inner != null })
54+
// .GroupBy(x => x.IsTested);
5955

60-
foreach (var g in grouping)
61-
{
62-
Console.WriteLine(g.Key ? "Tested::::::::::::::::::" : "NotTested::::::::::::::::::");
63-
foreach (var item in g)
64-
{
65-
Console.WriteLine(item.Name);
66-
}
67-
}
56+
//foreach (var g in grouping)
57+
//{
58+
// Console.WriteLine(g.Key ? "Tested::::::::::::::::::" : "NotTested::::::::::::::::::");
59+
// foreach (var item in g)
60+
// {
61+
// Console.WriteLine(item.Name);
62+
// }
63+
//}
6864

6965

7066
return;

src/ZLinq/Linq/OrderBy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ struct OrderBy<TEnumerator, TSource, TKey>(TEnumerator source, Func<TSource, TKe
113113
#endif
114114
{
115115
TEnumerator source = source;
116-
OrderByComparable<TSource, TKey> comparable = new(keySelector, comparer, parent, descending); // boxed
116+
OrderByComparable<TSource, TKey> comparable = new(keySelector, comparer, parent, descending); // boxed, this is safe to copy as struct state
117117

118118
RentedArrayBox<TSource>? buffer;
119119
int index;

src/ZLinq/Linq/Take.cs

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -114,27 +114,31 @@ public ref
114114
#else
115115
public
116116
#endif
117-
struct TakeRange<TEnumerator, TSource>
117+
struct TakeRange<TEnumerator, TSource>(TEnumerator source, Range range)
118118
: IValueEnumerator<TSource>
119119
where TEnumerator : struct, IValueEnumerator<TSource>
120120
#if NET9_0_OR_GREATER
121121
, allows ref struct
122122
#endif
123123
{
124-
TEnumerator source;
125-
readonly Range range;
124+
TEnumerator source = source;
125+
readonly Range range = range;
126126

127127
int index;
128128
int remains;
129-
readonly int skipIndex;
130-
readonly int fromEndQueueCount; // 0 is not use q
131-
Queue<TSource>? q; // TODO:RefBox<ValueQUeue>>
129+
int skipIndex;
130+
int fromEndQueueCount; // 0 is not use q
131+
RefBox<ValueQueue<TSource>>? q;
132+
bool isInitialized;
132133

133-
public TakeRange(TEnumerator source, Range range)
134+
void Init()
134135
{
135-
// initialize before run.
136-
this.source = source;
137-
this.range = range;
136+
if (isInitialized)
137+
{
138+
return;
139+
}
140+
isInitialized = true;
141+
138142
this.fromEndQueueCount = 0;
139143
this.remains = -1; // unknown
140144

@@ -172,15 +176,15 @@ public TakeRange(TEnumerator source, Range range)
172176
}
173177

174178
this.fromEndQueueCount = int.MaxValue; // unknown queue count
175-
this.q = new();
179+
this.q = new(new(4));
176180
}
177181
else if (range.Start.IsFromEnd && !range.End.IsFromEnd) // start-fromend
178182
{
179183
// unknown skipIndex and remains
180184
this.skipIndex = 0;
181185
this.fromEndQueueCount = range.Start.Value; //queue size is fixed from end-of-start
182186
if (this.fromEndQueueCount == 0) fromEndQueueCount = 1;
183-
this.q = new();
187+
this.q = new(new(4));
184188
}
185189
else if (range.Start.IsFromEnd && range.End.IsFromEnd) // both fromend
186190
{
@@ -195,13 +199,15 @@ public TakeRange(TEnumerator source, Range range)
195199
}
196200
this.fromEndQueueCount = range.Start.Value;
197201
if (this.fromEndQueueCount == 0) fromEndQueueCount = 1;
198-
this.q = new();
202+
this.q = new(new(4));
199203
}
200204
}
201205
}
202206

203207
public bool TryGetNonEnumeratedCount(out int count)
204208
{
209+
Init();
210+
205211
if (source.TryGetNonEnumeratedCount(out _))
206212
{
207213
count = remains;
@@ -213,6 +219,8 @@ public bool TryGetNonEnumeratedCount(out int count)
213219

214220
public bool TryGetSpan(out ReadOnlySpan<TSource> span)
215221
{
222+
Init();
223+
216224
if (source.TryGetSpan(out span))
217225
{
218226
span = span.Slice(skipIndex, remains);
@@ -225,6 +233,8 @@ public bool TryGetSpan(out ReadOnlySpan<TSource> span)
225233

226234
public bool TryCopyTo(Span<TSource> destination, Index offset)
227235
{
236+
Init();
237+
228238
if (source.TryGetNonEnumeratedCount(out var totalCount))
229239
{
230240
var effectiveRemains = skipIndex < totalCount
@@ -262,13 +272,15 @@ public bool TryCopyTo(Span<TSource> destination, Index offset)
262272

263273
public bool TryGetNext(out TSource current)
264274
{
275+
Init();
276+
265277
if (remains == 0)
266278
{
267279
goto END;
268280
}
269281

270282
DEQUEUE:
271-
if (q != null && q.Count != 0)
283+
if (q != null && q.GetValueRef().Count != 0)
272284
{
273285
if (remains == -1)
274286
{
@@ -284,18 +296,18 @@ public bool TryGetNext(out TSource current)
284296
}
285297

286298
// q.Count is fromEnd
287-
var offset = count - q.Count;
299+
var offset = count - q.GetValueRef().Count;
288300
var skipIndex = Math.Max(0, start - offset);
289301
while (skipIndex > 0)
290302
{
291-
q.Dequeue();
303+
q.GetValueRef().Dequeue();
292304
skipIndex--;
293305
}
294306
}
295307

296308
if (remains-- > 0)
297309
{
298-
current = q.Dequeue();
310+
current = q.GetValueRef().Dequeue();
299311
return true;
300312
}
301313
else
@@ -329,15 +341,15 @@ public bool TryGetNext(out TSource current)
329341
continue;
330342
}
331343

332-
if (q.Count == fromEndQueueCount)
344+
if (q.GetValueRef().Count == fromEndQueueCount)
333345
{
334-
q.Dequeue();
346+
q.GetValueRef().Dequeue();
335347
}
336-
q.Enqueue(current);
348+
q.GetValueRef().Enqueue(current);
337349
}
338350
}
339351

340-
if (q != null && q.Count != 0)
352+
if (q != null && q.GetValueRef().Count != 0)
341353
{
342354
goto DEQUEUE;
343355
}
@@ -350,6 +362,7 @@ public bool TryGetNext(out TSource current)
350362

351363
public void Dispose()
352364
{
365+
q?.Dispose();
353366
source.Dispose();
354367
}
355368
}

src/ZLinq/Linq/TakeLast.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ struct TakeLast<TEnumerator, TSource>(TEnumerator source, Int32 count)
3030
{
3131
TEnumerator source = source;
3232
readonly int takeCount = Math.Max(0, count);
33-
Queue<TSource>? q; // TODO:RefBox<ValueQUeue>>
33+
RefBox<ValueQueue<TSource>>? q;
3434

3535
public bool TryGetNonEnumeratedCount(out int count)
3636
{
@@ -101,33 +101,34 @@ public bool TryGetNext(out TSource current)
101101

102102
if (q == null)
103103
{
104-
q = new Queue<TSource>();
104+
q = new(new(4));
105105
}
106106

107107
DEQUEUE:
108-
if (q.Count != 0)
108+
if (q.GetValueRef().Count != 0)
109109
{
110-
current = q.Dequeue();
110+
current = q.GetValueRef().Dequeue();
111111
return true;
112112
}
113113

114114
while (source.TryGetNext(out current))
115115
{
116-
if (q.Count == takeCount)
116+
if (q.GetValueRef().Count == takeCount)
117117
{
118-
q.Dequeue();
118+
q.GetValueRef().Dequeue();
119119
}
120-
q.Enqueue(current);
120+
q.GetValueRef().Enqueue(current);
121121
}
122122

123-
if (q.Count != 0) goto DEQUEUE;
123+
if (q.GetValueRef().Count != 0) goto DEQUEUE;
124124

125125
Unsafe.SkipInit(out current);
126126
return false;
127127
}
128128

129129
public void Dispose()
130130
{
131+
q?.Dispose();
131132
source.Dispose();
132133
}
133134
}

tests/ZLinq.Tests/Linq/TakeTest.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,41 @@ public void T2()
272272
source.AsValueEnumerable().Take(3).Skip(1).ToArray().ShouldBe([2, 3]);
273273
source.AsValueEnumerable().Skip(1).Take(3).ToArray().ShouldBe([2, 3, 4]);
274274
}
275+
276+
[Fact]
277+
public void T3()
278+
{
279+
IEnumerable<int> source = ForceNotCollection([1, 2, 3, 4, 5]);
280+
281+
// LINQ
282+
{
283+
var values = source.Take(^5..3); // 1,2,3
284+
Assert.Equal(values.ToArray(), values.ToArray());
285+
}
286+
287+
// ZLinq
288+
{
289+
var values = source.AsValueEnumerable().Take(^5..3); // 1,2,3
290+
Assert.Equal(values.ToArray(), values.ToArray());
291+
}
292+
293+
static IEnumerable<T> ForceNotCollection<T>(IEnumerable<T> source)
294+
{
295+
foreach (T item in source)
296+
yield return item;
297+
}
298+
299+
}
300+
301+
[Fact]
302+
public void T4()
303+
{
304+
int[] source = [1, 2, 3, 4, 5];
305+
306+
// LINQ
307+
Assert.Equal(0, source.Take(0..0).LastOrDefault());
308+
309+
// ZLinq
310+
Assert.Equal(0, source.AsValueEnumerable().Take(0..0).LastOrDefault());
311+
}
275312
}

0 commit comments

Comments
 (0)