From 133ca2592280ee646bba09154f259177a4eecac4 Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:53:45 -0700 Subject: [PATCH 01/15] Added `SkipLastWhile` extension method and associated tests #1036 --- MoreLinq.Test/SkipLastWhileTest.cs | 96 +++++++++++++++++++ MoreLinq/Extensions.g.cs | 22 +++++ MoreLinq/MoreLinq.csproj | 1 + .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 3 + .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 3 + .../netstandard2.0/PublicAPI.Unshipped.txt | 3 + .../netstandard2.1/PublicAPI.Unshipped.txt | 3 + MoreLinq/SkipLastWhile.cs | 62 ++++++++++++ README.md | 4 + bld/Copyright.props | 1 + 10 files changed, 198 insertions(+) create mode 100644 MoreLinq.Test/SkipLastWhileTest.cs create mode 100644 MoreLinq/SkipLastWhile.cs diff --git a/MoreLinq.Test/SkipLastWhileTest.cs b/MoreLinq.Test/SkipLastWhileTest.cs new file mode 100644 index 000000000..6137b46be --- /dev/null +++ b/MoreLinq.Test/SkipLastWhileTest.cs @@ -0,0 +1,96 @@ +#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; + using static MoreLinq.Extensions.SkipLastWhileExtension; + + [TestFixture] + public class SkipLastWhileTest + { + [Test] + public void SkipLastWhilePredicateNeverFalse() + { + using var sequence = TestingSequence.Of(0, 1, 2, 3, 4); + + sequence.SkipLastWhile(x => x < 5) + .AssertSequenceEqual(Enumerable.Empty()); + } + + [Test] + public void SkipLastWhilePredicateNeverTrue() + { + using var sequence = TestingSequence.Of(0, 1, 2, 3, 4); + + sequence.SkipLastWhile(x => x == 100) + .AssertSequenceEqual(0, 1, 2, 3, 4); + } + + [Test] + public void SkipLastWhilePredicateBecomesTrueHalfWay() + { + using var sequence = TestingSequence.Of(0, 1, 2, 3, 4); + + sequence.SkipLastWhile(x => x > 2) + .AssertSequenceEqual(0, 1, 2); + } + + [Test] + public void SkipLastWhileReturnsEmptySequenceWhenSourceIsEmpty() + { + using var sequence = TestingSequence.Of(); + + sequence.SkipLastWhile(_ => true) + .AssertSequenceEqual(Enumerable.Empty()); + } + + [Test] + public void SkipLastWhileReturnsEntireSequenceWhenPredicateIsAlwaysFalse() + { + using var sequence = TestingSequence.Of(0, 0, 0, 0); + + sequence.SkipLastWhile(_ => false) + .AssertSequenceEqual(0, 0, 0, 0); + } + + [Test] + public void SkipLastWhileEvaluatesSourceLazily() + { + _ = new BreakingSequence().SkipLastWhile(_ => true); + } + + [Test] + public void SkipLastWhileEvaluatesPredicateLazily() + { + using var sequence = TestingSequence.Of(0, 1, 2, 3, 4); + + sequence.SkipLastWhile(x => 1 / x != 1) + .AssertSequenceEqual(0, 1); + } + + [Test] + public void SkipLastWhileUsesCollectionCountAtIterationTime() + { + var list = new List { 1, 2, 3, 4 }; + var result = list.SkipLastWhile(x => x > 2); + list.Add(5); + result.AssertSequenceEqual(1, 2); + } + } +} diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index 81e42062e..189af7ee4 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -5452,6 +5452,28 @@ public static IEnumerable SkipLast(this IEnumerable source, int count) } + /// SkipLastWhile extension. + + [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] + public static partial class SkipLastWhileExtension + { + /// + /// Removes elements from the end of a sequence as long as a specified condition is true. + /// + /// Type of the source sequence + /// The source sequence. + /// The predicate to use to remove items from the tail of the sequence. + /// + /// An containing the source sequence elements except for the bypassed ones at the end. + /// + /// The source sequence is null. + /// The predicate is null. + + public static IEnumerable SkipLastWhile(this IEnumerable source, Func predicate) + => MoreEnumerable.SkipLastWhile(source, predicate); + + } + /// SkipUntil extension. [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index 4da96aafa..ff8760e9b 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -86,6 +86,7 @@ - Sequence - Shuffle - SkipLast + - SkipLastWhile - SkipUntil - Slice - SortedMerge diff --git a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt index 7dc5c5811..6adcc5bb5 100644 --- a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +MoreLinq.Extensions.SkipLastWhileExtension +static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.SkipLastWhile(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 5b1195af3..bab15dae1 100644 --- a/MoreLinq/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -1,4 +1,7 @@ #nullable enable +MoreLinq.Extensions.SkipLastWhileExtension +static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Sequence(T start) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Sequence(T start, T stop) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Sequence(T start, T stop, T step) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.SkipLastWhile(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 7dc5c5811..6adcc5bb5 100644 --- a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +MoreLinq.Extensions.SkipLastWhileExtension +static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.SkipLastWhile(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt index 7dc5c5811..6adcc5bb5 100644 --- a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +MoreLinq.Extensions.SkipLastWhileExtension +static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.SkipLastWhile(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/SkipLastWhile.cs b/MoreLinq/SkipLastWhile.cs new file mode 100644 index 000000000..3069a047e --- /dev/null +++ b/MoreLinq/SkipLastWhile.cs @@ -0,0 +1,62 @@ +#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; + using System.Linq; + + static partial class MoreEnumerable + { + /// + /// Removes elements from the end of a sequence as long as a specified condition is true. + /// + /// Type of the source sequence + /// The source sequence. + /// The predicate to use to remove items from the tail of the sequence. + /// + /// An containing the source sequence elements except for the bypassed ones at the end. + /// + /// The source sequence is null. + /// The predicate is null. + + public static IEnumerable SkipLastWhile(this IEnumerable source, Func predicate) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); + + return _(source, predicate); + + static IEnumerable _(IEnumerable source, Func predicate) + { + var list = source.ToArray(); + int tailIndex; + for (tailIndex = list.Length - 1; tailIndex >= 0; tailIndex--) + { + if (!predicate(list[tailIndex])) + break; + } + + for (var returnIndex = 0; returnIndex <= tailIndex; returnIndex++) + { + yield return list[returnIndex]; + } + } + } + } +} diff --git a/README.md b/README.md index 2a897f211..9ad54b459 100644 --- a/README.md +++ b/README.md @@ -576,6 +576,10 @@ This method has 2 overloads. Bypasses a specified number of elements at the end of the sequence. +### SkipLastWhile + +Skips items starting from the end of the input sequence until the given predicate returns true. + ### SkipUntil Skips items from the input sequence until the given predicate returns true diff --git a/bld/Copyright.props b/bld/Copyright.props index 96a381457..d37246fe3 100644 --- a/bld/Copyright.props +++ b/bld/Copyright.props @@ -8,6 +8,7 @@ Portions © 2016 Andreas Gullberg Larsen, Leandro F. Vieira (leandromoh). Portions © 2017 Jonas Nyrup (jnyrup). Portions © 2023 Julien Aspirot (julienasp). + Portions © 2024 Andy Romero (armorynode). Portions © Microsoft. All rights reserved. From 3208017abadc222be210392edbe789d5e2741e5f Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:01:22 -0700 Subject: [PATCH 02/15] Added missing tests --- MoreLinq.Test/SkipLastWhileTest.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/MoreLinq.Test/SkipLastWhileTest.cs b/MoreLinq.Test/SkipLastWhileTest.cs index 6137b46be..89e5dfd6f 100644 --- a/MoreLinq.Test/SkipLastWhileTest.cs +++ b/MoreLinq.Test/SkipLastWhileTest.cs @@ -17,6 +17,7 @@ namespace MoreLinq.Test { + using System; using System.Collections.Generic; using NUnit.Framework; using static MoreLinq.Extensions.SkipLastWhileExtension; @@ -92,5 +93,19 @@ public void SkipLastWhileUsesCollectionCountAtIterationTime() list.Add(5); result.AssertSequenceEqual(1, 2); } + + [Test] + public void SkipLastWhileThrowsArgumentNullExceptionWhenSourceIsNull() + { + IEnumerable source = null!; + _ = Assert.Throws(() => source.SkipLastWhile(_ => true)); + } + + [Test] + public void SkipLstWhileThrowsArgumentNullExceptionWhenPredicateIsNull() + { + Func predicate = null!; + _ = Assert.Throws(() => TestingSequence.Of(0).SkipLastWhile(predicate)); + } } } From 44e54609f5b391564dd6b6351ac340dea43010f4 Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:19:58 -0700 Subject: [PATCH 03/15] Updated tests, fixed typos, and removed unnecessary test cases --- MoreLinq.Test/SkipLastWhileTest.cs | 41 +++--------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/MoreLinq.Test/SkipLastWhileTest.cs b/MoreLinq.Test/SkipLastWhileTest.cs index 89e5dfd6f..56235e746 100644 --- a/MoreLinq.Test/SkipLastWhileTest.cs +++ b/MoreLinq.Test/SkipLastWhileTest.cs @@ -17,10 +17,8 @@ namespace MoreLinq.Test { - using System; using System.Collections.Generic; using NUnit.Framework; - using static MoreLinq.Extensions.SkipLastWhileExtension; [TestFixture] public class SkipLastWhileTest @@ -30,8 +28,7 @@ public void SkipLastWhilePredicateNeverFalse() { using var sequence = TestingSequence.Of(0, 1, 2, 3, 4); - sequence.SkipLastWhile(x => x < 5) - .AssertSequenceEqual(Enumerable.Empty()); + Assert.That(sequence.SkipLastWhile(x => x < 5), Is.Empty); } [Test] @@ -44,7 +41,7 @@ public void SkipLastWhilePredicateNeverTrue() } [Test] - public void SkipLastWhilePredicateBecomesTrueHalfWay() + public void SkipLastWhilePredicateBecomesTruePartWay() { using var sequence = TestingSequence.Of(0, 1, 2, 3, 4); @@ -53,27 +50,11 @@ public void SkipLastWhilePredicateBecomesTrueHalfWay() } [Test] - public void SkipLastWhileReturnsEmptySequenceWhenSourceIsEmpty() + public void SkipLastWhileNeverEvaluatesPredicateWhenSourceIsEmpty() { using var sequence = TestingSequence.Of(); - sequence.SkipLastWhile(_ => true) - .AssertSequenceEqual(Enumerable.Empty()); - } - - [Test] - public void SkipLastWhileReturnsEntireSequenceWhenPredicateIsAlwaysFalse() - { - using var sequence = TestingSequence.Of(0, 0, 0, 0); - - sequence.SkipLastWhile(_ => false) - .AssertSequenceEqual(0, 0, 0, 0); - } - - [Test] - public void SkipLastWhileEvaluatesSourceLazily() - { - _ = new BreakingSequence().SkipLastWhile(_ => true); + Assert.That(sequence.SkipLastWhile(BreakingFunc.Of()), Is.Empty); } [Test] @@ -93,19 +74,5 @@ public void SkipLastWhileUsesCollectionCountAtIterationTime() list.Add(5); result.AssertSequenceEqual(1, 2); } - - [Test] - public void SkipLastWhileThrowsArgumentNullExceptionWhenSourceIsNull() - { - IEnumerable source = null!; - _ = Assert.Throws(() => source.SkipLastWhile(_ => true)); - } - - [Test] - public void SkipLstWhileThrowsArgumentNullExceptionWhenPredicateIsNull() - { - Func predicate = null!; - _ = Assert.Throws(() => TestingSequence.Of(0).SkipLastWhile(predicate)); - } } } From 26d8d204cc89c7fb7dcb3036c097c0eb82d664fc Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:22:01 -0700 Subject: [PATCH 04/15] Apply suggestions from code review Added missing period and added tags around "null" in docstrings Co-authored-by: Atif Aziz --- MoreLinq/SkipLastWhile.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MoreLinq/SkipLastWhile.cs b/MoreLinq/SkipLastWhile.cs index 3069a047e..ae1a629e1 100644 --- a/MoreLinq/SkipLastWhile.cs +++ b/MoreLinq/SkipLastWhile.cs @@ -26,14 +26,14 @@ static partial class MoreEnumerable /// /// Removes elements from the end of a sequence as long as a specified condition is true. /// - /// Type of the source sequence + /// Type of the source sequence. /// The source sequence. /// The predicate to use to remove items from the tail of the sequence. /// /// An containing the source sequence elements except for the bypassed ones at the end. /// - /// The source sequence is null. - /// The predicate is null. + /// The source sequence is . + /// The predicate is . public static IEnumerable SkipLastWhile(this IEnumerable source, Func predicate) { From 9568d0841feb873e07b4bd026dc26946c434912f Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:27:12 -0700 Subject: [PATCH 05/15] `SkipLastWhile` now uses a `queue` internally instead of copying the sequence to an array --- MoreLinq/SkipLastWhile.cs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/MoreLinq/SkipLastWhile.cs b/MoreLinq/SkipLastWhile.cs index ae1a629e1..20833aa9b 100644 --- a/MoreLinq/SkipLastWhile.cs +++ b/MoreLinq/SkipLastWhile.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Linq; static partial class MoreEnumerable { @@ -44,17 +43,21 @@ public static IEnumerable SkipLastWhile(this IEnumerable source, Func _(IEnumerable source, Func predicate) { - var list = source.ToArray(); - int tailIndex; - for (tailIndex = list.Length - 1; tailIndex >= 0; tailIndex--) + var queue = new Queue(); + foreach (var item in source) { - if (!predicate(list[tailIndex])) - break; - } - - for (var returnIndex = 0; returnIndex <= tailIndex; returnIndex++) - { - yield return list[returnIndex]; + if (predicate(item)) + { + queue.Enqueue(item); + } + else + { + while (queue.Count > 0) + { + yield return queue.Dequeue(); + } + yield return item; + } } } } From 198a0d08e07f6e1ed084ed616795acd40b8fa399 Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:29:25 -0700 Subject: [PATCH 06/15] Removed unnecessary test --- MoreLinq.Test/SkipLastWhileTest.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/MoreLinq.Test/SkipLastWhileTest.cs b/MoreLinq.Test/SkipLastWhileTest.cs index 56235e746..1fffbbdc1 100644 --- a/MoreLinq.Test/SkipLastWhileTest.cs +++ b/MoreLinq.Test/SkipLastWhileTest.cs @@ -57,15 +57,6 @@ public void SkipLastWhileNeverEvaluatesPredicateWhenSourceIsEmpty() Assert.That(sequence.SkipLastWhile(BreakingFunc.Of()), Is.Empty); } - [Test] - public void SkipLastWhileEvaluatesPredicateLazily() - { - using var sequence = TestingSequence.Of(0, 1, 2, 3, 4); - - sequence.SkipLastWhile(x => 1 / x != 1) - .AssertSequenceEqual(0, 1); - } - [Test] public void SkipLastWhileUsesCollectionCountAtIterationTime() { From a9c03ce3093ebb60a339ed468297fd9776134b27 Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:56:04 -0800 Subject: [PATCH 07/15] Updated `SkipLastWhile` to include optimizations for list types --- MoreLinq/Extensions.g.cs | 10 +++++++--- MoreLinq/SkipLastWhile.cs | 28 ++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index 189af7ee4..201d885f5 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -5460,14 +5460,18 @@ public static partial class SkipLastWhileExtension /// /// Removes elements from the end of a sequence as long as a specified condition is true. /// - /// Type of the source sequence + /// Type of the source sequence. /// The source sequence. /// The predicate to use to remove items from the tail of the sequence. /// /// An containing the source sequence elements except for the bypassed ones at the end. /// - /// The source sequence is null. - /// The predicate is null. + /// The source sequence is . + /// The predicate is . + /// + /// This operator uses deferred execution and streams its results. At any given time, it + /// will buffer as many consecutive elements as satisfied by . + /// public static IEnumerable SkipLastWhile(this IEnumerable source, Func predicate) => MoreEnumerable.SkipLastWhile(source, predicate); diff --git a/MoreLinq/SkipLastWhile.cs b/MoreLinq/SkipLastWhile.cs index 20833aa9b..301899871 100644 --- a/MoreLinq/SkipLastWhile.cs +++ b/MoreLinq/SkipLastWhile.cs @@ -39,20 +39,40 @@ public static IEnumerable SkipLastWhile(this IEnumerable source, Func source, + { } list => IterateList(list, predicate), + _ => IterateSequence(source, predicate), + }; + + static IEnumerable IterateList(ListLike list, Func 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 _(IEnumerable source, Func predicate) + static IEnumerable IterateSequence(IEnumerable source, Func predicate) { - var queue = new Queue(); + Queue? queue = null; foreach (var item in source) { if (predicate(item)) { + queue ??= new Queue(); queue.Enqueue(item); } else { - while (queue.Count > 0) + while (queue?.Count > 0) { yield return queue.Dequeue(); } From 40e0c67fdf23af1c827f273af396b198d72e391b Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:56:35 -0800 Subject: [PATCH 08/15] Added "remarks" section to `SkipLastWhile` and updated README --- MoreLinq/SkipLastWhile.cs | 4 ++++ README.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/MoreLinq/SkipLastWhile.cs b/MoreLinq/SkipLastWhile.cs index 301899871..18a992f2c 100644 --- a/MoreLinq/SkipLastWhile.cs +++ b/MoreLinq/SkipLastWhile.cs @@ -33,6 +33,10 @@ static partial class MoreEnumerable /// /// The source sequence is . /// The predicate is . + /// + /// This operator uses deferred execution and streams its results. At any given time, it + /// will buffer as many consecutive elements as satisfied by . + /// public static IEnumerable SkipLastWhile(this IEnumerable source, Func predicate) { diff --git a/README.md b/README.md index 9ad54b459..f15277b21 100644 --- a/README.md +++ b/README.md @@ -578,7 +578,7 @@ Bypasses a specified number of elements at the end of the sequence. ### SkipLastWhile -Skips items starting from the end of the input sequence until the given predicate returns true. +Removes elements from the end of a sequence as long as a specified condition is true. ### SkipUntil From 7ca9bd92ce8f7fc1971c7c23d44a961673e20e62 Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:42:59 -0800 Subject: [PATCH 09/15] Renamed tests and added a new one --- MoreLinq.Test/SkipLastWhileTest.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/MoreLinq.Test/SkipLastWhileTest.cs b/MoreLinq.Test/SkipLastWhileTest.cs index 1fffbbdc1..aca2e3545 100644 --- a/MoreLinq.Test/SkipLastWhileTest.cs +++ b/MoreLinq.Test/SkipLastWhileTest.cs @@ -24,7 +24,13 @@ namespace MoreLinq.Test public class SkipLastWhileTest { [Test] - public void SkipLastWhilePredicateNeverFalse() + public void IsLazy() + { + _ = new BreakingSequence().SkipLastWhile(BreakingFunc.Of()); + } + + [Test] + public void PredicateNeverFalse() { using var sequence = TestingSequence.Of(0, 1, 2, 3, 4); @@ -32,7 +38,7 @@ public void SkipLastWhilePredicateNeverFalse() } [Test] - public void SkipLastWhilePredicateNeverTrue() + public void PredicateNeverTrue() { using var sequence = TestingSequence.Of(0, 1, 2, 3, 4); @@ -41,7 +47,7 @@ public void SkipLastWhilePredicateNeverTrue() } [Test] - public void SkipLastWhilePredicateBecomesTruePartWay() + public void PredicateBecomesTruePartWay() { using var sequence = TestingSequence.Of(0, 1, 2, 3, 4); @@ -50,7 +56,7 @@ public void SkipLastWhilePredicateBecomesTruePartWay() } [Test] - public void SkipLastWhileNeverEvaluatesPredicateWhenSourceIsEmpty() + public void NeverEvaluatesPredicateWhenSourceIsEmpty() { using var sequence = TestingSequence.Of(); @@ -58,7 +64,7 @@ public void SkipLastWhileNeverEvaluatesPredicateWhenSourceIsEmpty() } [Test] - public void SkipLastWhileUsesCollectionCountAtIterationTime() + public void UsesCollectionCountAtIterationTime() { var list = new List { 1, 2, 3, 4 }; var result = list.SkipLastWhile(x => x > 2); From 9cc785ff792f61d9ad32c07186bf32080034430e Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Tue, 5 Nov 2024 08:44:17 -0800 Subject: [PATCH 10/15] Adds tests for different collection types --- MoreLinq.Test/SkipLastWhileTest.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MoreLinq.Test/SkipLastWhileTest.cs b/MoreLinq.Test/SkipLastWhileTest.cs index aca2e3545..59ffd6cdc 100644 --- a/MoreLinq.Test/SkipLastWhileTest.cs +++ b/MoreLinq.Test/SkipLastWhileTest.cs @@ -71,5 +71,16 @@ public void UsesCollectionCountAtIterationTime() list.Add(5); result.AssertSequenceEqual(1, 2); } + + [TestCase(SourceKind.Sequence)] + [TestCase(SourceKind.BreakingList)] + [TestCase(SourceKind.BreakingReadOnlyList)] + public void OptimizedForCollections(SourceKind sourceKind) + { + var sequence = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.ToSourceKind(sourceKind); + + sequence.SkipLastWhile(x => x > 7) + .AssertSequenceEqual(1, 2, 3, 4, 5, 6, 7); + } } } From 292a1eb58a0b5f269c26fbbb656e2a4f061a7a81 Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:53:11 -0800 Subject: [PATCH 11/15] Added different collection test cases to `SkipLastWhile` tests --- MoreLinq.Test/SkipLastWhileTest.cs | 34 +++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/MoreLinq.Test/SkipLastWhileTest.cs b/MoreLinq.Test/SkipLastWhileTest.cs index 59ffd6cdc..68199ef76 100644 --- a/MoreLinq.Test/SkipLastWhileTest.cs +++ b/MoreLinq.Test/SkipLastWhileTest.cs @@ -29,29 +29,37 @@ public void IsLazy() _ = new BreakingSequence().SkipLastWhile(BreakingFunc.Of()); } - [Test] - public void PredicateNeverFalse() + [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.SkipLastWhile(x => x < 5), Is.Empty); + Assert.That(sequence.ToSourceKind(sourceKind).SkipLastWhile(x => x < 5), Is.Empty); } - [Test] - public void PredicateNeverTrue() + [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.SkipLastWhile(x => x == 100) + sequence.ToSourceKind(sourceKind) + .SkipLastWhile(x => x == 100) .AssertSequenceEqual(0, 1, 2, 3, 4); } - [Test] - public void PredicateBecomesTruePartWay() + [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.SkipLastWhile(x => x > 2) + sequence.ToSourceKind(sourceKind) + .SkipLastWhile(x => x > 2) .AssertSequenceEqual(0, 1, 2); } @@ -63,11 +71,13 @@ public void NeverEvaluatesPredicateWhenSourceIsEmpty() Assert.That(sequence.SkipLastWhile(BreakingFunc.Of()), Is.Empty); } - [Test] - public void UsesCollectionCountAtIterationTime() + [TestCase(SourceKind.Sequence)] + [TestCase(SourceKind.BreakingList)] + [TestCase(SourceKind.BreakingReadOnlyList)] + public void UsesCollectionCountAtIterationTime(SourceKind sourceKind) { var list = new List { 1, 2, 3, 4 }; - var result = list.SkipLastWhile(x => x > 2); + var result = list.ToSourceKind(sourceKind).SkipLastWhile(x => x > 2); list.Add(5); result.AssertSequenceEqual(1, 2); } From 453b0eda0316c9944d9014758eb9e934008ffc7d Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Wed, 6 Nov 2024 15:48:37 -0800 Subject: [PATCH 12/15] Updated test to use `SourceKind` and added a missing test scenario --- MoreLinq.Test/SkipLastWhileTest.cs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/MoreLinq.Test/SkipLastWhileTest.cs b/MoreLinq.Test/SkipLastWhileTest.cs index 68199ef76..8c64d32b4 100644 --- a/MoreLinq.Test/SkipLastWhileTest.cs +++ b/MoreLinq.Test/SkipLastWhileTest.cs @@ -63,12 +63,16 @@ public void PredicateBecomesTruePartWay(SourceKind sourceKind) .AssertSequenceEqual(0, 1, 2); } - [Test] - public void NeverEvaluatesPredicateWhenSourceIsEmpty() + [TestCase(SourceKind.Sequence)] + // [TestCase(SourceKind.BreakingList)] + // [TestCase(SourceKind.BreakingReadOnlyList)] + public void NeverEvaluatesPredicateWhenSourceIsEmpty(SourceKind sourceKind) { using var sequence = TestingSequence.Of(); - Assert.That(sequence.SkipLastWhile(BreakingFunc.Of()), Is.Empty); + Assert.That(sequence + .ToSourceKind(sourceKind) + .SkipLastWhile(BreakingFunc.Of()), Is.Empty); } [TestCase(SourceKind.Sequence)] @@ -92,5 +96,17 @@ public void OptimizedForCollections(SourceKind sourceKind) sequence.SkipLastWhile(x => x > 7) .AssertSequenceEqual(1, 2, 3, 4, 5, 6, 7); } + + [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); + } } } From 5ec4179f34925f99c71c989752535611a40c2b79 Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:14:09 -0800 Subject: [PATCH 13/15] Added `SkipLastWhile` to public API for .NET 9 --- MoreLinq/PublicAPI/net9.0/PublicAPI.Unshipped.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MoreLinq/PublicAPI/net9.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/net9.0/PublicAPI.Unshipped.txt index d59ac1739..00c6971c9 100644 --- a/MoreLinq/PublicAPI/net9.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/net9.0/PublicAPI.Unshipped.txt @@ -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 @@ -339,6 +340,7 @@ static MoreLinq.Extensions.ShuffleExtension.Shuffle(this System.Collections.G static MoreLinq.Extensions.SingleExtension.Single(this MoreLinq.IExtremaEnumerable! source) -> T static MoreLinq.Extensions.SingleOrDefaultExtension.SingleOrDefault(this MoreLinq.IExtremaEnumerable! source) -> T? static MoreLinq.Extensions.SkipLastExtension.SkipLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! +static MoreLinq.Extensions.SkipLastWhileExtension.SkipLastWhile(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! static MoreLinq.Extensions.SkipUntilExtension.SkipUntil(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! static MoreLinq.Extensions.SliceExtension.Slice(this System.Collections.Generic.IEnumerable! sequence, int startIndex, int count) -> System.Collections.Generic.IEnumerable! static MoreLinq.Extensions.SortedMergeExtension.SortedMerge(this System.Collections.Generic.IEnumerable! source, MoreLinq.OrderByDirection direction, params System.Collections.Generic.IEnumerable![]! otherSequences) -> System.Collections.Generic.IEnumerable! @@ -619,6 +621,7 @@ static MoreLinq.MoreEnumerable.Shuffle(this System.Collections.Generic.IEnume static MoreLinq.MoreEnumerable.Single(this MoreLinq.IExtremaEnumerable! source) -> T static MoreLinq.MoreEnumerable.SingleOrDefault(this MoreLinq.IExtremaEnumerable! source) -> T? static MoreLinq.MoreEnumerable.SkipLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.SkipLastWhile(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.SkipUntil(this System.Collections.Generic.IEnumerable! source, System.Func! predicate) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Slice(this System.Collections.Generic.IEnumerable! sequence, int startIndex, int count) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.SortedMerge(this System.Collections.Generic.IEnumerable! source, MoreLinq.OrderByDirection direction, params System.Collections.Generic.IEnumerable![]! otherSequences) -> System.Collections.Generic.IEnumerable! From d5607a8d0281a670caec4d6d405ea89b8374620a Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:14:52 -0800 Subject: [PATCH 14/15] Removed switch condition and uncommented tests --- MoreLinq.Test/SkipLastWhileTest.cs | 8 ++++---- MoreLinq/SkipLastWhile.cs | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/MoreLinq.Test/SkipLastWhileTest.cs b/MoreLinq.Test/SkipLastWhileTest.cs index 8c64d32b4..4805c74b9 100644 --- a/MoreLinq.Test/SkipLastWhileTest.cs +++ b/MoreLinq.Test/SkipLastWhileTest.cs @@ -64,8 +64,8 @@ public void PredicateBecomesTruePartWay(SourceKind sourceKind) } [TestCase(SourceKind.Sequence)] - // [TestCase(SourceKind.BreakingList)] - // [TestCase(SourceKind.BreakingReadOnlyList)] + [TestCase(SourceKind.BreakingList)] + [TestCase(SourceKind.BreakingReadOnlyList)] public void NeverEvaluatesPredicateWhenSourceIsEmpty(SourceKind sourceKind) { using var sequence = TestingSequence.Of(); @@ -98,8 +98,8 @@ public void OptimizedForCollections(SourceKind sourceKind) } [TestCase(SourceKind.Sequence)] - // [TestCase(SourceKind.BreakingList)] - // [TestCase(SourceKind.BreakingReadOnlyList)] + [TestCase(SourceKind.BreakingList)] + [TestCase(SourceKind.BreakingReadOnlyList)] public void KeepsNonTrailingItemsThatMatchPredicate(SourceKind sourceKind) { using var sequence = TestingSequence.Of(1, 2, 0, 0, 3, 4, 0, 0); diff --git a/MoreLinq/SkipLastWhile.cs b/MoreLinq/SkipLastWhile.cs index 18a992f2c..215f04755 100644 --- a/MoreLinq/SkipLastWhile.cs +++ b/MoreLinq/SkipLastWhile.cs @@ -45,7 +45,6 @@ public static IEnumerable SkipLastWhile(this IEnumerable source, Func source, { } list => IterateList(list, predicate), _ => IterateSequence(source, predicate), }; From 8a6ac3d7cb54e0aa1b1de40eeb85edc6b5dc80ab Mon Sep 17 00:00:00 2001 From: ArmoryNode <22787155+ArmoryNode@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:32:54 -0800 Subject: [PATCH 15/15] Removed redundant test, fixed code formatting, and removed UTF-8 BOM marker --- MoreLinq.Test/SkipLastWhileTest.cs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/MoreLinq.Test/SkipLastWhileTest.cs b/MoreLinq.Test/SkipLastWhileTest.cs index 4805c74b9..01f045a7b 100644 --- a/MoreLinq.Test/SkipLastWhileTest.cs +++ b/MoreLinq.Test/SkipLastWhileTest.cs @@ -1,4 +1,4 @@ -#region License and Terms +#region License and Terms // MoreLINQ - Extensions to LINQ to Objects // Copyright (c) 2024 Andy Romero (armorynode). All rights reserved. // @@ -70,9 +70,9 @@ public void NeverEvaluatesPredicateWhenSourceIsEmpty(SourceKind sourceKind) { using var sequence = TestingSequence.Of(); - Assert.That(sequence - .ToSourceKind(sourceKind) - .SkipLastWhile(BreakingFunc.Of()), Is.Empty); + Assert.That(sequence.ToSourceKind(sourceKind) + .SkipLastWhile(BreakingFunc.Of()), + Is.Empty); } [TestCase(SourceKind.Sequence)] @@ -86,17 +86,6 @@ public void UsesCollectionCountAtIterationTime(SourceKind sourceKind) result.AssertSequenceEqual(1, 2); } - [TestCase(SourceKind.Sequence)] - [TestCase(SourceKind.BreakingList)] - [TestCase(SourceKind.BreakingReadOnlyList)] - public void OptimizedForCollections(SourceKind sourceKind) - { - var sequence = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.ToSourceKind(sourceKind); - - sequence.SkipLastWhile(x => x > 7) - .AssertSequenceEqual(1, 2, 3, 4, 5, 6, 7); - } - [TestCase(SourceKind.Sequence)] [TestCase(SourceKind.BreakingList)] [TestCase(SourceKind.BreakingReadOnlyList)]