diff --git a/CHANGELOG.md b/CHANGELOG.md index cd5246e..4eed9b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ - X10D: Added `Color.GetClosestConsoleColor()` - X10D: Added `DateTime.GetIso8601WeekOfYear()` and `DateTimeOffset.GetIso8601WeekOfYear()` - X10D: Added `DirectoryInfo.Clear([bool])` +- X10D: Added `IEnumerable.CountWhereNot(Func)` +- X10D: Added `IEnumerable.FirstWhereNot(Func)` +- X10D: Added `IEnumerable.FirstWhereNotOrDefault(Func)` +- X10D: Added `IEnumerable.LastWhereNot(Func)` +- X10D: Added `IEnumerable.LastWhereNotOrDefault(Func)` +- X10D: Added `IEnumerable.WhereNot(Func)` - X10D: Added `IList.RemoveRange(Range)` - X10D: Added `IList.Swap(IList)` (#62) - X10D: Added `Point.IsOnLine(LineF)`, `Point.IsOnLine(PointF, PointF)`, and `Point.IsOnLine(Vector2, Vector2)` diff --git a/X10D.Tests/src/Collections/EnumerableTests.cs b/X10D.Tests/src/Collections/EnumerableTests.cs index 533a00e..1631040 100644 --- a/X10D.Tests/src/Collections/EnumerableTests.cs +++ b/X10D.Tests/src/Collections/EnumerableTests.cs @@ -1,11 +1,40 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using X10D.Collections; +using X10D.Core; namespace X10D.Tests.Collections; [TestClass] public class EnumerableTests { + [TestMethod] + public void CountWhereNot_ShouldReturnCorrectCount_GivenSequence() + { + var enumerable = new[] {2, 4, 6, 7, 8, 9, 10}; + int count = enumerable.CountWhereNot(x => x % 2 == 0); + Assert.AreEqual(2, count); + } + + [TestMethod] + public void CountWhereNot_ShouldThrowArgumentNullException_GivenNullSource() + { + Assert.ThrowsException(() => ((IEnumerable?)null)!.CountWhereNot(x => x % 2 == 0)); + } + + [TestMethod] + public void CountWhereNot_ShouldThrowOverflowException_GivenLargeSource() + { + IEnumerable GetValues() + { + while (true) + { + yield return 1; + } + } + + Assert.ThrowsException(() => GetValues().CountWhereNot(x => x % 2 == 0)); + } + [TestMethod] public void DisposeAll_ShouldDispose_GivenCollection() { @@ -36,6 +65,72 @@ public class EnumerableTests await Assert.ThrowsExceptionAsync(async () => await collection!.DisposeAllAsync()); } + [TestMethod] + public void FirstWhereNot_ShouldReturnCorrectElements_GivenSequence() + { + var enumerable = new[] {2, 4, 6, 7, 8, 9, 10}; + int result = enumerable.FirstWhereNot(x => x % 2 == 0); + Assert.AreEqual(7, result); + } + + [TestMethod] + public void FirstWhereNot_ShouldThrowArgumentNullException_GivenNullSource() + { + Assert.ThrowsException(() => ((IEnumerable?)null)!.FirstWhereNot(x => x % 2 == 0)); + } + + [TestMethod] + public void FirstWhereNot_ShouldThrowArgumentNullException_GivenNullPredicate() + { + Assert.ThrowsException(() => Array.Empty().FirstWhereNotOrDefault(null!)); + } + + [TestMethod] + public void FirstWhereNot_ShouldThrowInvalidOperationException_GivenEmptySource() + { + Assert.ThrowsException(() => Array.Empty().FirstWhereNot(x => x % 2 == 0)); + } + + [TestMethod] + public void FirstWhereNot_ShouldThrowInvalidOperationException_GivenSourceWithNoMatchingElements() + { + Assert.ThrowsException(() => 2.AsArrayValue().FirstWhereNot(x => x % 2 == 0)); + } + + [TestMethod] + public void FirstWhereNotOrDefault_ShouldReturnCorrectElements_GivenSequence() + { + var enumerable = new[] {2, 4, 6, 7, 8, 9, 10}; + int result = enumerable.FirstWhereNotOrDefault(x => x % 2 == 0); + Assert.AreEqual(7, result); + } + + [TestMethod] + public void FirstWhereNotOrDefault_ShouldThrowArgumentNullException_GivenNullSource() + { + Assert.ThrowsException(() => ((IEnumerable?)null)!.FirstWhereNotOrDefault(x => x % 2 == 0)); + } + + [TestMethod] + public void FirstWhereNotOrDefault_ShouldThrowArgumentNullException_GivenNullPredicate() + { + Assert.ThrowsException(() => Array.Empty().FirstWhereNotOrDefault(null!)); + } + + [TestMethod] + public void FirstWhereNotOrDefault_ShouldReturnDefault_GivenEmptySource() + { + int result = Array.Empty().FirstWhereNotOrDefault(x => x % 2 == 0); + Assert.AreEqual(default, result); + } + + [TestMethod] + public void FirstWhereNotOrDefault_ShouldReturnDefault_GivenSourceWithNoMatchingElements() + { + int result = 2.AsArrayValue().FirstWhereNotOrDefault(x => x % 2 == 0); + Assert.AreEqual(default, result); + } + [TestMethod] public void For_ShouldTransform_GivenTransformationDelegate() { @@ -94,6 +189,72 @@ public class EnumerableTests Assert.ThrowsException(() => source.ForEach(null!)); } + [TestMethod] + public void LastWhereNot_ShouldReturnCorrectElements_GivenSequence() + { + var enumerable = new[] {2, 4, 6, 7, 8, 9, 10}; + int result = enumerable.LastWhereNot(x => x % 2 == 0); + Assert.AreEqual(9, result); + } + + [TestMethod] + public void LastWhereNot_ShouldThrowArgumentNullException_GivenNullSource() + { + Assert.ThrowsException(() => ((IEnumerable?)null)!.LastWhereNot(x => x % 2 == 0)); + } + + [TestMethod] + public void LastWhereNot_ShouldThrowArgumentNullException_GivenNullPredicate() + { + Assert.ThrowsException(() => Array.Empty().LastWhereNot(null!)); + } + + [TestMethod] + public void LastWhereNot_ShouldThrowInvalidOperationException_GivenEmptySource() + { + Assert.ThrowsException(() => Array.Empty().LastWhereNot(x => x % 2 == 0)); + } + + [TestMethod] + public void LastWhereNot_ShouldThrowInvalidOperationException_GivenSourceWithNoMatchingElements() + { + Assert.ThrowsException(() => 2.AsArrayValue().LastWhereNot(x => x % 2 == 0)); + } + + [TestMethod] + public void LastWhereNotOrDefault_ShouldReturnCorrectElements_GivenSequence() + { + var enumerable = new[] {2, 4, 6, 7, 8, 9, 10}; + int result = enumerable.LastWhereNotOrDefault(x => x % 2 == 0); + Assert.AreEqual(9, result); + } + + [TestMethod] + public void LastWhereNotOrDefault_ShouldThrowArgumentNullException_GivenNullSource() + { + Assert.ThrowsException(() => ((IEnumerable?)null)!.LastWhereNotOrDefault(x => x % 2 == 0)); + } + + [TestMethod] + public void LastWhereNotOrDefault_ShouldThrowArgumentNullException_GivenNullPredicate() + { + Assert.ThrowsException(() => Array.Empty().LastWhereNotOrDefault(null!)); + } + + [TestMethod] + public void LastWhereNotOrDefault_ShouldReturnDefault_GivenEmptySource() + { + int result = Array.Empty().LastWhereNotOrDefault(x => x % 2 == 0); + Assert.AreEqual(default, result); + } + + [TestMethod] + public void LastWhereNotOrDefault_ShouldReturnDefault_GivenSourceWithNoMatchingElements() + { + int result = 2.AsArrayValue().LastWhereNotOrDefault(x => x % 2 == 0); + Assert.AreEqual(default, result); + } + [TestMethod] public void Shuffled_ShouldThrow_GivenNull() { @@ -112,6 +273,20 @@ public class EnumerableTests CollectionAssert.AreNotEqual(array, shuffled); } + [TestMethod] + public void WhereNot_ShouldReturnCorrectElements_GivenSequence() + { + var enumerable = new[] {2, 4, 6, 7, 8, 9, 10}; + IEnumerable result = enumerable.WhereNot(x => x % 2 == 0); + CollectionAssert.AreEqual(new[] {7, 9}, result.ToArray()); + } + + [TestMethod] + public void WhereNot_ShouldThrowArgumentNullException_GivenNullSource() + { + Assert.ThrowsException(() => ((IEnumerable?)null)!.WhereNot(x => x % 2 == 0)); + } + private class DummyClass { public int Value { get; set; } diff --git a/X10D/src/Collections/EnumerableExtensions.cs b/X10D/src/Collections/EnumerableExtensions.cs index 4a75370..9747235 100644 --- a/X10D/src/Collections/EnumerableExtensions.cs +++ b/X10D/src/Collections/EnumerableExtensions.cs @@ -7,6 +7,108 @@ namespace X10D.Collections; /// public static class EnumerableExtensions { + /// + /// Returns a number that represents how many elements in the specified sequence do not satisfy a condition. + /// + /// A sequence that contains elements to be tested and counted. + /// A function to test each element for a condition. + /// The type of the elements of . + /// + /// A number that represents how many elements in the sequence do not satisfy the condition in the + /// function. + /// + /// or is null. + /// + /// The number of elements in is larger than . + /// + [Pure] + public static int CountWhereNot(this IEnumerable source, Func predicate) + { +#if NET6_0 + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(predicate); +#else + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } +#endif + + return source.Count(item => !predicate(item)); + } + + /// + /// Returns the first element in a sequence that does not satisfy a specified condition. + /// + /// An to return an element from. + /// A function to test each element for a condition. + /// The type of the elements in + /// The first element in the sequence that fails the test in the specified predicate function. + /// or is null. + /// + /// No element satisfies the condition in predicate. + /// -or- + /// The source sequence is empty. + /// + [Pure] + public static TSource FirstWhereNot(this IEnumerable source, Func predicate) + { +#if NET6_0 + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(predicate); +#else + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } +#endif + + return source.First(item => !predicate(item)); + } + + /// + /// Returns the first element in a sequence that does not satisfy a specified condition. + /// + /// An to return an element from. + /// A function to test each element for a condition. + /// The type of the elements in + /// + /// if is empty or if no element passes the test specified + /// by ; otherwise, the first element in that fails the test + /// specified by . + /// + /// or is null. + [Pure] + public static TSource? FirstWhereNotOrDefault(this IEnumerable source, Func predicate) + { +#if NET6_0 + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(predicate); +#else + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } +#endif + + return source.FirstOrDefault(item => !predicate(item)); + } + /// /// Performs the specified action on each element of the . /// @@ -128,6 +230,73 @@ public static class EnumerableExtensions } } + /// + /// Returns the last element in a sequence that does not satisfy a specified condition. + /// + /// An to return an element from. + /// A function to test each element for a condition. + /// The type of the elements in + /// The last element in the sequence that fails the test in the specified predicate function. + /// or is null. + /// + /// No element satisfies the condition in predicate. + /// -or- + /// The source sequence is empty. + /// + [Pure] + public static TSource LastWhereNot(this IEnumerable source, Func predicate) + { +#if NET6_0 + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(predicate); +#else + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } +#endif + + return source.Last(item => !predicate(item)); + } + + /// + /// Returns the last element in a sequence that does not satisfy a specified condition. + /// + /// An to return an element from. + /// A function to test each element for a condition. + /// The type of the elements in + /// + /// if is empty or if no element passes the test specified + /// by ; otherwise, the last element in that fails the test + /// specified by . + /// + /// or is null. + [Pure] + public static TSource? LastWhereNotOrDefault(this IEnumerable source, Func predicate) + { +#if NET6_0 + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(predicate); +#else + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } +#endif + + return source.LastOrDefault(item => !predicate(item)); + } + /// /// Reorganizes the elements in an enumerable by implementing a Fisher-Yates shuffle, and returns th shuffled result. /// @@ -152,4 +321,35 @@ public static class EnumerableExtensions list.Shuffle(random); return list.AsReadOnly(); } + + /// + /// Filters a sequence of values based on a predicate, such that all elements in the result do not match the predicate. + /// + /// An to filter. + /// A function to test each element for a condition. + /// The type of the elements of . + /// + /// An that contains elements from the input sequence that do not satisfy the condition. + /// + /// or is null. + [Pure] + public static IEnumerable WhereNot(this IEnumerable source, Func predicate) + { +#if NET6_0 + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(predicate); +#else + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } +#endif + + return source.Where(item => !predicate(item)); + } }