Skip to content

Commit

Permalink
Add "SkipLastWhile" extension
Browse files Browse the repository at this point in the history
This is a squashed merge of PR #1085 that closes #1036.
---------

Co-authored-by: Atif Aziz <code@raboof.com>
  • Loading branch information
ArmoryNode and atifaziz authored Dec 30, 2024
1 parent 72c3c66 commit cf2b725
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 0 deletions.
101 changes: 101 additions & 0 deletions MoreLinq.Test/SkipLastWhileTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2024 Andy Romero (armorynode). All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace MoreLinq.Test
{
using System.Collections.Generic;
using NUnit.Framework;

[TestFixture]
public class SkipLastWhileTest
{
[Test]
public void IsLazy()
{
_ = new BreakingSequence<object>().SkipLastWhile(BreakingFunc.Of<object, bool>());
}

[TestCase(SourceKind.Sequence)]
[TestCase(SourceKind.BreakingList)]
[TestCase(SourceKind.BreakingReadOnlyList)]
public void PredicateNeverFalse(SourceKind sourceKind)
{
using var sequence = TestingSequence.Of(0, 1, 2, 3, 4);

Assert.That(sequence.ToSourceKind(sourceKind).SkipLastWhile(x => x < 5), Is.Empty);
}

[TestCase(SourceKind.Sequence)]
[TestCase(SourceKind.BreakingList)]
[TestCase(SourceKind.BreakingReadOnlyList)]
public void PredicateNeverTrue(SourceKind sourceKind)
{
using var sequence = TestingSequence.Of(0, 1, 2, 3, 4);

sequence.ToSourceKind(sourceKind)
.SkipLastWhile(x => x == 100)
.AssertSequenceEqual(0, 1, 2, 3, 4);
}

[TestCase(SourceKind.Sequence)]
[TestCase(SourceKind.BreakingList)]
[TestCase(SourceKind.BreakingReadOnlyList)]
public void PredicateBecomesTruePartWay(SourceKind sourceKind)
{
using var sequence = TestingSequence.Of(0, 1, 2, 3, 4);

sequence.ToSourceKind(sourceKind)
.SkipLastWhile(x => x > 2)
.AssertSequenceEqual(0, 1, 2);
}

[TestCase(SourceKind.Sequence)]
[TestCase(SourceKind.BreakingList)]
[TestCase(SourceKind.BreakingReadOnlyList)]
public void NeverEvaluatesPredicateWhenSourceIsEmpty(SourceKind sourceKind)
{
using var sequence = TestingSequence.Of<int>();

Assert.That(sequence.ToSourceKind(sourceKind)
.SkipLastWhile(BreakingFunc.Of<int, bool>()),
Is.Empty);
}

[TestCase(SourceKind.Sequence)]
[TestCase(SourceKind.BreakingList)]
[TestCase(SourceKind.BreakingReadOnlyList)]
public void UsesCollectionCountAtIterationTime(SourceKind sourceKind)
{
var list = new List<int> { 1, 2, 3, 4 };
var result = list.ToSourceKind(sourceKind).SkipLastWhile(x => x > 2);
list.Add(5);
result.AssertSequenceEqual(1, 2);
}

[TestCase(SourceKind.Sequence)]
[TestCase(SourceKind.BreakingList)]
[TestCase(SourceKind.BreakingReadOnlyList)]
public void KeepsNonTrailingItemsThatMatchPredicate(SourceKind sourceKind)
{
using var sequence = TestingSequence.Of(1, 2, 0, 0, 3, 4, 0, 0);

sequence.ToSourceKind(sourceKind)
.SkipLastWhile(x => x == 0)
.AssertSequenceEqual(1, 2, 0, 0, 3, 4);
}
}
}
26 changes: 26 additions & 0 deletions MoreLinq/Extensions.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5460,6 +5460,32 @@ public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)

}

/// <summary><c>SkipLastWhile</c> extension.</summary>

[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
public static partial class SkipLastWhileExtension
{
/// <summary>
/// Removes elements from the end of a sequence as long as a specified condition is true.
/// </summary>
/// <typeparam name="T">Type of the source sequence.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="predicate">The predicate to use to remove items from the tail of the sequence.</param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> containing the source sequence elements except for the bypassed ones at the end.
/// </returns>
/// <exception cref="ArgumentNullException">The source sequence is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">The predicate is <see langword="null"/>.</exception>
/// <remarks>
/// This operator uses deferred execution and streams its results. At any given time, it
/// will buffer as many consecutive elements as satisfied by <paramref name="predicate"/>.
/// </remarks>

public static IEnumerable<T> SkipLastWhile<T>(this IEnumerable<T> source, Func<T, bool> predicate)
=> MoreEnumerable.SkipLastWhile(source, predicate);

}

/// <summary><c>SkipUntil</c> extension.</summary>

[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
Expand Down
1 change: 1 addition & 0 deletions MoreLinq/MoreLinq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
- Sequence
- Shuffle
- SkipLast
- SkipLastWhile
- SkipUntil
- Slice
- SortedMerge
Expand Down
3 changes: 3 additions & 0 deletions MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
#nullable enable
MoreLinq.Extensions.SkipLastWhileExtension
static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
3 changes: 3 additions & 0 deletions MoreLinq/PublicAPI/net8.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#nullable enable
MoreLinq.Extensions.SkipLastWhileExtension
static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.Sequence<T>(T start) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.Sequence<T>(T start, T stop) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.Sequence<T>(T start, T stop, T step) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
3 changes: 3 additions & 0 deletions MoreLinq/PublicAPI/net9.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ MoreLinq.Extensions.ShuffleExtension
MoreLinq.Extensions.SingleExtension
MoreLinq.Extensions.SingleOrDefaultExtension
MoreLinq.Extensions.SkipLastExtension
MoreLinq.Extensions.SkipLastWhileExtension
MoreLinq.Extensions.SkipUntilExtension
MoreLinq.Extensions.SliceExtension
MoreLinq.Extensions.SortedMergeExtension
Expand Down Expand Up @@ -339,6 +340,7 @@ static MoreLinq.Extensions.ShuffleExtension.Shuffle<T>(this System.Collections.G
static MoreLinq.Extensions.SingleExtension.Single<T>(this MoreLinq.IExtremaEnumerable<T>! source) -> T
static MoreLinq.Extensions.SingleOrDefaultExtension.SingleOrDefault<T>(this MoreLinq.IExtremaEnumerable<T>! source) -> T?
static MoreLinq.Extensions.SkipLastExtension.SkipLast<T>(this System.Collections.Generic.IEnumerable<T>! source, int count) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.Extensions.SkipUntilExtension.SkipUntil<TSource>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, bool>! predicate) -> System.Collections.Generic.IEnumerable<TSource>!
static MoreLinq.Extensions.SliceExtension.Slice<T>(this System.Collections.Generic.IEnumerable<T>! sequence, int startIndex, int count) -> System.Collections.Generic.IEnumerable<T>!
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>!
Expand Down Expand Up @@ -619,6 +621,7 @@ static MoreLinq.MoreEnumerable.Shuffle<T>(this System.Collections.Generic.IEnume
static MoreLinq.MoreEnumerable.Single<T>(this MoreLinq.IExtremaEnumerable<T>! source) -> T
static MoreLinq.MoreEnumerable.SingleOrDefault<T>(this MoreLinq.IExtremaEnumerable<T>! source) -> T?
static MoreLinq.MoreEnumerable.SkipLast<T>(System.Collections.Generic.IEnumerable<T>! source, int count) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.SkipUntil<TSource>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, bool>! predicate) -> System.Collections.Generic.IEnumerable<TSource>!
static MoreLinq.MoreEnumerable.Slice<T>(this System.Collections.Generic.IEnumerable<T>! sequence, int startIndex, int count) -> System.Collections.Generic.IEnumerable<T>!
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>!
Expand Down
3 changes: 3 additions & 0 deletions MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
#nullable enable
MoreLinq.Extensions.SkipLastWhileExtension
static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
3 changes: 3 additions & 0 deletions MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
#nullable enable
MoreLinq.Extensions.SkipLastWhileExtension
static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.SkipLastWhile<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Func<T, bool>! predicate) -> System.Collections.Generic.IEnumerable<T>!
88 changes: 88 additions & 0 deletions MoreLinq/SkipLastWhile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2024 Andy Romero (armorynode). All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace MoreLinq
{
using System;
using System.Collections.Generic;

static partial class MoreEnumerable
{
/// <summary>
/// Removes elements from the end of a sequence as long as a specified condition is true.
/// </summary>
/// <typeparam name="T">Type of the source sequence.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="predicate">The predicate to use to remove items from the tail of the sequence.</param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> containing the source sequence elements except for the bypassed ones at the end.
/// </returns>
/// <exception cref="ArgumentNullException">The source sequence is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">The predicate is <see langword="null"/>.</exception>
/// <remarks>
/// This operator uses deferred execution and streams its results. At any given time, it
/// will buffer as many consecutive elements as satisfied by <paramref name="predicate"/>.
/// </remarks>

public static IEnumerable<T> SkipLastWhile<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));

return source.TryAsListLike() switch
{
{ } list => IterateList(list, predicate),
_ => IterateSequence(source, predicate),
};

static IEnumerable<T> IterateList(ListLike<T> list, Func<T, bool> predicate)
{
var i = list.Count - 1;
while (i >= 0 && predicate(list[i]))
{
i--;
}

for (var j = 0; j <= i; j++)
{
yield return list[j];
}
}

static IEnumerable<T> IterateSequence(IEnumerable<T> source, Func<T, bool> predicate)
{
Queue<T>? queue = null;
foreach (var item in source)
{
if (predicate(item))
{
queue ??= new Queue<T>();
queue.Enqueue(item);
}
else
{
while (queue?.Count > 0)
{
yield return queue.Dequeue();
}
yield return item;
}
}
}
}
}
}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,10 @@ This method has 2 overloads.

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

### SkipLastWhile

Removes elements from the end of a sequence as long as a specified condition is true.

### SkipUntil

Skips items from the input sequence until the given predicate returns true
Expand Down
1 change: 1 addition & 0 deletions bld/Copyright.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Portions &#169; 2016 Andreas Gullberg Larsen, Leandro F. Vieira (leandromoh).
Portions &#169; 2017 Jonas Nyrup (jnyrup).
Portions &#169; 2023 Julien Aspirot (julienasp).
Portions &#169; 2024 Andy Romero (armorynode).
Portions &#169; Microsoft. All rights reserved.
</Copyright>
</PropertyGroup>
Expand Down

0 comments on commit cf2b725

Please sign in to comment.