Skip to content

Commit cf2b725

Browse files
ArmoryNodeatifaziz
andauthored
Add "SkipLastWhile" extension
This is a squashed merge of PR #1085 that closes #1036. --------- Co-authored-by: Atif Aziz <[email protected]>
1 parent 72c3c66 commit cf2b725

File tree

11 files changed

+236
-0
lines changed

11 files changed

+236
-0
lines changed

MoreLinq.Test/SkipLastWhileTest.cs

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#region License and Terms
2+
// MoreLINQ - Extensions to LINQ to Objects
3+
// Copyright (c) 2024 Andy Romero (armorynode). All rights reserved.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
#endregion
17+
18+
namespace MoreLinq.Test
19+
{
20+
using System.Collections.Generic;
21+
using NUnit.Framework;
22+
23+
[TestFixture]
24+
public class SkipLastWhileTest
25+
{
26+
[Test]
27+
public void IsLazy()
28+
{
29+
_ = new BreakingSequence<object>().SkipLastWhile(BreakingFunc.Of<object, bool>());
30+
}
31+
32+
[TestCase(SourceKind.Sequence)]
33+
[TestCase(SourceKind.BreakingList)]
34+
[TestCase(SourceKind.BreakingReadOnlyList)]
35+
public void PredicateNeverFalse(SourceKind sourceKind)
36+
{
37+
using var sequence = TestingSequence.Of(0, 1, 2, 3, 4);
38+
39+
Assert.That(sequence.ToSourceKind(sourceKind).SkipLastWhile(x => x < 5), Is.Empty);
40+
}
41+
42+
[TestCase(SourceKind.Sequence)]
43+
[TestCase(SourceKind.BreakingList)]
44+
[TestCase(SourceKind.BreakingReadOnlyList)]
45+
public void PredicateNeverTrue(SourceKind sourceKind)
46+
{
47+
using var sequence = TestingSequence.Of(0, 1, 2, 3, 4);
48+
49+
sequence.ToSourceKind(sourceKind)
50+
.SkipLastWhile(x => x == 100)
51+
.AssertSequenceEqual(0, 1, 2, 3, 4);
52+
}
53+
54+
[TestCase(SourceKind.Sequence)]
55+
[TestCase(SourceKind.BreakingList)]
56+
[TestCase(SourceKind.BreakingReadOnlyList)]
57+
public void PredicateBecomesTruePartWay(SourceKind sourceKind)
58+
{
59+
using var sequence = TestingSequence.Of(0, 1, 2, 3, 4);
60+
61+
sequence.ToSourceKind(sourceKind)
62+
.SkipLastWhile(x => x > 2)
63+
.AssertSequenceEqual(0, 1, 2);
64+
}
65+
66+
[TestCase(SourceKind.Sequence)]
67+
[TestCase(SourceKind.BreakingList)]
68+
[TestCase(SourceKind.BreakingReadOnlyList)]
69+
public void NeverEvaluatesPredicateWhenSourceIsEmpty(SourceKind sourceKind)
70+
{
71+
using var sequence = TestingSequence.Of<int>();
72+
73+
Assert.That(sequence.ToSourceKind(sourceKind)
74+
.SkipLastWhile(BreakingFunc.Of<int, bool>()),
75+
Is.Empty);
76+
}
77+
78+
[TestCase(SourceKind.Sequence)]
79+
[TestCase(SourceKind.BreakingList)]
80+
[TestCase(SourceKind.BreakingReadOnlyList)]
81+
public void UsesCollectionCountAtIterationTime(SourceKind sourceKind)
82+
{
83+
var list = new List<int> { 1, 2, 3, 4 };
84+
var result = list.ToSourceKind(sourceKind).SkipLastWhile(x => x > 2);
85+
list.Add(5);
86+
result.AssertSequenceEqual(1, 2);
87+
}
88+
89+
[TestCase(SourceKind.Sequence)]
90+
[TestCase(SourceKind.BreakingList)]
91+
[TestCase(SourceKind.BreakingReadOnlyList)]
92+
public void KeepsNonTrailingItemsThatMatchPredicate(SourceKind sourceKind)
93+
{
94+
using var sequence = TestingSequence.Of(1, 2, 0, 0, 3, 4, 0, 0);
95+
96+
sequence.ToSourceKind(sourceKind)
97+
.SkipLastWhile(x => x == 0)
98+
.AssertSequenceEqual(1, 2, 0, 0, 3, 4);
99+
}
100+
}
101+
}

MoreLinq/Extensions.g.cs

+26
Original file line numberDiff line numberDiff line change
@@ -5460,6 +5460,32 @@ public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
54605460

54615461
}
54625462

5463+
/// <summary><c>SkipLastWhile</c> extension.</summary>
5464+
5465+
[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
5466+
public static partial class SkipLastWhileExtension
5467+
{
5468+
/// <summary>
5469+
/// Removes elements from the end of a sequence as long as a specified condition is true.
5470+
/// </summary>
5471+
/// <typeparam name="T">Type of the source sequence.</typeparam>
5472+
/// <param name="source">The source sequence.</param>
5473+
/// <param name="predicate">The predicate to use to remove items from the tail of the sequence.</param>
5474+
/// <returns>
5475+
/// An <see cref="IEnumerable{T}"/> containing the source sequence elements except for the bypassed ones at the end.
5476+
/// </returns>
5477+
/// <exception cref="ArgumentNullException">The source sequence is <see langword="null"/>.</exception>
5478+
/// <exception cref="ArgumentNullException">The predicate is <see langword="null"/>.</exception>
5479+
/// <remarks>
5480+
/// This operator uses deferred execution and streams its results. At any given time, it
5481+
/// will buffer as many consecutive elements as satisfied by <paramref name="predicate"/>.
5482+
/// </remarks>
5483+
5484+
public static IEnumerable<T> SkipLastWhile<T>(this IEnumerable<T> source, Func<T, bool> predicate)
5485+
=> MoreEnumerable.SkipLastWhile(source, predicate);
5486+
5487+
}
5488+
54635489
/// <summary><c>SkipUntil</c> extension.</summary>
54645490

54655491
[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]

MoreLinq/MoreLinq.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
- Sequence
8787
- Shuffle
8888
- SkipLast
89+
- SkipLastWhile
8990
- SkipUntil
9091
- Slice
9192
- SortedMerge
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
#nullable enable
2+
MoreLinq.Extensions.SkipLastWhileExtension
3+
static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
4+
static MoreLinq.MoreEnumerable.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#nullable enable
2+
MoreLinq.Extensions.SkipLastWhileExtension
3+
static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
24
static MoreLinq.MoreEnumerable.Sequence<T>(T start) -> System.Collections.Generic.IEnumerable<T>!
35
static MoreLinq.MoreEnumerable.Sequence<T>(T start, T stop) -> System.Collections.Generic.IEnumerable<T>!
46
static MoreLinq.MoreEnumerable.Sequence<T>(T start, T stop, T step) -> System.Collections.Generic.IEnumerable<T>!
7+
static MoreLinq.MoreEnumerable.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!

MoreLinq/PublicAPI/net9.0/PublicAPI.Unshipped.txt

+3
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ MoreLinq.Extensions.ShuffleExtension
9494
MoreLinq.Extensions.SingleExtension
9595
MoreLinq.Extensions.SingleOrDefaultExtension
9696
MoreLinq.Extensions.SkipLastExtension
97+
MoreLinq.Extensions.SkipLastWhileExtension
9798
MoreLinq.Extensions.SkipUntilExtension
9899
MoreLinq.Extensions.SliceExtension
99100
MoreLinq.Extensions.SortedMergeExtension
@@ -339,6 +340,7 @@ static MoreLinq.Extensions.ShuffleExtension.Shuffle<T>(this System.Collections.G
339340
static MoreLinq.Extensions.SingleExtension.Single<T>(this MoreLinq.IExtremaEnumerable<T>! source) -> T
340341
static MoreLinq.Extensions.SingleOrDefaultExtension.SingleOrDefault<T>(this MoreLinq.IExtremaEnumerable<T>! source) -> T?
341342
static MoreLinq.Extensions.SkipLastExtension.SkipLast<T>(this System.Collections.Generic.IEnumerable<T>! source, int count) -> System.Collections.Generic.IEnumerable<T>!
343+
static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
342344
static MoreLinq.Extensions.SkipUntilExtension.SkipUntil<TSource>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, bool>! predicate) -> System.Collections.Generic.IEnumerable<TSource>!
343345
static MoreLinq.Extensions.SliceExtension.Slice<T>(this System.Collections.Generic.IEnumerable<T>! sequence, int startIndex, int count) -> System.Collections.Generic.IEnumerable<T>!
344346
static MoreLinq.Extensions.SortedMergeExtension.SortedMerge<TSource>(this System.Collections.Generic.IEnumerable<TSource>! source, MoreLinq.OrderByDirection direction, params System.Collections.Generic.IEnumerable<TSource>![]! otherSequences) -> System.Collections.Generic.IEnumerable<TSource>!
@@ -619,6 +621,7 @@ static MoreLinq.MoreEnumerable.Shuffle<T>(this System.Collections.Generic.IEnume
619621
static MoreLinq.MoreEnumerable.Single<T>(this MoreLinq.IExtremaEnumerable<T>! source) -> T
620622
static MoreLinq.MoreEnumerable.SingleOrDefault<T>(this MoreLinq.IExtremaEnumerable<T>! source) -> T?
621623
static MoreLinq.MoreEnumerable.SkipLast<T>(System.Collections.Generic.IEnumerable<T>! source, int count) -> System.Collections.Generic.IEnumerable<T>!
624+
static MoreLinq.MoreEnumerable.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
622625
static MoreLinq.MoreEnumerable.SkipUntil<TSource>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, bool>! predicate) -> System.Collections.Generic.IEnumerable<TSource>!
623626
static MoreLinq.MoreEnumerable.Slice<T>(this System.Collections.Generic.IEnumerable<T>! sequence, int startIndex, int count) -> System.Collections.Generic.IEnumerable<T>!
624627
static MoreLinq.MoreEnumerable.SortedMerge<TSource>(this System.Collections.Generic.IEnumerable<TSource>! source, MoreLinq.OrderByDirection direction, params System.Collections.Generic.IEnumerable<TSource>![]! otherSequences) -> System.Collections.Generic.IEnumerable<TSource>!
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
#nullable enable
2+
MoreLinq.Extensions.SkipLastWhileExtension
3+
static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
4+
static MoreLinq.MoreEnumerable.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
#nullable enable
2+
MoreLinq.Extensions.SkipLastWhileExtension
3+
static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
4+
static MoreLinq.MoreEnumerable.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!

MoreLinq/SkipLastWhile.cs

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#region License and Terms
2+
// MoreLINQ - Extensions to LINQ to Objects
3+
// Copyright (c) 2024 Andy Romero (armorynode). All rights reserved.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
#endregion
17+
18+
namespace MoreLinq
19+
{
20+
using System;
21+
using System.Collections.Generic;
22+
23+
static partial class MoreEnumerable
24+
{
25+
/// <summary>
26+
/// Removes elements from the end of a sequence as long as a specified condition is true.
27+
/// </summary>
28+
/// <typeparam name="T">Type of the source sequence.</typeparam>
29+
/// <param name="source">The source sequence.</param>
30+
/// <param name="predicate">The predicate to use to remove items from the tail of the sequence.</param>
31+
/// <returns>
32+
/// An <see cref="IEnumerable{T}"/> containing the source sequence elements except for the bypassed ones at the end.
33+
/// </returns>
34+
/// <exception cref="ArgumentNullException">The source sequence is <see langword="null"/>.</exception>
35+
/// <exception cref="ArgumentNullException">The predicate is <see langword="null"/>.</exception>
36+
/// <remarks>
37+
/// This operator uses deferred execution and streams its results. At any given time, it
38+
/// will buffer as many consecutive elements as satisfied by <paramref name="predicate"/>.
39+
/// </remarks>
40+
41+
public static IEnumerable<T> SkipLastWhile<T>(this IEnumerable<T> source, Func<T, bool> predicate)
42+
{
43+
if (source == null) throw new ArgumentNullException(nameof(source));
44+
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
45+
46+
return source.TryAsListLike() switch
47+
{
48+
{ } list => IterateList(list, predicate),
49+
_ => IterateSequence(source, predicate),
50+
};
51+
52+
static IEnumerable<T> IterateList(ListLike<T> list, Func<T, bool> predicate)
53+
{
54+
var i = list.Count - 1;
55+
while (i >= 0 && predicate(list[i]))
56+
{
57+
i--;
58+
}
59+
60+
for (var j = 0; j <= i; j++)
61+
{
62+
yield return list[j];
63+
}
64+
}
65+
66+
static IEnumerable<T> IterateSequence(IEnumerable<T> source, Func<T, bool> predicate)
67+
{
68+
Queue<T>? queue = null;
69+
foreach (var item in source)
70+
{
71+
if (predicate(item))
72+
{
73+
queue ??= new Queue<T>();
74+
queue.Enqueue(item);
75+
}
76+
else
77+
{
78+
while (queue?.Count > 0)
79+
{
80+
yield return queue.Dequeue();
81+
}
82+
yield return item;
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,10 @@ This method has 2 overloads.
576576

577577
Bypasses a specified number of elements at the end of the sequence.
578578

579+
### SkipLastWhile
580+
581+
Removes elements from the end of a sequence as long as a specified condition is true.
582+
579583
### SkipUntil
580584

581585
Skips items from the input sequence until the given predicate returns true

bld/Copyright.props

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
Portions &#169; 2016 Andreas Gullberg Larsen, Leandro F. Vieira (leandromoh).
99
Portions &#169; 2017 Jonas Nyrup (jnyrup).
1010
Portions &#169; 2023 Julien Aspirot (julienasp).
11+
Portions &#169; 2024 Andy Romero (armorynode).
1112
Portions &#169; Microsoft. All rights reserved.
1213
</Copyright>
1314
</PropertyGroup>

0 commit comments

Comments
 (0)