From d0f94a6493183a3daafcc6c3db94cfdf5de70352 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 30 Mar 2023 17:29:54 +0100 Subject: [PATCH] feat: add IEnumerable.Grep() --- CHANGELOG.md | 1 + X10D.Tests/src/Text/EnumerableTests.cs | 57 ++++++++++++++++++++ X10D/src/Text/EnumerableExtensions.cs | 72 ++++++++++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 X10D.Tests/src/Text/EnumerableTests.cs create mode 100644 X10D/src/Text/EnumerableExtensions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7969e28..d88d34e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - X10D: Added `IEnumerable.MinMax()` and `IEnumerable.MinMaxBy()`. (#72) - X10D: Added `IEnumerable.WhereNot(Func)`. - X10D: Added `IEnumerable.WhereNotNull()`. +- X10D: Added `IEnumerable.Grep(string[, bool])`. - X10D: Added `IList.RemoveRange(Range)`. - X10D: Added `IList.Swap(IList)`. (#62) - X10D: Added `IReadOnlyList.IndexOf(T[, int[, int]])`. diff --git a/X10D.Tests/src/Text/EnumerableTests.cs b/X10D.Tests/src/Text/EnumerableTests.cs new file mode 100644 index 0000000..540a121 --- /dev/null +++ b/X10D.Tests/src/Text/EnumerableTests.cs @@ -0,0 +1,57 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class EnumerableTests +{ + [TestMethod] + public void Grep_ShouldFilterCorrectly_GivenPattern() + { + int year = DateTime.Now.Year; + var source = new[] {"Hello", "World", "String 123", $"The year is {year}"}; + var expectedResult = new[] {"String 123", $"The year is {year}"}; + + const string pattern = /*lang=regex*/@"[0-9]+"; + string[] actualResult = source.Grep(pattern).ToArray(); + + CollectionAssert.AreEqual(expectedResult, actualResult); + } + + [TestMethod] + public void Grep_ShouldMatchUpperCase_GivenIgnoreCaseTrue() + { + int year = DateTime.Now.Year; + var source = new[] {"Hello", "WORLD", "String 123", $"The year is {year}"}; + var expectedResult = new[] {"WORLD"}; + + const string pattern = /*lang=regex*/@"world"; + string[] actualResult = source.Grep(pattern, true).ToArray(); + + CollectionAssert.AreEqual(expectedResult, actualResult); + } + + [TestMethod] + public void Grep_ShouldNotMatchUpperCase_GivenIgnoreCaseFalse() + { + int year = DateTime.Now.Year; + var source = new[] {"Hello", "WORLD", "String 123", $"The year is {year}"}; + + const string pattern = /*lang=regex*/@"world"; + string[] actualResult = source.Grep(pattern, false).ToArray(); + + Assert.AreEqual(0, actualResult.Length); + } + + [TestMethod] + public void Grep_ShouldYieldNoElements_GivenNoMatchingStrings() + { + var source = new[] {"Hello", "World", "String"}; + + const string pattern = /*lang=regex*/@"[0-9]+"; + string[] actualResult = source.Grep(pattern).ToArray(); + + Assert.AreEqual(0, actualResult.Length); + } +} diff --git a/X10D/src/Text/EnumerableExtensions.cs b/X10D/src/Text/EnumerableExtensions.cs new file mode 100644 index 0000000..01f5038 --- /dev/null +++ b/X10D/src/Text/EnumerableExtensions.cs @@ -0,0 +1,72 @@ +using System.Text.RegularExpressions; + +namespace X10D.Text; + +/// +/// Text-related extension methods for . +/// +public static class EnumerableExtensions +{ + /// + /// Filters a sequence of strings by regular expression. + /// + /// The sequence of strings to filter. + /// The regular expression pattern to use for matching. + /// The filtered sequence. + public static IEnumerable Grep(this IEnumerable source, string pattern) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(pattern); +#else + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (pattern is null) + { + throw new ArgumentNullException(nameof(pattern)); + } +#endif + + return Grep(source, pattern, false); + } + + /// + /// Filters a sequence of strings by regular expression, optionally allowing to ignore casing. + /// + /// The sequence of strings to filter. + /// The regular expression pattern to use for matching. + /// + /// to ignore casing when matching; otherwise, . + /// + /// The filtered sequence. + public static IEnumerable Grep(this IEnumerable source, string pattern, bool ignoreCase) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(pattern); +#else + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (pattern is null) + { + throw new ArgumentNullException(nameof(pattern)); + } +#endif + + var regex = new Regex(pattern, RegexOptions.Compiled | (ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None)); + + foreach (string item in source) + { + if (regex.IsMatch(item)) + { + yield return item; + } + } + } +}