Skip to content

Commit 9bad9fe

Browse files
committed
fix for take...
1 parent 1dd812f commit 9bad9fe

File tree

8 files changed

+575
-26
lines changed

8 files changed

+575
-26
lines changed

src/ZLinq/Linq/Skip.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,36 @@ public bool TryCopyTo(Span<TSource> destination, Index offset)
4747
{
4848
if (source.TryGetNonEnumeratedCount(out var count))
4949
{
50-
var sourceOffset = offset.GetOffset(count);
51-
var skipOffset = sourceOffset + skipCount;
52-
if (skipOffset >= count)
50+
var actualSkipCount = Math.Min(count, skipCount);
51+
52+
var remainingCount = count - actualSkipCount;
53+
54+
if (remainingCount <= 0)
55+
{
56+
return false;
57+
}
58+
59+
var offsetInSkipped = offset.GetOffset(remainingCount);
60+
61+
if (offsetInSkipped < 0 || offsetInSkipped >= remainingCount)
5362
{
5463
return false;
5564
}
5665

57-
return source.TryCopyTo(destination, skipOffset);
66+
var sourceOffset = actualSkipCount + offsetInSkipped;
67+
68+
var elementsAvailable = remainingCount - offsetInSkipped;
69+
70+
var elementsToCopy = Math.Min(elementsAvailable, destination.Length);
71+
72+
if (elementsToCopy <= 0)
73+
{
74+
return false;
75+
}
76+
77+
return source.TryCopyTo(destination.Slice(0, elementsToCopy), sourceOffset);
5878
}
79+
5980
return false;
6081
}
6182

src/ZLinq/Linq/SkipLast.cs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,36 @@ public bool TryCopyTo(Span<TSource> destination, Index offset)
6464
{
6565
if (source.TryGetNonEnumeratedCount(out var count))
6666
{
67-
Index skipFromLast = ^skipCount;
68-
var offset1 = offset.GetOffset(count);
69-
var takeCount = skipFromLast.GetOffset(count - offset1);
70-
if (source.TryCopyTo(destination.Slice(0, Math.Min(takeCount, destination.Length)), offset))
67+
var actualSkipCount = Math.Min(count, skipCount);
68+
69+
var remainingCount = count - actualSkipCount;
70+
71+
if (remainingCount <= 0)
7172
{
72-
return true;
73+
return false;
74+
}
75+
76+
var offsetInSkipped = offset.GetOffset(remainingCount);
77+
78+
if (offsetInSkipped < 0 || offsetInSkipped >= remainingCount)
79+
{
80+
return false;
7381
}
82+
83+
var sourceOffset = offsetInSkipped;
84+
85+
var elementsAvailable = remainingCount - offsetInSkipped;
86+
87+
var elementsToCopy = Math.Min(elementsAvailable, destination.Length);
88+
89+
if (elementsToCopy <= 0)
90+
{
91+
return false;
92+
}
93+
94+
return source.TryCopyTo(destination.Slice(0, elementsToCopy), sourceOffset);
7495
}
96+
7597
return false;
7698
}
7799

src/ZLinq/Linq/Take.cs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,36 @@ public bool TryGetSpan(out ReadOnlySpan<TSource> span)
225225

226226
public bool TryCopyTo(Span<TSource> destination, Index offset)
227227
{
228-
// skip-index and remains(count) is valid.
229-
if (TryGetNonEnumeratedCount(out var count))
228+
if (source.TryGetNonEnumeratedCount(out var totalCount))
230229
{
231-
var skipOffset = offset.GetOffset(count) + skipIndex;
232-
if (source.TryCopyTo(destination.Slice(0, Math.Min(destination.Length, count)), skipOffset))
230+
var effectiveRemains = skipIndex < totalCount
231+
? Math.Min(remains, totalCount - skipIndex)
232+
: 0;
233+
234+
if (effectiveRemains <= 0)
233235
{
234-
return true;
236+
return false;
235237
}
238+
239+
var offsetInRange = offset.GetOffset(effectiveRemains);
240+
241+
if (offsetInRange < 0 || offsetInRange >= effectiveRemains)
242+
{
243+
return false;
244+
}
245+
246+
var sourceOffset = skipIndex + offsetInRange;
247+
248+
var elementsAvailable = effectiveRemains - offsetInRange;
249+
250+
var elementsToCopy = Math.Min(elementsAvailable, destination.Length);
251+
252+
if (elementsToCopy <= 0)
253+
{
254+
return false;
255+
}
256+
257+
return source.TryCopyTo(destination.Slice(0, elementsToCopy), sourceOffset);
236258
}
237259

238260
return false;

src/ZLinq/Linq/TakeLast.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,36 @@ public bool TryGetSpan(out ReadOnlySpan<TSource> span)
5858
return false;
5959
}
6060

61-
public bool TryCopyTo(Span<TSource> destination, Index offset) // TODO: impl
61+
public bool TryCopyTo(Span<TSource> destination, Index offset)
6262
{
63-
if (TryGetSpan(out var span) && span.Length <= destination.Length)
63+
if (source.TryGetNonEnumeratedCount(out var count))
6464
{
65-
span.CopyTo(destination);
66-
return true;
65+
var actualTakeCount = Math.Min(count, takeCount);
66+
if (actualTakeCount <= 0)
67+
{
68+
return false;
69+
}
70+
71+
var takeLastStartIndex = count - actualTakeCount;
72+
var offsetInTakeLast = offset.GetOffset(actualTakeCount);
73+
74+
if (offsetInTakeLast < 0 || offsetInTakeLast >= actualTakeCount)
75+
{
76+
return false;
77+
}
78+
79+
var sourceOffset = takeLastStartIndex + offsetInTakeLast;
80+
var remainingElements = actualTakeCount - offsetInTakeLast;
81+
var elementsToCopy = Math.Min(remainingElements, destination.Length);
82+
83+
if (elementsToCopy <= 0)
84+
{
85+
return false;
86+
}
87+
88+
return source.TryCopyTo(destination.Slice(0, elementsToCopy), sourceOffset);
6789
}
90+
6891
return false;
6992
}
7093

tests/ZLinq.Tests/Linq/SkipLastTest.cs

Lines changed: 166 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -201,14 +201,6 @@ public void SkipLastTryCopyTo()
201201
Array.Clear(dest);
202202
source.SkipLast(2).TryCopyTo(dest, 2).ShouldBeTrue();
203203
dest.ShouldBe([3, 0, 0, 0, 0]);
204-
205-
Array.Clear(dest);
206-
source.SkipLast(2).TryCopyTo(dest, 3).ShouldBeTrue();
207-
dest.ShouldBe([0, 0, 0, 0, 0]);
208-
209-
Array.Clear(dest);
210-
source.SkipLast(3).TryCopyTo(dest, 2).ShouldBeTrue();
211-
dest.ShouldBe([0, 0, 0, 0, 0]);
212204
}
213205

214206
[Fact]
@@ -224,6 +216,172 @@ public void SkipLastTryCopyTo2()
224216
source2.Take(7).SkipLast(2).SkipLast(3).ToArray().ShouldBe(expected2);
225217
}
226218

219+
[Fact]
220+
public void SkipLast_TryCopyTo_EmptyCollection()
221+
{
222+
var emptySource = Array.Empty<int>().AsValueEnumerable();
223+
var dest = new int[5];
224+
225+
// Empty source with skip
226+
emptySource.SkipLast(3).TryCopyTo(dest, 0).ShouldBeFalse();
227+
dest.ShouldBe(new int[5]); // Destination should remain unchanged
228+
}
229+
230+
[Fact]
231+
public void SkipLast_TryCopyTo_SkipCountExceedsSourceSize()
232+
{
233+
var source = new[] { 1, 2, 3 }.AsValueEnumerable();
234+
var dest = new int[5];
235+
236+
// Skip more than available
237+
source.SkipLast(5).TryCopyTo(dest, 0).ShouldBeFalse();
238+
dest.ShouldBe(new int[5]); // Destination should remain unchanged
239+
}
240+
241+
[Fact]
242+
public void SkipLast_TryCopyTo_InvalidOffset()
243+
{
244+
var source = new[] { 1, 2, 3, 4, 5 }.AsValueEnumerable();
245+
var dest = new int[3];
246+
247+
// Negative offset (in equivalent offset form)
248+
source.SkipLast(2).TryCopyTo(dest, ^4).ShouldBeFalse();
249+
250+
// Offset beyond remaining count
251+
source.SkipLast(2).TryCopyTo(dest, 3).ShouldBeFalse();
252+
}
253+
254+
[Fact]
255+
public void SkipLast_TryCopyTo_DestinationTooSmall()
256+
{
257+
var source = new[] { 1, 2, 3, 4, 5, 6, 7 }.AsValueEnumerable();
258+
var smallDest = new int[2];
259+
260+
// Partial copy with small destination
261+
source.SkipLast(2).TryCopyTo(smallDest, 0).ShouldBeTrue();
262+
smallDest.ShouldBe(new[] { 1, 2 }); // Should copy only what fits
263+
264+
// With offset
265+
Array.Clear(smallDest);
266+
source.SkipLast(2).TryCopyTo(smallDest, 2).ShouldBeTrue();
267+
smallDest.ShouldBe(new[] { 3, 4 });
268+
}
269+
270+
[Fact]
271+
public void SkipLast_TryCopyTo_FromEnd()
272+
{
273+
var source = new[] { 1, 2, 3, 4, 5 }.AsValueEnumerable();
274+
var dest = new int[3];
275+
276+
// Using Index.FromEnd
277+
source.SkipLast(2).TryCopyTo(dest, ^3).ShouldBeTrue();
278+
dest.ShouldBe(new[] { 1, 2, 3 });
279+
280+
Array.Clear(dest);
281+
source.SkipLast(2).TryCopyTo(dest, ^2).ShouldBeTrue();
282+
dest.ShouldBe(new[] { 2, 3, 0 });
283+
284+
Array.Clear(dest);
285+
source.SkipLast(2).TryCopyTo(dest, ^1).ShouldBeTrue();
286+
dest.ShouldBe(new[] { 3, 0, 0 });
287+
}
288+
289+
[Fact]
290+
public void SkipLast_TryCopyTo_WithZeroElementsAvailable()
291+
{
292+
var source = new[] { 1, 2, 3 }.AsValueEnumerable();
293+
var dest = new int[3];
294+
295+
// When element count is exactly zero after calculations
296+
source.SkipLast(3).TryCopyTo(dest, 0).ShouldBeFalse();
297+
dest.ShouldBe(new int[3]); // Destination should remain unchanged
298+
}
299+
300+
[Fact]
301+
public void SkipLast_TryCopyTo_WithDestinationSlicing()
302+
{
303+
var source = new[] { 1, 2, 3, 4, 5, 6 }.AsValueEnumerable();
304+
var dest = new int[5];
305+
306+
// Ensure destination slicing works correctly
307+
source.SkipLast(2).TryCopyTo(dest, 1).ShouldBeTrue();
308+
dest.ShouldBe(new[] { 2, 3, 4, 0, 0 });
309+
310+
// Test with partial destination
311+
Array.Clear(dest);
312+
var slice = dest.AsSpan(1, 3);
313+
source.SkipLast(3).TryCopyTo(slice, 0).ShouldBeTrue();
314+
dest.ShouldBe(new[] { 0, 1, 2, 3, 0 });
315+
}
316+
317+
[Fact]
318+
public void SkipLast_TryCopyTo_ExactElementCount()
319+
{
320+
var source = new[] { 1, 2, 3, 4, 5 }.AsValueEnumerable();
321+
var dest = new int[3];
322+
323+
// Destination length exactly matches remaining elements
324+
source.SkipLast(2).TryCopyTo(dest, 0).ShouldBeTrue();
325+
dest.ShouldBe(new[] { 1, 2, 3 });
326+
}
327+
328+
[Fact]
329+
public void SkipLast_TryCopyTo_ZeroElementsToCopy()
330+
{
331+
var source = new[] { 1, 2, 3, 4, 5 }.AsValueEnumerable();
332+
var dest = new int[0];
333+
334+
source.SkipLast(2).TryCopyTo(dest, 2).ShouldBeFalse();
335+
}
336+
337+
[Fact]
338+
public void SkipLast_TryCopyTo_ChainedOperations()
339+
{
340+
var source = Enumerable.Range(1, 10).ToArray().AsValueEnumerable();
341+
var dest = new int[5];
342+
343+
// Chain multiple operations
344+
source.Skip(2).SkipLast(3).TryCopyTo(dest, 0).ShouldBeTrue();
345+
dest.ShouldBe(new[] { 3, 4, 5, 6, 7 });
346+
347+
// With different offset
348+
Array.Clear(dest);
349+
source.Skip(2).SkipLast(3).TryCopyTo(dest, 2).ShouldBeTrue();
350+
dest.ShouldBe(new[] { 5, 6, 7, 0, 0 });
351+
}
352+
353+
[Fact]
354+
public void SkipLast_TryCopyTo_NonEnumerableSourceCount()
355+
{
356+
// Create a custom enumerable that doesn't implement TryGetNonEnumeratedCount
357+
var nonCountableSource = new[] { 1, 2, 3, 4, 5 }
358+
.Select(x => x) // Forces enumeration
359+
.AsValueEnumerable();
360+
361+
var dest = new int[3];
362+
363+
// Should return false since the source can't provide count without enumeration
364+
nonCountableSource.SkipLast(2).TryCopyTo(dest, 0).ShouldBeFalse();
365+
dest.ShouldBe(new int[3]); // Destination should remain unchanged
366+
}
367+
368+
[Fact]
369+
public void SkipLast_TryCopyTo_BoundaryConditions()
370+
{
371+
var source = new[] { 1, 2, 3, 4, 5 }.AsValueEnumerable();
372+
var dest = new int[3];
373+
374+
// Skip everything but one element
375+
source.SkipLast(4).TryCopyTo(dest, 0).ShouldBeTrue();
376+
dest.ShouldBe(new[] { 1, 0, 0 });
377+
378+
// Skip exactly the right number to leave one element with max offset
379+
Array.Clear(dest);
380+
source.SkipLast(4).TryCopyTo(dest, 0).ShouldBeTrue();
381+
dest.ShouldBe(new[] { 1, 0, 0 });
382+
}
383+
384+
227385
// Helper class to test disposal behavior
228386
private class DisposableTestEnumerator<T>(IEnumerable<T> source, Action onDispose) : IEnumerable<T>
229387
{

0 commit comments

Comments
 (0)