From 852fb1c66714e95863992d41f77ca82f687b90ee Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 3 Apr 2023 00:55:52 +0200 Subject: [PATCH 01/24] Use docopt.net to parse extensions generator CLI args --- .../MoreLinq.ExtensionsGenerator.csproj | 1 + bld/ExtensionsGenerator/Program.cs | 97 ++++++++----------- 2 files changed, 41 insertions(+), 57 deletions(-) diff --git a/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj b/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj index faf971cfd..9e19b8bfb 100644 --- a/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj +++ b/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj @@ -5,6 +5,7 @@ false + diff --git a/bld/ExtensionsGenerator/Program.cs b/bld/ExtensionsGenerator/Program.cs index d275c3272..f9cc083c2 100644 --- a/bld/ExtensionsGenerator/Program.cs +++ b/bld/ExtensionsGenerator/Program.cs @@ -24,6 +24,7 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using DocoptNet; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -35,8 +36,19 @@ try #pragma warning restore CA1852 // Seal internal types { - Run(args); - return 0; + var exitCode = + ProgramArguments.CreateParser() + .Parse(args) + .Match(args => { Run(args); return 0; }, _ => 1); + + if (exitCode != 0) + { + Console.Error.WriteLine("Invalid argument or usage!"); + Console.Error.WriteLine(); + Console.Error.WriteLine(ProgramArguments.Help); + } + + return exitCode; } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception e) @@ -46,54 +58,9 @@ return 0xbad; } -static void Run(IEnumerable args) +static void Run(ProgramArguments args) { - var dir = Directory.GetCurrentDirectory(); - - string? includePattern = null; - string? excludePattern = null; - var debug = false; - var usings = new List(); - var noClassLead = false; - - using (var arg = args.GetEnumerator()) - { - while (arg.MoveNext()) - { - switch (arg.Current) - { - case "-i": - case "--include": - includePattern = Read(arg, MissingArgValue); - break; - case "-x": - case "--exclude": - excludePattern = Read(arg, MissingArgValue); - break; - case "-u": - case "--using": - usings.Add(Read(arg, MissingArgValue)); - break; - case "--no-class-lead": - noClassLead = true; - break; - case "-d": - case "--debug": - debug = true; - break; - case "": - continue; - default: - dir = arg.Current[0] != '-' - ? arg.Current - : throw new Exception("Invalid argument: " + arg.Current); - break; - } - } - - static Exception MissingArgValue() => - new InvalidOperationException("Missing argument value."); - } + var dir = args.ArgDir ?? Directory.GetCurrentDirectory(); static Func PredicateFromPattern(string? pattern, bool @default) => @@ -101,8 +68,8 @@ static Func ? delegate { return @default; } : new Func(new Regex(pattern).IsMatch); - var includePredicate = PredicateFromPattern(includePattern, true); - var excludePredicate = PredicateFromPattern(excludePattern, false); + var includePredicate = PredicateFromPattern(args.OptInclude, true); + var excludePredicate = PredicateFromPattern(args.OptExclude, false); var thisAssemblyName = typeof(TypeKey).GetTypeInfo().Assembly.GetName(); @@ -198,7 +165,7 @@ from e in ms.Select((m, i) => (SourceOrder: i + 1, Method: m)) q = q.ToArray(); - if (debug) + if (args.OptDebug) { var ms = // @@ -252,7 +219,7 @@ into e }; var imports = - from ns in baseImports.Concat(usings) + from ns in baseImports.Concat(args.OptUsing) select indent + $"using {ns};"; var classes = @@ -290,7 +257,7 @@ select Argument(IdentifierName(p.Identifier)), .WithSemicolonToken(ParseToken(";").WithTrailingTrivia(LineFeed)) } into m - let classLead = !noClassLead + let classLead = !args.OptNoClassLead ? $$""" /// {{m.Name}} extension. @@ -373,9 +340,6 @@ select Walk(te.Type))), }; } -static T Read(IEnumerator e, Func errorFactory) => - e.MoveNext() ? e.Current : throw errorFactory(); - // // Logical type nodes designed to be structurally sortable based on: // @@ -478,3 +442,22 @@ protected override int CompareParameters(TypeKey other) return base.CompareParameters(other); } } + +[DocoptArguments(HelpConstName = nameof(HelpText))] +sealed partial class ProgramArguments +{ + const string HelpText = """ + Usage: + #BIN# [options] [-u NAMESPACE]... [DIR] + + Options: + -i, --include REGEX Include files matching pattern. + -x, --exclude REGEX Exclude files matching pattern. + -u, --using NAMESPACE Additional using imports to add. + --no-class-lead Skip generating class lead. + -d, --debug Produce output for debugging. + """; + + public static string Help => + HelpText.Replace("#BIN#", Path.GetFileName(Environment.ProcessPath), StringComparison.Ordinal); +} From ac57083bc83544b8beffb1403dc9763a0a171c75 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sun, 18 Jun 2023 15:11:19 +0200 Subject: [PATCH 02/24] Mark test struct read-only --- MoreLinq.Test/ToDataTableTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq.Test/ToDataTableTest.cs b/MoreLinq.Test/ToDataTableTest.cs index f9cbe83ac..ae053830c 100644 --- a/MoreLinq.Test/ToDataTableTest.cs +++ b/MoreLinq.Test/ToDataTableTest.cs @@ -181,7 +181,7 @@ public void ToDataTableWithSchema() Assert.That(rows.Select(r => r["Value"]).ToArray(), Is.EqualTo(vars.Select(e => e.Value).ToArray())); } - struct Point + readonly struct Point { #pragma warning disable CA1805 // Do not initialize unnecessarily (avoids CS0649) From bf8e270d7b9135d3563e89666d6fca26940c35fb Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sun, 18 Jun 2023 16:27:44 +0200 Subject: [PATCH 03/24] Remove temporary allocation in query --- MoreLinq/ToDataTable.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index 491ec8ca6..249488619 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -190,16 +190,16 @@ static MemberInfo[] BuildOrBindSchema(DataTable table, MemberInfo[] members) var columns = table.Columns; var schemas = from m in members - let type = m.MemberType == MemberTypes.Property - ? ((PropertyInfo)m).PropertyType - : ((FieldInfo)m).FieldType select new { Member = m, - Type = type.IsGenericType - && typeof(Nullable<>) == type.GetGenericTypeDefinition() - ? type.GetGenericArguments()[0] - : type, + Type = (m.MemberType == MemberTypes.Property + ? ((PropertyInfo)m).PropertyType + : ((FieldInfo)m).FieldType) switch + { + var type when Nullable.GetUnderlyingType(type) is { } t => t, + var type => type, + }, Column = columns[m.Name], }; From 8db4bef22ce0ba465e980d9e381d84f99aae31db Mon Sep 17 00:00:00 2001 From: Pawel Flajszer <45766938+pflajszer@users.noreply.github.com> Date: Sat, 24 Jun 2023 11:17:40 +0200 Subject: [PATCH 04/24] Remove obsolete "Concat" method This is a squashed merge of PR #1002 that closes #993. --- MoreLinq/Append.cs | 13 --------- MoreLinq/CompatibilitySuppressions.xml | 28 +++++++++++++++++++ .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 1 + .../netstandard2.0/PublicAPI.Unshipped.txt | 1 + .../netstandard2.1/PublicAPI.Unshipped.txt | 1 + README.md | 7 +++-- 6 files changed, 36 insertions(+), 15 deletions(-) diff --git a/MoreLinq/Append.cs b/MoreLinq/Append.cs index e0800b364..b081bbd83 100644 --- a/MoreLinq/Append.cs +++ b/MoreLinq/Append.cs @@ -38,18 +38,5 @@ public static IEnumerable Append(this IEnumerable head, T tail) ? node.Concat(tail) : PendNode.WithSource(head).Concat(tail); } - - /// - /// Returns a sequence consisting of the head elements and the given tail element. - /// - /// Type of sequence - /// All elements of the head. Must not be null. - /// Tail element of the new sequence. - /// A sequence consisting of the head elements and the given tail element. - /// This operator uses deferred execution and streams its results. - - [Obsolete("Use " + nameof(Append) + " instead.")] - public static IEnumerable Concat(this IEnumerable head, T tail) => - head.Append(tail); } } diff --git a/MoreLinq/CompatibilitySuppressions.xml b/MoreLinq/CompatibilitySuppressions.xml index 1d7bf3d3e..da1602414 100644 --- a/MoreLinq/CompatibilitySuppressions.xml +++ b/MoreLinq/CompatibilitySuppressions.xml @@ -1,6 +1,34 @@ + + CP0002 + M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/net462/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + + + CP0002 + M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/net6.0/MoreLinq.dll + lib/net6.0/MoreLinq.dll + true + + + CP0002 + M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/netstandard2.0/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + + + CP0002 + M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/netstandard2.1/MoreLinq.dll + lib/netstandard2.1/MoreLinq.dll + true + PKV006 .NETStandard,Version=v1.0 diff --git a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt index 7dc5c5811..b4ddb014c 100644 --- a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +*REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 7dc5c5811..b4ddb014c 100644 --- a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +*REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt index 7dc5c5811..b4ddb014c 100644 --- a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +*REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! diff --git a/README.md b/README.md index ece3287fc..2c9962eda 100644 --- a/README.md +++ b/README.md @@ -167,8 +167,11 @@ first sequence has fewer, the same or more elements than the second sequence. Returns a sequence consisting of the head element and the given tail elements. -This method is obsolete and will be removed in a future version. Use `Append` -instead. +This extension was rendered obsolete in version 3.0 and eventually removed in +version 4.0. Use [`Append`][linq-append] from .NET instead that's been available +since .NET Standard 1.6+, .NET Core 1.0+ and .NET Framework 4.7.1+. + +[linq-append]: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.append ### Consume From f7668273de76bcbdbecc95b0cb8f616a90479646 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sat, 6 May 2023 10:42:52 +0200 Subject: [PATCH 05/24] Fix bug in "FillForward" when filler is null --- MoreLinq.Test/FillForwardTest.cs | 13 +++++++++++++ MoreLinq/FillForward.cs | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/MoreLinq.Test/FillForwardTest.cs b/MoreLinq.Test/FillForwardTest.cs index bd156f096..97c68f563 100644 --- a/MoreLinq.Test/FillForwardTest.cs +++ b/MoreLinq.Test/FillForwardTest.cs @@ -83,5 +83,18 @@ select line.Trim() into line new { Continent = "Africa", Country = "Kenya", City = "Nairobi", Value = 901 }, })); } + + /// + /// Exercises bug reported in issue #999. + /// + + [Test] + public void FillForwardWithNullFiller() + { + var input = new int?[] { null, 1, null, 2, null, 3, null }; + var result = input.FillForward(x => x is not null); + result.AssertSequenceEqual(Enumerable.Repeat((int?)null, input.Length)); + } } } diff --git a/MoreLinq/FillForward.cs b/MoreLinq/FillForward.cs index 6d304a366..a38faae49 100644 --- a/MoreLinq/FillForward.cs +++ b/MoreLinq/FillForward.cs @@ -112,10 +112,10 @@ static IEnumerable FillForwardImpl(IEnumerable source, Func pr { if (predicate(item)) { - yield return seed is (true, { } someSeed) + yield return seed is (true, var theSeed) ? fillSelector != null - ? fillSelector(item, someSeed) - : someSeed + ? fillSelector(item, theSeed) + : theSeed : item; } else From b9c3062bcf0ef9c82f14b60108529205a4ea1de5 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Tue, 20 Jun 2023 13:26:37 +0200 Subject: [PATCH 06/24] Dissolve schema query into distinct "ToDataTable" paths --- MoreLinq/ToDataTable.cs | 49 ++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index 249488619..59a42b4fd 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -189,20 +189,6 @@ static MemberInfo[] BuildOrBindSchema(DataTable table, MemberInfo[] members) var columns = table.Columns; - var schemas = from m in members - select new - { - Member = m, - Type = (m.MemberType == MemberTypes.Property - ? ((PropertyInfo)m).PropertyType - : ((FieldInfo)m).FieldType) switch - { - var type when Nullable.GetUnderlyingType(type) is { } t => t, - var type => type, - }, - Column = columns[m.Name], - }; - // // If the table has no columns then build the schema. // If it has columns then validate members against the columns @@ -211,25 +197,34 @@ var type when Nullable.GetUnderlyingType(type) is { } t => t, if (columns.Count == 0) { - columns.AddRange(schemas.Select(m => new DataColumn(m.Member.Name, m.Type)).ToArray()); + foreach (var member in members) + _ = columns.Add(member.Name, GetElementaryTypeOfPropertyOrField(member)); + + return members; } - else - { - members = new MemberInfo[columns.Count]; - foreach (var info in schemas) - { - var member = info.Member; - var column = info.Column ?? throw new ArgumentException($"Column named '{member.Name}' is missing.", nameof(table)); + var columnMembers = new MemberInfo[columns.Count]; - if (info.Type != column.DataType) - throw new ArgumentException($"Column named '{member.Name}' has wrong data type. It should be {info.Type} when it is {column.DataType}.", nameof(table)); + foreach (var member in members) + { + var column = columns[member.Name] ?? throw new ArgumentException($"Column named '{member.Name}' is missing.", nameof(table)); - members[column.Ordinal] = member; - } + if (GetElementaryTypeOfPropertyOrField(member) is var type && type != column.DataType) + throw new ArgumentException($"Column named '{member.Name}' has wrong data type. It should be {type} when it is {column.DataType}.", nameof(table)); + + columnMembers[column.Ordinal] = member; } - return members; + return columnMembers; + + static Type GetElementaryTypeOfPropertyOrField(MemberInfo member) => + (member.MemberType == MemberTypes.Property ? ((PropertyInfo)member).PropertyType + : ((FieldInfo)member).FieldType) + switch + { + var type when Nullable.GetUnderlyingType(type) is { } ut => ut, + var type => type, + }; } static Func CreateShredder(IEnumerable members) From f59f7a5ad3079debd19a9911e88e35c3c7b1db64 Mon Sep 17 00:00:00 2001 From: Stuart Turner Date: Sun, 25 Jun 2023 09:32:22 -0500 Subject: [PATCH 07/24] Hide extensions that conflict with newer .NET versions This is a squashed merge of PR #945 that: - closes #565 - closes #738 Co-authored-by: Atif Aziz --- MoreLinq.Test/AppendTest.cs | 4 + MoreLinq.Test/DistinctByTest.cs | 4 + MoreLinq.Test/Enumerable.cs | 5 + MoreLinq.Test/PrependTest.cs | 4 + MoreLinq.Test/SkipLastTest.cs | 4 + MoreLinq.Test/TakeLastTest.cs | 4 + MoreLinq/Append.cs | 4 + MoreLinq/CompatibilitySuppressions.xml | 112 ++++++++++++++++++ MoreLinq/DistinctBy.cs | 12 +- MoreLinq/Prepend.cs | 4 + .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 16 +++ .../netstandard2.0/PublicAPI.Unshipped.txt | 4 + .../netstandard2.1/PublicAPI.Unshipped.txt | 12 ++ MoreLinq/SkipLast.cs | 4 + MoreLinq/TakeLast.cs | 4 + MoreLinq/ToHashSet.cs | 10 +- bld/ExtensionsGenerator/Program.cs | 11 +- 17 files changed, 215 insertions(+), 3 deletions(-) diff --git a/MoreLinq.Test/AppendTest.cs b/MoreLinq.Test/AppendTest.cs index 736ba6498..55ff31597 100644 --- a/MoreLinq.Test/AppendTest.cs +++ b/MoreLinq.Test/AppendTest.cs @@ -15,6 +15,8 @@ // limitations under the License. #endregion +#if !NET471_OR_GREATER && !NETSTANDARD1_6_OR_GREATER && !NETCOREAPP2_0_OR_GREATER + namespace MoreLinq.Test { using System.Collections.Generic; @@ -90,3 +92,5 @@ public void AppendWithSharedSource() } } } + +#endif diff --git a/MoreLinq.Test/DistinctByTest.cs b/MoreLinq.Test/DistinctByTest.cs index 7035578f2..377c9a318 100644 --- a/MoreLinq.Test/DistinctByTest.cs +++ b/MoreLinq.Test/DistinctByTest.cs @@ -15,6 +15,8 @@ // limitations under the License. #endregion +#if !NET6_0_OR_GREATER + namespace MoreLinq.Test { using System; @@ -61,3 +63,5 @@ public void DistinctByIsLazyWithComparer() } } } + +#endif diff --git a/MoreLinq.Test/Enumerable.cs b/MoreLinq.Test/Enumerable.cs index f7795af4e..ae22e31cd 100644 --- a/MoreLinq.Test/Enumerable.cs +++ b/MoreLinq.Test/Enumerable.cs @@ -45,6 +45,11 @@ public static bool Any(this IEnumerable source) => public static bool Any(this IEnumerable source, Func predicate) => LinqEnumerable.Any(source, predicate); +#if NET471_OR_GREATER || NET6_0_OR_GREATER + public static IEnumerable Append (this IEnumerable source, TSource element) => + LinqEnumerable.Append(source, element); +#endif + public static IEnumerable AsEnumerable(this IEnumerable source) => LinqEnumerable.AsEnumerable(source); diff --git a/MoreLinq.Test/PrependTest.cs b/MoreLinq.Test/PrependTest.cs index eaaad5c81..3a4f938d1 100644 --- a/MoreLinq.Test/PrependTest.cs +++ b/MoreLinq.Test/PrependTest.cs @@ -15,6 +15,8 @@ // limitations under the License. #endregion +#if !NET471_OR_GREATER && !NETSTANDARD1_6_OR_GREATER && !NETCOREAPP2_0_OR_GREATER + namespace MoreLinq.Test { using System.Collections.Generic; @@ -89,3 +91,5 @@ public void PrependWithSharedSource() } } } + +#endif diff --git a/MoreLinq.Test/SkipLastTest.cs b/MoreLinq.Test/SkipLastTest.cs index df7b19680..f6bbf7796 100644 --- a/MoreLinq.Test/SkipLastTest.cs +++ b/MoreLinq.Test/SkipLastTest.cs @@ -15,6 +15,8 @@ // limitations under the License. #endregion +#if !NETSTANDARD2_1 && !NETCOREAPP2_0_OR_GREATER + namespace MoreLinq.Test { using System.Collections.Generic; @@ -68,3 +70,5 @@ public void SkipLastUsesCollectionCountAtIterationTime() } } } + +#endif diff --git a/MoreLinq.Test/TakeLastTest.cs b/MoreLinq.Test/TakeLastTest.cs index 5779f4cfc..fc0347bf6 100644 --- a/MoreLinq.Test/TakeLastTest.cs +++ b/MoreLinq.Test/TakeLastTest.cs @@ -15,6 +15,8 @@ // limitations under the License. #endregion +#if !NETSTANDARD2_1 && !NETCOREAPP2_0_OR_GREATER + namespace MoreLinq.Test { using NUnit.Framework; @@ -89,3 +91,5 @@ static void AssertTakeLast(ICollection input, int count, ActionA sequence consisting of the head elements and the given tail element. /// This operator uses deferred execution and streams its results. +#if NET471_OR_GREATER || NETSTANDARD1_6_OR_GREATER || NETCOREAPP2_0_OR_GREATER + public static IEnumerable Append(IEnumerable head, T tail) +#else public static IEnumerable Append(this IEnumerable head, T tail) +#endif { if (head == null) throw new ArgumentNullException(nameof(head)); return head is PendNode node diff --git a/MoreLinq/CompatibilitySuppressions.xml b/MoreLinq/CompatibilitySuppressions.xml index da1602414..227399b89 100644 --- a/MoreLinq/CompatibilitySuppressions.xml +++ b/MoreLinq/CompatibilitySuppressions.xml @@ -1,6 +1,69 @@ + + CP0001 + T:MoreLinq.Extensions.AppendExtension + lib/net462/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + + + CP0001 + T:MoreLinq.Extensions.PrependExtension + lib/net462/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + + + CP0001 + T:MoreLinq.Extensions.AppendExtension + lib/net6.0/MoreLinq.dll + lib/net6.0/MoreLinq.dll + true + + + CP0001 + T:MoreLinq.Extensions.PrependExtension + lib/net6.0/MoreLinq.dll + lib/net6.0/MoreLinq.dll + true + + + CP0001 + T:MoreLinq.Extensions.AppendExtension + lib/netstandard2.0/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + + + CP0001 + T:MoreLinq.Extensions.PrependExtension + lib/netstandard2.0/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + + + CP0001 + T:MoreLinq.Extensions.AppendExtension + lib/netstandard2.1/MoreLinq.dll + lib/netstandard2.1/MoreLinq.dll + true + + + CP0001 + T:MoreLinq.Extensions.PrependExtension + lib/netstandard2.1/MoreLinq.dll + lib/netstandard2.1/MoreLinq.dll + true + + + CP0002 + M:MoreLinq.MoreEnumerable.Append``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/net462/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -8,6 +71,20 @@ lib/netstandard2.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Prepend``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/net462/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + + + CP0002 + M:MoreLinq.MoreEnumerable.Append``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/net6.0/MoreLinq.dll + lib/net6.0/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -15,6 +92,20 @@ lib/net6.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Prepend``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/net6.0/MoreLinq.dll + lib/net6.0/MoreLinq.dll + true + + + CP0002 + M:MoreLinq.MoreEnumerable.Append``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/netstandard2.0/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -22,6 +113,20 @@ lib/netstandard2.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Prepend``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/netstandard2.0/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + + + CP0002 + M:MoreLinq.MoreEnumerable.Append``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/netstandard2.1/MoreLinq.dll + lib/netstandard2.1/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -29,6 +134,13 @@ lib/netstandard2.1/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Prepend``1(System.Collections.Generic.IEnumerable{``0},``0) + lib/netstandard2.1/MoreLinq.dll + lib/netstandard2.1/MoreLinq.dll + true + PKV006 .NETStandard,Version=v1.0 diff --git a/MoreLinq/DistinctBy.cs b/MoreLinq/DistinctBy.cs index a05faab82..1079ff814 100644 --- a/MoreLinq/DistinctBy.cs +++ b/MoreLinq/DistinctBy.cs @@ -38,10 +38,15 @@ static partial class MoreEnumerable /// A sequence consisting of distinct elements from the source sequence, /// comparing them by the specified key projection. +#if NET6_0_OR_GREATER + public static IEnumerable DistinctBy(IEnumerable source, + Func keySelector) +#else public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) +#endif { - return source.DistinctBy(keySelector, null); + return DistinctBy(source, keySelector, null); } /// @@ -62,8 +67,13 @@ public static IEnumerable DistinctBy(this IEnumerableA sequence consisting of distinct elements from the source sequence, /// comparing them by the specified key projection. +#if NET6_0_OR_GREATER + public static IEnumerable DistinctBy(IEnumerable source, + Func keySelector, IEqualityComparer? comparer) +#else public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector, IEqualityComparer? comparer) +#endif { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); diff --git a/MoreLinq/Prepend.cs b/MoreLinq/Prepend.cs index 06f5a0f27..fb82e8538 100644 --- a/MoreLinq/Prepend.cs +++ b/MoreLinq/Prepend.cs @@ -41,7 +41,11 @@ static partial class MoreEnumerable /// The result variable, when iterated over, will yield /// 0, 1, 2 and 3, in turn. +#if NET471_OR_GREATER || NETSTANDARD1_6_OR_GREATER || NETCOREAPP2_0_OR_GREATER + public static IEnumerable Prepend(IEnumerable source, TSource value) +#else public static IEnumerable Prepend(this IEnumerable source, TSource value) +#endif { if (source == null) throw new ArgumentNullException(nameof(source)); return source is PendNode node diff --git a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt index b4ddb014c..8959c7eec 100644 --- a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -1,2 +1,18 @@ #nullable enable +*REMOVED*static MoreLinq.MoreEnumerable.Append(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.Prepend(this System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.DistinctBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.DistinctBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.SkipLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.TakeLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.HashSet! +*REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.HashSet! +static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.DistinctBy(System.Collections.Generic.IEnumerable! source, System.Func! keySelector) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.DistinctBy(System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Prepend(System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.SkipLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.TakeLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.ToHashSet(System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.HashSet! +static MoreLinq.MoreEnumerable.ToHashSet(System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.HashSet! diff --git a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index b4ddb014c..056e1801b 100644 --- a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,2 +1,6 @@ #nullable enable +*REMOVED*static MoreLinq.MoreEnumerable.Append(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.Prepend(this System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Prepend(System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt index b4ddb014c..b025e7aed 100644 --- a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt @@ -1,2 +1,14 @@ #nullable enable +*REMOVED*static MoreLinq.MoreEnumerable.Append(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.Prepend(this System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.SkipLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.TakeLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.HashSet! +*REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.HashSet! +static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Prepend(System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.SkipLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.TakeLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.ToHashSet(System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.HashSet! +static MoreLinq.MoreEnumerable.ToHashSet(System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.HashSet! diff --git a/MoreLinq/SkipLast.cs b/MoreLinq/SkipLast.cs index 0483f3e74..c88cec5e4 100644 --- a/MoreLinq/SkipLast.cs +++ b/MoreLinq/SkipLast.cs @@ -33,7 +33,11 @@ static partial class MoreEnumerable /// An containing the source sequence elements except for the bypassed ones at the end. /// +#if NETSTANDARD2_1 || NETCOREAPP2_0_OR_GREATER + public static IEnumerable SkipLast(IEnumerable source, int count) +#else public static IEnumerable SkipLast(this IEnumerable source, int count) +#endif { if (source == null) throw new ArgumentNullException(nameof(source)); diff --git a/MoreLinq/TakeLast.cs b/MoreLinq/TakeLast.cs index 6619ff196..88eac7a8e 100644 --- a/MoreLinq/TakeLast.cs +++ b/MoreLinq/TakeLast.cs @@ -46,7 +46,11 @@ static partial class MoreEnumerable /// 56 and 78 in turn. /// +#if NETSTANDARD2_1 || NETCOREAPP2_0_OR_GREATER + public static IEnumerable TakeLast(IEnumerable source, int count) +#else public static IEnumerable TakeLast(this IEnumerable source, int count) +#endif { if (source == null) throw new ArgumentNullException(nameof(source)); diff --git a/MoreLinq/ToHashSet.cs b/MoreLinq/ToHashSet.cs index 7301ed357..1f6dc8bd1 100644 --- a/MoreLinq/ToHashSet.cs +++ b/MoreLinq/ToHashSet.cs @@ -35,9 +35,13 @@ static partial class MoreEnumerable /// This evaluates the input sequence completely. /// +#if NETSTANDARD2_1 || NET472_OR_GREATER || NETCOREAPP2_0_OR_GREATER + public static HashSet ToHashSet(IEnumerable source) +#else public static HashSet ToHashSet(this IEnumerable source) +#endif { - return source.ToHashSet(null); + return ToHashSet(source, null); } /// @@ -53,7 +57,11 @@ public static HashSet ToHashSet(this IEnumerable sour /// This evaluates the input sequence completely. /// +#if NETSTANDARD2_1 || NET472_OR_GREATER || NETCOREAPP2_0_OR_GREATER + public static HashSet ToHashSet(IEnumerable source, IEqualityComparer? comparer) +#else public static HashSet ToHashSet(this IEnumerable source, IEqualityComparer? comparer) +#endif { if (source == null) throw new ArgumentNullException(nameof(source)); return new HashSet(source, comparer); diff --git a/bld/ExtensionsGenerator/Program.cs b/bld/ExtensionsGenerator/Program.cs index f9cc083c2..87dba483d 100644 --- a/bld/ExtensionsGenerator/Program.cs +++ b/bld/ExtensionsGenerator/Program.cs @@ -235,7 +235,16 @@ from md in g select MethodDeclaration(md.ReturnType, md.Identifier) .WithAttributeLists(md.AttributeLists) - .WithModifiers(md.Modifiers) + .WithModifiers( + TokenList(md.Modifiers[0] // assume at least one modifier, like public + .WithLeadingTrivia( + from lt in md.Modifiers[0].LeadingTrivia + where lt.Kind() is not (SyntaxKind.DisabledTextTrivia + or SyntaxKind.IfDirectiveTrivia + or SyntaxKind.ElseDirectiveTrivia + or SyntaxKind.EndIfDirectiveTrivia) + select lt)) + .AddRange(md.Modifiers.Skip(1))) .WithTypeParameterList(md.TypeParameterList) .WithConstraintClauses(md.ConstraintClauses) .WithParameterList(md.ParameterList) From d739f65e4ebbc458647a1608c8f7bc2908784d82 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Tue, 20 Jun 2023 17:28:05 +0200 Subject: [PATCH 08/24] Fix schema binding signature in "ToDataTable" implementation --- MoreLinq/ToDataTable.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index 59a42b4fd..476fdb9bd 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -103,8 +103,8 @@ public static TTable ToDataTable(this IEnumerable source, TTable t expressions ??= EmptyArray>>.Value; var members = PrepareMemberInfos(expressions).ToArray(); - members = BuildOrBindSchema(table, members); - var shredder = CreateShredder(members); + var boundMembers = BuildOrBindSchema(table, members); + var shredder = CreateShredder(boundMembers); // // Builds rows out of elements in the sequence and @@ -180,7 +180,7 @@ static MemberInfo GetAccessedMember(LambdaExpression lambda) /// columns for which there is no source member supplying a value. /// - static MemberInfo[] BuildOrBindSchema(DataTable table, MemberInfo[] members) + static MemberInfo?[] BuildOrBindSchema(DataTable table, MemberInfo[] members) { // // Retrieve member information needed to @@ -227,7 +227,7 @@ var type when Nullable.GetUnderlyingType(type) is { } ut => ut, }; } - static Func CreateShredder(IEnumerable members) + static Func CreateShredder(MemberInfo?[] members) { var parameter = Expression.Parameter(typeof(T), "e"); From e98d6322c75af1902359947661b85a867c40df50 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 26 Jun 2023 22:50:37 +0200 Subject: [PATCH 09/24] Re-enable tests for hidden extensions on all targets --- MoreLinq.Test/AggregateTest.cs | 1 + MoreLinq.Test/AppendTest.cs | 5 +---- MoreLinq.Test/DistinctByTest.cs | 5 +---- MoreLinq.Test/Enumerable.cs | 5 ----- MoreLinq.Test/GroupAdjacentTest.cs | 1 + MoreLinq.Test/PartialSortTest.cs | 1 + MoreLinq.Test/PrependTest.cs | 5 +---- MoreLinq.Test/SkipLastTest.cs | 5 +---- MoreLinq.Test/TakeLastTest.cs | 5 +---- 9 files changed, 8 insertions(+), 25 deletions(-) diff --git a/MoreLinq.Test/AggregateTest.cs b/MoreLinq.Test/AggregateTest.cs index 9ef7b2336..04ec29154 100644 --- a/MoreLinq.Test/AggregateTest.cs +++ b/MoreLinq.Test/AggregateTest.cs @@ -26,6 +26,7 @@ namespace MoreLinq.Test using System.Reactive.Linq; using System.Reflection; using NUnit.Framework.Interfaces; + using static MoreLinq.Extensions.AppendExtension; using static FuncModule; [TestFixture] diff --git a/MoreLinq.Test/AppendTest.cs b/MoreLinq.Test/AppendTest.cs index 55ff31597..58dbd6c9d 100644 --- a/MoreLinq.Test/AppendTest.cs +++ b/MoreLinq.Test/AppendTest.cs @@ -15,12 +15,11 @@ // limitations under the License. #endregion -#if !NET471_OR_GREATER && !NETSTANDARD1_6_OR_GREATER && !NETCOREAPP2_0_OR_GREATER - namespace MoreLinq.Test { using System.Collections.Generic; using NUnit.Framework; + using static MoreLinq.Extensions.AppendExtension; [TestFixture] public class AppendTest @@ -92,5 +91,3 @@ public void AppendWithSharedSource() } } } - -#endif diff --git a/MoreLinq.Test/DistinctByTest.cs b/MoreLinq.Test/DistinctByTest.cs index 377c9a318..70c684592 100644 --- a/MoreLinq.Test/DistinctByTest.cs +++ b/MoreLinq.Test/DistinctByTest.cs @@ -15,12 +15,11 @@ // limitations under the License. #endregion -#if !NET6_0_OR_GREATER - namespace MoreLinq.Test { using System; using NUnit.Framework; + using static MoreLinq.Extensions.DistinctByExtension; [TestFixture] public class DistinctByTest @@ -63,5 +62,3 @@ public void DistinctByIsLazyWithComparer() } } } - -#endif diff --git a/MoreLinq.Test/Enumerable.cs b/MoreLinq.Test/Enumerable.cs index ae22e31cd..f7795af4e 100644 --- a/MoreLinq.Test/Enumerable.cs +++ b/MoreLinq.Test/Enumerable.cs @@ -45,11 +45,6 @@ public static bool Any(this IEnumerable source) => public static bool Any(this IEnumerable source, Func predicate) => LinqEnumerable.Any(source, predicate); -#if NET471_OR_GREATER || NET6_0_OR_GREATER - public static IEnumerable Append (this IEnumerable source, TSource element) => - LinqEnumerable.Append(source, element); -#endif - public static IEnumerable AsEnumerable(this IEnumerable source) => LinqEnumerable.AsEnumerable(source); diff --git a/MoreLinq.Test/GroupAdjacentTest.cs b/MoreLinq.Test/GroupAdjacentTest.cs index d6da9909a..b3830a2d2 100644 --- a/MoreLinq.Test/GroupAdjacentTest.cs +++ b/MoreLinq.Test/GroupAdjacentTest.cs @@ -20,6 +20,7 @@ namespace MoreLinq.Test using System; using System.Collections.Generic; using NUnit.Framework; + using static MoreLinq.Extensions.AppendExtension; [TestFixture] public class GroupAdjacentTest diff --git a/MoreLinq.Test/PartialSortTest.cs b/MoreLinq.Test/PartialSortTest.cs index a838efdc3..555428fad 100644 --- a/MoreLinq.Test/PartialSortTest.cs +++ b/MoreLinq.Test/PartialSortTest.cs @@ -19,6 +19,7 @@ namespace MoreLinq.Test { using System; using NUnit.Framework; + using static MoreLinq.Extensions.AppendExtension; [TestFixture] public class PartialSortTests diff --git a/MoreLinq.Test/PrependTest.cs b/MoreLinq.Test/PrependTest.cs index 3a4f938d1..de8253f6e 100644 --- a/MoreLinq.Test/PrependTest.cs +++ b/MoreLinq.Test/PrependTest.cs @@ -15,13 +15,12 @@ // limitations under the License. #endregion -#if !NET471_OR_GREATER && !NETSTANDARD1_6_OR_GREATER && !NETCOREAPP2_0_OR_GREATER - namespace MoreLinq.Test { using System.Collections.Generic; using NUnit.Framework; using NUnit.Framework.Interfaces; + using static MoreLinq.Extensions.PrependExtension; [TestFixture] public class PrependTest @@ -91,5 +90,3 @@ public void PrependWithSharedSource() } } } - -#endif diff --git a/MoreLinq.Test/SkipLastTest.cs b/MoreLinq.Test/SkipLastTest.cs index f6bbf7796..94187ef75 100644 --- a/MoreLinq.Test/SkipLastTest.cs +++ b/MoreLinq.Test/SkipLastTest.cs @@ -15,12 +15,11 @@ // limitations under the License. #endregion -#if !NETSTANDARD2_1 && !NETCOREAPP2_0_OR_GREATER - namespace MoreLinq.Test { using System.Collections.Generic; using NUnit.Framework; + using static MoreLinq.Extensions.SkipLastExtension; [TestFixture] public class SkipLastTest @@ -70,5 +69,3 @@ public void SkipLastUsesCollectionCountAtIterationTime() } } } - -#endif diff --git a/MoreLinq.Test/TakeLastTest.cs b/MoreLinq.Test/TakeLastTest.cs index fc0347bf6..2c20f8137 100644 --- a/MoreLinq.Test/TakeLastTest.cs +++ b/MoreLinq.Test/TakeLastTest.cs @@ -15,13 +15,12 @@ // limitations under the License. #endregion -#if !NETSTANDARD2_1 && !NETCOREAPP2_0_OR_GREATER - namespace MoreLinq.Test { using NUnit.Framework; using System.Collections.Generic; using System; + using static MoreLinq.Extensions.TakeLastExtension; [TestFixture] public class TakeLastTest @@ -91,5 +90,3 @@ static void AssertTakeLast(ICollection input, int count, Action Date: Tue, 27 Jun 2023 08:35:39 +0200 Subject: [PATCH 10/24] Test "SortedMerge" does not call "MoveNext" eagerly --- MoreLinq.Test/SortedMergeTest.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/MoreLinq.Test/SortedMergeTest.cs b/MoreLinq.Test/SortedMergeTest.cs index a9ddb706b..7434df0c4 100644 --- a/MoreLinq.Test/SortedMergeTest.cs +++ b/MoreLinq.Test/SortedMergeTest.cs @@ -54,6 +54,21 @@ public void TestSortedMergeDisposesOnError() Throws.BreakException); } + /// + /// Verify that SortedMerge do not call MoveNext method eagerly + /// + [Test] + public void TestSortedMergeDoNotCallMoveNextEagerly() + { + using var sequenceA = TestingSequence.Of(1, 3); + using var sequenceB = MoreEnumerable.From(() => 2, () => throw new TestException()) + .AsTestingSequence(); + + var result = sequenceA.SortedMerge(OrderByDirection.Ascending, sequenceB).Take(2); + + Assert.That(() => result.Consume(), Throws.Nothing); + } + /// /// Verify that SortedMerge throws an exception if invoked on a null sequence. /// From 35fefdfffe5024b4ee5286a234c68204ac5382ce Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Tue, 27 Jun 2023 21:48:02 +0200 Subject: [PATCH 11/24] Update package validation tool to v1.0.12 --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 516b7dafe..87d6ac9db 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "meziantou.framework.nugetpackagevalidation.tool": { - "version": "1.0.9", + "version": "1.0.12", "commands": [ "meziantou.validate-nuget-package" ] From 5eebc0af5f8614489e36cb734d808d03018526c9 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sat, 14 Oct 2023 13:34:38 +0200 Subject: [PATCH 12/24] Suppress "EnableGenerateDocumentationFile" warning This is a workaround for dotnet/roslyn#41640. --- Directory.Build.props | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Directory.Build.props b/Directory.Build.props index 0b85fe477..ef2ed08ed 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,5 +5,7 @@ true 7.0-all true + + EnableGenerateDocumentationFile From 189a0546bdedf05a9fc5a387e55d813262f0749b Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sun, 15 Oct 2023 10:19:46 +0200 Subject: [PATCH 13/24] Change "Batch" to return arrays This is a squashed merge or PR #1014 that closes #98. --- MoreLinq.Test/BatchTest.cs | 11 ---- MoreLinq/Batch.cs | 4 +- MoreLinq/CompatibilitySuppressions.xml | 56 +++++++++++++++++++ MoreLinq/Extensions.g.cs | 4 +- .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 8 +++ .../netstandard2.0/PublicAPI.Unshipped.txt | 8 +++ .../netstandard2.1/PublicAPI.Unshipped.txt | 8 +++ 7 files changed, 84 insertions(+), 15 deletions(-) diff --git a/MoreLinq.Test/BatchTest.cs b/MoreLinq.Test/BatchTest.cs index bd2db7127..ee10acfb2 100644 --- a/MoreLinq.Test/BatchTest.cs +++ b/MoreLinq.Test/BatchTest.cs @@ -62,17 +62,6 @@ public void BatchSequenceTransformingResult() result.AssertSequenceEqual(10, 26, 9); } - [Test] - public void BatchSequenceYieldsListsOfBatches() - { - var result = new[] { 1, 2, 3 }.Batch(2); - - using var reader = result.Read(); - Assert.That(reader.Read(), Is.InstanceOf(typeof(IList))); - Assert.That(reader.Read(), Is.InstanceOf(typeof(IList))); - reader.ReadEnd(); - } - [Test] public void BatchSequencesAreIndependentInstances() { diff --git a/MoreLinq/Batch.cs b/MoreLinq/Batch.cs index b8abf7f1e..5da60c07a 100644 --- a/MoreLinq/Batch.cs +++ b/MoreLinq/Batch.cs @@ -47,7 +47,7 @@ static partial class MoreEnumerable /// /// - public static IEnumerable> Batch(this IEnumerable source, int size) + public static IEnumerable Batch(this IEnumerable source, int size) { return Batch(source, size, IdFn); } @@ -80,7 +80,7 @@ public static IEnumerable> Batch(this IEnumerable< /// public static IEnumerable Batch(this IEnumerable source, int size, - Func, TResult> resultSelector) + Func resultSelector) { if (source == null) throw new ArgumentNullException(nameof(source)); if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size)); diff --git a/MoreLinq/CompatibilitySuppressions.xml b/MoreLinq/CompatibilitySuppressions.xml index 227399b89..689b85169 100644 --- a/MoreLinq/CompatibilitySuppressions.xml +++ b/MoreLinq/CompatibilitySuppressions.xml @@ -57,6 +57,13 @@ lib/netstandard2.1/MoreLinq.dll true + + CP0002 + M:MoreLinq.Extensions.BatchExtension.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) + lib/net462/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Append``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -64,6 +71,13 @@ lib/netstandard2.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) + lib/net462/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -78,6 +92,13 @@ lib/netstandard2.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.Extensions.BatchExtension.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) + lib/net6.0/MoreLinq.dll + lib/net6.0/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Append``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -85,6 +106,13 @@ lib/net6.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) + lib/net6.0/MoreLinq.dll + lib/net6.0/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -99,6 +127,13 @@ lib/net6.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.Extensions.BatchExtension.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) + lib/netstandard2.0/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Append``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -106,6 +141,13 @@ lib/netstandard2.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) + lib/netstandard2.0/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -120,6 +162,13 @@ lib/netstandard2.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.Extensions.BatchExtension.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) + lib/netstandard2.1/MoreLinq.dll + lib/netstandard2.1/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Append``1(System.Collections.Generic.IEnumerable{``0},``0) @@ -127,6 +176,13 @@ lib/netstandard2.1/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) + lib/netstandard2.1/MoreLinq.dll + lib/netstandard2.1/MoreLinq.dll + true + CP0002 M:MoreLinq.MoreEnumerable.Concat``1(System.Collections.Generic.IEnumerable{``0},``0) diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index b25c873fb..f492ce171 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -680,7 +680,7 @@ public static partial class BatchExtension /// /// - public static IEnumerable> Batch(this IEnumerable source, int size) + public static IEnumerable Batch(this IEnumerable source, int size) => MoreEnumerable.Batch(source, size); /// @@ -711,7 +711,7 @@ public static IEnumerable> Batch(this IEnumerable< /// public static IEnumerable Batch(this IEnumerable source, int size, - Func, TResult> resultSelector) + Func resultSelector) => MoreEnumerable.Batch(source, size, resultSelector); } diff --git a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt index 8959c7eec..fb44539c9 100644 --- a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -1,5 +1,9 @@ #nullable enable +*REMOVED*static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func!, TResult>! resultSelector) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! *REMOVED*static MoreLinq.MoreEnumerable.Append(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func!, TResult>! resultSelector) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! *REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.Prepend(this System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.DistinctBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector) -> System.Collections.Generic.IEnumerable! @@ -8,7 +12,11 @@ *REMOVED*static MoreLinq.MoreEnumerable.TakeLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.HashSet! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.HashSet! +static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! +static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.DistinctBy(System.Collections.Generic.IEnumerable! source, System.Func! keySelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.DistinctBy(System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Prepend(System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 056e1801b..ecb558421 100644 --- a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,6 +1,14 @@ #nullable enable +*REMOVED*static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func!, TResult>! resultSelector) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! *REMOVED*static MoreLinq.MoreEnumerable.Append(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func!, TResult>! resultSelector) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! *REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.Prepend(this System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! +static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! +static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Prepend(System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt index b025e7aed..b2858b738 100644 --- a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt @@ -1,12 +1,20 @@ #nullable enable +*REMOVED*static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func!, TResult>! resultSelector) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! *REMOVED*static MoreLinq.MoreEnumerable.Append(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func!, TResult>! resultSelector) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! *REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.Prepend(this System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.SkipLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.TakeLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.HashSet! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.HashSet! +static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! +static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Prepend(System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.SkipLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.TakeLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! From 80335506aaed74b5af3b99b5e8653c62ccbd10e3 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Fri, 20 Oct 2023 18:50:11 +0200 Subject: [PATCH 14/24] Fix linking of some methods in read-me doc --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2c9962eda..9d1f76a44 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,11 @@ using static MoreLinq.Extensions.LeadExtension; In the example above, only the [`Lag`][lag] and [`Lead`][lead] extension methods will be available in scope. -Apart from extension methods, MoreLINQ also offers regular static method -that *generate* (instead of operating on) sequences, like `Unfold`, -`Random`, `Sequence` and others. If you want to use these while statically -importing other individual extension methods, you can do so via aliasing: +Apart from extension methods, MoreLINQ also offers regular static method that +*generate* (instead of operating on) sequences, like [`Unfold`][unfold], +[`Random`][random], [`Sequence`][sequence] and others. If you want to use these +while statically importing other individual extension methods, you can do so via +aliasing: ```c# using static MoreLinq.Extensions.LagExtension; From 7d438eda053d7aeaf5269e9fb9866d4103327758 Mon Sep 17 00:00:00 2001 From: Orace Date: Sat, 21 Oct 2023 13:12:45 +0200 Subject: [PATCH 15/24] Fix "Subsets" to return an empty set when k = 0 This is a squashed merge of PR #646 that fixes #645. --------- Co-authored-by: Atif Aziz --- MoreLinq.Test/SubsetTest.cs | 13 +++++++++++++ MoreLinq/Subsets.cs | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/MoreLinq.Test/SubsetTest.cs b/MoreLinq.Test/SubsetTest.cs index 4f55cb70b..5b35524bf 100644 --- a/MoreLinq.Test/SubsetTest.cs +++ b/MoreLinq.Test/SubsetTest.cs @@ -131,6 +131,19 @@ public void TestAllSubsetsExpectedResults() Assert.That(subset, Is.EqualTo(expectedSubsets[index++])); } + /// + /// See issue #645. + /// + [Test] + public void Test0SubsetIsEmptyList() + { + var sequence = Enumerable.Range(1, 4); + var actual = sequence.Subsets(0); + + // For any set there is always 1 subset of size 0: the empty set. + actual.AssertSequenceEqual(new int[0]); + } + /// /// Verify that the number of subsets for a given subset-size is correct. /// diff --git a/MoreLinq/Subsets.cs b/MoreLinq/Subsets.cs index bb5752723..7852dd3c2 100644 --- a/MoreLinq/Subsets.cs +++ b/MoreLinq/Subsets.cs @@ -166,7 +166,7 @@ public void Reset() _k = _subset.Length; _n = _set.Count; _z = _n - _k + 1; - _continue = _subset.Length > 0; + _continue = true; } public IList Current => (IList)_subset.Clone(); @@ -198,7 +198,7 @@ public bool MoveNext() ExtractSubset(); - _continue = _indices[0] != _z; + _continue = _indices.Length > 0 && _indices[0] != _z; return true; } From dd8e8a89bfb13af9f6082b6911460eb06e9bae69 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 23 Oct 2023 21:32:51 +0200 Subject: [PATCH 16/24] Remove "Windowed" that's superseded by "Window" This is a squashed merge of PR #1017 that closes #1016. --- MoreLinq/CompatibilitySuppressions.xml | 28 +++++++++++++++++++ .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 1 + .../netstandard2.0/PublicAPI.Unshipped.txt | 1 + .../netstandard2.1/PublicAPI.Unshipped.txt | 1 + MoreLinq/Window.cs | 20 ------------- README.md | 3 +- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/MoreLinq/CompatibilitySuppressions.xml b/MoreLinq/CompatibilitySuppressions.xml index 689b85169..ead8c1613 100644 --- a/MoreLinq/CompatibilitySuppressions.xml +++ b/MoreLinq/CompatibilitySuppressions.xml @@ -92,6 +92,13 @@ lib/netstandard2.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Windowed``1(System.Collections.Generic.IEnumerable{``0},System.Int32) + lib/net462/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + CP0002 M:MoreLinq.Extensions.BatchExtension.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) @@ -127,6 +134,13 @@ lib/net6.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Windowed``1(System.Collections.Generic.IEnumerable{``0},System.Int32) + lib/net6.0/MoreLinq.dll + lib/net6.0/MoreLinq.dll + true + CP0002 M:MoreLinq.Extensions.BatchExtension.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) @@ -162,6 +176,13 @@ lib/netstandard2.0/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Windowed``1(System.Collections.Generic.IEnumerable{``0},System.Int32) + lib/netstandard2.0/MoreLinq.dll + lib/netstandard2.0/MoreLinq.dll + true + CP0002 M:MoreLinq.Extensions.BatchExtension.Batch``2(System.Collections.Generic.IEnumerable{``0},System.Int32,System.Func{System.Collections.Generic.IEnumerable{``0},``1}) @@ -197,6 +218,13 @@ lib/netstandard2.1/MoreLinq.dll true + + CP0002 + M:MoreLinq.MoreEnumerable.Windowed``1(System.Collections.Generic.IEnumerable{``0},System.Int32) + lib/netstandard2.1/MoreLinq.dll + lib/netstandard2.1/MoreLinq.dll + true + PKV006 .NETStandard,Version=v1.0 diff --git a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt index fb44539c9..c2a9e629e 100644 --- a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -12,6 +12,7 @@ *REMOVED*static MoreLinq.MoreEnumerable.TakeLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.HashSet! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.HashSet! +*REMOVED*static MoreLinq.MoreEnumerable.Windowed(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index ecb558421..fa0a2a28d 100644 --- a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -6,6 +6,7 @@ *REMOVED*static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! *REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.Prepend(this System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.Windowed(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt index b2858b738..85be22323 100644 --- a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt @@ -10,6 +10,7 @@ *REMOVED*static MoreLinq.MoreEnumerable.TakeLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.HashSet! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.HashSet! +*REMOVED*static MoreLinq.MoreEnumerable.Windowed(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/Window.cs b/MoreLinq/Window.cs index b7db91a85..04b1ddefd 100644 --- a/MoreLinq/Window.cs +++ b/MoreLinq/Window.cs @@ -73,25 +73,5 @@ public static IEnumerable> Window(this IEnumerable - /// Processes a sequence into a series of sub-sequences representing a windowed subset of the original. - /// - /// The type of the elements of the source sequence. - /// The sequence to evaluate a sliding window over. - /// The size (number of elements) in each window. - /// - /// A series of sequences representing each sliding window subsequence. - /// - /// - /// The number of sequences returned is: Max(0, sequence.Count() - windowSize) + - /// 1 - /// - /// Returned sub-sequences are buffered, but the overall operation is streamed. - /// - - [Obsolete("Use " + nameof(Window) + " instead.")] - public static IEnumerable> Windowed(this IEnumerable source, int size) => - source.Window(size); } } diff --git a/README.md b/README.md index 9d1f76a44..de6b4e622 100644 --- a/README.md +++ b/README.md @@ -693,8 +693,7 @@ subset of the original Processes a sequence into a series of subsequences representing a windowed subset of the original -This method is obsolete and will be removed in a future version. Use `Window` -instead. +This method was removed and has been superseded by [`Window`](#window) instead. ### WindowLeft From 137564f07e7b7110bc552b8105a8e51726d1b71e Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 23 Oct 2023 22:43:03 +0200 Subject: [PATCH 17/24] Validate "expressions" of "ToDataTable" This is a squashed merge of PR #1021 that fixes #802. --- MoreLinq.Test/NullArgumentTest.cs | 1 - MoreLinq/ToDataTable.cs | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/MoreLinq.Test/NullArgumentTest.cs b/MoreLinq.Test/NullArgumentTest.cs index 9cd3ec8cd..13a9aa364 100644 --- a/MoreLinq.Test/NullArgumentTest.cs +++ b/MoreLinq.Test/NullArgumentTest.cs @@ -124,7 +124,6 @@ static bool CanBeNull(ParameterInfo parameter) nameof(MoreEnumerable.From) + ".function1", nameof(MoreEnumerable.From) + ".function2", nameof(MoreEnumerable.From) + ".function3", - nameof(MoreEnumerable.ToDataTable) + ".expressions", nameof(MoreEnumerable.Trace) + ".format" }; diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index 476fdb9bd..08027567c 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -97,10 +97,7 @@ public static TTable ToDataTable(this IEnumerable source, TTable t { if (source == null) throw new ArgumentNullException(nameof(source)); if (table == null) throw new ArgumentNullException(nameof(table)); - - // TODO disallow null for "expressions" in next major update - - expressions ??= EmptyArray>>.Value; + if (expressions == null) throw new ArgumentNullException(nameof(expressions)); var members = PrepareMemberInfos(expressions).ToArray(); var boundMembers = BuildOrBindSchema(table, members); From 66f7cab9fb16c64b3f7b305904b6dfab48cd9fdf Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Tue, 24 Oct 2023 08:26:05 +0200 Subject: [PATCH 18/24] Add "Minima" & "Maxima" that supersede "MinBy" & "MaxBy" This is a squashed merge of PR #1019 that closes #1018. --- MoreLinq.Test/{MaxByTest.cs => MaximaTest.cs} | 68 ++-- MoreLinq.Test/{MinByTest.cs => MinimaTest.cs} | 68 ++-- MoreLinq/Extensions.g.cs | 106 ++++- MoreLinq/ExtremaMembers.cs | 25 ++ MoreLinq/MaxBy.cs | 317 +-------------- MoreLinq/Maxima.cs | 368 ++++++++++++++++++ MoreLinq/MinBy.cs | 23 +- MoreLinq/Minima.cs | 74 ++++ MoreLinq/MoreLinq.csproj | 4 +- .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 18 + .../netstandard2.0/PublicAPI.Unshipped.txt | 10 + .../netstandard2.1/PublicAPI.Unshipped.txt | 10 + README.md | 22 +- bld/ExtensionsGenerator/Program.cs | 1 - 14 files changed, 734 insertions(+), 380 deletions(-) rename MoreLinq.Test/{MaxByTest.cs => MaximaTest.cs} (77%) rename MoreLinq.Test/{MinByTest.cs => MinimaTest.cs} (77%) create mode 100644 MoreLinq/ExtremaMembers.cs create mode 100644 MoreLinq/Maxima.cs create mode 100644 MoreLinq/Minima.cs diff --git a/MoreLinq.Test/MaxByTest.cs b/MoreLinq.Test/MaximaTest.cs similarity index 77% rename from MoreLinq.Test/MaxByTest.cs rename to MoreLinq.Test/MaximaTest.cs index 6934c6773..272bbf5a3 100644 --- a/MoreLinq.Test/MaxByTest.cs +++ b/MoreLinq.Test/MaximaTest.cs @@ -20,44 +20,44 @@ namespace MoreLinq.Test using NUnit.Framework; [TestFixture] - public class MaxByTest + public class MaximaTest { [Test] - public void MaxByIsLazy() + public void MaximaIsLazy() { - _ = new BreakingSequence().MaxBy(BreakingFunc.Of()); + _ = new BreakingSequence().Maxima(BreakingFunc.Of()); } [Test] - public void MaxByReturnsMaxima() + public void MaximaReturnsMaxima() { - Assert.That(SampleData.Strings.MaxBy(x => x.Length), + Assert.That(SampleData.Strings.Maxima(x => x.Length), Is.EqualTo(new[] { "hello", "world" })); } [Test] - public void MaxByNullComparer() + public void MaximaNullComparer() { - Assert.That(SampleData.Strings.MaxBy(x => x.Length, null), - Is.EqualTo(SampleData.Strings.MaxBy(x => x.Length))); + Assert.That(SampleData.Strings.Maxima(x => x.Length, null), + Is.EqualTo(SampleData.Strings.Maxima(x => x.Length))); } [Test] - public void MaxByEmptySequence() + public void MaximaEmptySequence() { - Assert.That(new string[0].MaxBy(x => x.Length), Is.Empty); + Assert.That(new string[0].Maxima(x => x.Length), Is.Empty); } [Test] - public void MaxByWithNaturalComparer() + public void MaximaWithNaturalComparer() { - Assert.That(SampleData.Strings.MaxBy(x => x[1]), Is.EqualTo(new[] { "az" })); + Assert.That(SampleData.Strings.Maxima(x => x[1]), Is.EqualTo(new[] { "az" })); } [Test] - public void MaxByWithComparer() + public void MaximaWithComparer() { - Assert.That(SampleData.Strings.MaxBy(x => x[1], Comparable.DescendingOrderComparer), Is.EqualTo(new[] { "aa" })); + Assert.That(SampleData.Strings.Maxima(x => x[1], Comparable.DescendingOrderComparer), Is.EqualTo(new[] { "aa" })); } public class First @@ -66,7 +66,7 @@ public class First public void ReturnsMaximum() { using var strings = SampleData.Strings.AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length); + var maxima = strings.Maxima(s => s.Length); Assert.That(MoreEnumerable.First(maxima), Is.EqualTo("hello")); } @@ -74,7 +74,7 @@ public void ReturnsMaximum() public void WithComparerReturnsMaximum() { using var strings = SampleData.Strings.AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + var maxima = strings.Maxima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.First(maxima), Is.EqualTo("ax")); } @@ -83,7 +83,7 @@ public void WithEmptySourceThrows() { using var strings = Enumerable.Empty().AsTestingSequence(); Assert.That(() => - MoreEnumerable.First(strings.MaxBy(s => s.Length)), + MoreEnumerable.First(strings.Maxima(s => s.Length)), Throws.InvalidOperationException); } @@ -92,7 +92,7 @@ public void WithEmptySourceWithComparerThrows() { using var strings = Enumerable.Empty().AsTestingSequence(); Assert.That(() => - MoreEnumerable.First(strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer)), + MoreEnumerable.First(strings.Maxima(s => s.Length, Comparable.DescendingOrderComparer)), Throws.InvalidOperationException); } } @@ -103,7 +103,7 @@ public class FirstOrDefault public void ReturnsMaximum() { using var strings = SampleData.Strings.AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length); + var maxima = strings.Maxima(s => s.Length); Assert.That(MoreEnumerable.FirstOrDefault(maxima), Is.EqualTo("hello")); } @@ -111,7 +111,7 @@ public void ReturnsMaximum() public void WithComparerReturnsMaximum() { using var strings = SampleData.Strings.AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + var maxima = strings.Maxima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.FirstOrDefault(maxima), Is.EqualTo("ax")); } @@ -119,7 +119,7 @@ public void WithComparerReturnsMaximum() public void WithEmptySourceReturnsDefault() { using var strings = Enumerable.Empty().AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length); + var maxima = strings.Maxima(s => s.Length); Assert.That(MoreEnumerable.FirstOrDefault(maxima), Is.Null); } @@ -127,7 +127,7 @@ public void WithEmptySourceReturnsDefault() public void WithEmptySourceWithComparerReturnsDefault() { using var strings = Enumerable.Empty().AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + var maxima = strings.Maxima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.FirstOrDefault(maxima), Is.Null); } } @@ -138,7 +138,7 @@ public class Last public void ReturnsMaximum() { using var strings = SampleData.Strings.AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length); + var maxima = strings.Maxima(s => s.Length); Assert.That(MoreEnumerable.Last(maxima), Is.EqualTo("world")); } @@ -146,7 +146,7 @@ public void ReturnsMaximum() public void WithComparerReturnsMaximumPerComparer() { using var strings = SampleData.Strings.AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + var maxima = strings.Maxima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.Last(maxima), Is.EqualTo("az")); } @@ -155,7 +155,7 @@ public void WithEmptySourceThrows() { using var strings = Enumerable.Empty().AsTestingSequence(); Assert.That(() => - MoreEnumerable.Last(strings.MaxBy(s => s.Length)), + MoreEnumerable.Last(strings.Maxima(s => s.Length)), Throws.InvalidOperationException); } @@ -164,7 +164,7 @@ public void WithEmptySourceWithComparerThrows() { using var strings = Enumerable.Empty().AsTestingSequence(); Assert.That(() => - MoreEnumerable.Last(strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer)), + MoreEnumerable.Last(strings.Maxima(s => s.Length, Comparable.DescendingOrderComparer)), Throws.InvalidOperationException); } } @@ -175,7 +175,7 @@ public class LastOrDefault public void ReturnsMaximum() { using var strings = SampleData.Strings.AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length); + var maxima = strings.Maxima(s => s.Length); Assert.That(MoreEnumerable.LastOrDefault(maxima), Is.EqualTo("world")); } @@ -183,7 +183,7 @@ public void ReturnsMaximum() public void WithComparerReturnsMaximumPerComparer() { using var strings = SampleData.Strings.AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + var maxima = strings.Maxima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.LastOrDefault(maxima), Is.EqualTo("az")); } @@ -191,7 +191,7 @@ public void WithComparerReturnsMaximumPerComparer() public void WithEmptySourceReturnsDefault() { using var strings = Enumerable.Empty().AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length); + var maxima = strings.Maxima(s => s.Length); Assert.That(MoreEnumerable.LastOrDefault(maxima), Is.Null); } @@ -199,7 +199,7 @@ public void WithEmptySourceReturnsDefault() public void WithEmptySourceWithComparerReturnsDefault() { using var strings = Enumerable.Empty().AsTestingSequence(); - var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + var maxima = strings.Maxima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.LastOrDefault(maxima), Is.Null); } } @@ -213,7 +213,7 @@ public class Take public string[] ReturnsMaxima(int count) { using var strings = SampleData.Strings.AsTestingSequence(); - return strings.MaxBy(s => s.Length).Take(count).ToArray(); + return strings.Maxima(s => s.Length).Take(count).ToArray(); } [TestCase(0, 0, ExpectedResult = new string[0] )] @@ -227,7 +227,7 @@ public string[] ReturnsMaxima(int count) public string[] WithComparerReturnsMaximaPerComparer(int count, int index) { using var strings = SampleData.Strings.AsTestingSequence(); - return strings.MaxBy(s => s[index], Comparable.DescendingOrderComparer) + return strings.Maxima(s => s[index], Comparable.DescendingOrderComparer) .Take(count) .ToArray(); } @@ -242,7 +242,7 @@ public class TakeLast public string[] TakeLastReturnsMaxima(int count) { using var strings = SampleData.Strings.AsTestingSequence(); - return strings.MaxBy(s => s.Length).TakeLast(count).ToArray(); + return strings.Maxima(s => s.Length).TakeLast(count).ToArray(); } [TestCase(0, 0, ExpectedResult = new string[0] )] @@ -256,7 +256,7 @@ public string[] TakeLastReturnsMaxima(int count) public string[] WithComparerReturnsMaximaPerComparer(int count, int index) { using var strings = SampleData.Strings.AsTestingSequence(); - return strings.MaxBy(s => s[index], Comparable.DescendingOrderComparer) + return strings.Maxima(s => s[index], Comparable.DescendingOrderComparer) .TakeLast(count) .ToArray(); } diff --git a/MoreLinq.Test/MinByTest.cs b/MoreLinq.Test/MinimaTest.cs similarity index 77% rename from MoreLinq.Test/MinByTest.cs rename to MoreLinq.Test/MinimaTest.cs index a549f1387..29caba348 100644 --- a/MoreLinq.Test/MinByTest.cs +++ b/MoreLinq.Test/MinimaTest.cs @@ -20,44 +20,44 @@ namespace MoreLinq.Test using NUnit.Framework; [TestFixture] - public class MinByTest + public class MinimaTest { [Test] - public void MinByIsLazy() + public void MinimaIsLazy() { - _ = new BreakingSequence().MinBy(BreakingFunc.Of()); + _ = new BreakingSequence().Minima(BreakingFunc.Of()); } [Test] - public void MinByReturnsMinima() + public void MinimaReturnsMinima() { - Assert.That(SampleData.Strings.MinBy(x => x.Length), + Assert.That(SampleData.Strings.Minima(x => x.Length), Is.EqualTo(new[] { "ax", "aa", "ab", "ay", "az" })); } [Test] - public void MinByNullComparer() + public void MinimaNullComparer() { - Assert.That(SampleData.Strings.MinBy(x => x.Length, null), - Is.EqualTo(SampleData.Strings.MinBy(x => x.Length))); + Assert.That(SampleData.Strings.Minima(x => x.Length, null), + Is.EqualTo(SampleData.Strings.Minima(x => x.Length))); } [Test] - public void MinByEmptySequence() + public void MinimaEmptySequence() { - Assert.That(new string[0].MinBy(x => x.Length), Is.Empty); + Assert.That(new string[0].Minima(x => x.Length), Is.Empty); } [Test] - public void MinByWithNaturalComparer() + public void MinimaWithNaturalComparer() { - Assert.That(SampleData.Strings.MinBy(x => x[1]), Is.EqualTo(new[] { "aa" })); + Assert.That(SampleData.Strings.Minima(x => x[1]), Is.EqualTo(new[] { "aa" })); } [Test] - public void MinByWithComparer() + public void MinimaWithComparer() { - Assert.That(SampleData.Strings.MinBy(x => x[1], Comparable.DescendingOrderComparer), Is.EqualTo(new[] { "az" })); + Assert.That(SampleData.Strings.Minima(x => x[1], Comparable.DescendingOrderComparer), Is.EqualTo(new[] { "az" })); } public class First @@ -66,7 +66,7 @@ public class First public void ReturnsMinimum() { using var strings = SampleData.Strings.AsTestingSequence(); - var minima = MoreEnumerable.First(strings.MinBy(s => s.Length)); + var minima = MoreEnumerable.First(strings.Minima(s => s.Length)); Assert.That(minima, Is.EqualTo("ax")); } @@ -74,7 +74,7 @@ public void ReturnsMinimum() public void WithComparerReturnsMinimum() { using var strings = SampleData.Strings.AsTestingSequence(); - var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + var minima = strings.Minima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.First(minima), Is.EqualTo("hello")); } @@ -82,7 +82,7 @@ public void WithComparerReturnsMinimum() public void WithEmptySourceThrows() { using var strings = Enumerable.Empty().AsTestingSequence(); - Assert.That(() => MoreEnumerable.First(strings.MinBy(s => s.Length)), + Assert.That(() => MoreEnumerable.First(strings.Minima(s => s.Length)), Throws.InvalidOperationException); } @@ -90,7 +90,7 @@ public void WithEmptySourceThrows() public void WithEmptySourceWithComparerThrows() { using var strings = Enumerable.Empty().AsTestingSequence(); - Assert.That(() => MoreEnumerable.First(strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer)), + Assert.That(() => MoreEnumerable.First(strings.Minima(s => s.Length, Comparable.DescendingOrderComparer)), Throws.InvalidOperationException); } } @@ -101,7 +101,7 @@ public class FirstOrDefault public void ReturnsMinimum() { using var strings = SampleData.Strings.AsTestingSequence(); - var minima = strings.MinBy(s => s.Length); + var minima = strings.Minima(s => s.Length); Assert.That(MoreEnumerable.FirstOrDefault(minima), Is.EqualTo("ax")); } @@ -109,7 +109,7 @@ public void ReturnsMinimum() public void WithComparerReturnsMinimum() { using var strings = SampleData.Strings.AsTestingSequence(); - var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + var minima = strings.Minima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.FirstOrDefault(minima), Is.EqualTo("hello")); } @@ -117,7 +117,7 @@ public void WithComparerReturnsMinimum() public void WithEmptySourceReturnsDefault() { using var strings = Enumerable.Empty().AsTestingSequence(); - var minima = strings.MinBy(s => s.Length); + var minima = strings.Minima(s => s.Length); Assert.That(MoreEnumerable.FirstOrDefault(minima), Is.Null); } @@ -125,7 +125,7 @@ public void WithEmptySourceReturnsDefault() public void WithEmptySourceWithComparerReturnsDefault() { using var strings = Enumerable.Empty().AsTestingSequence(); - var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + var minima = strings.Minima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.FirstOrDefault(minima), Is.Null); } } @@ -136,7 +136,7 @@ public class Last public void ReturnsMinimum() { using var strings = SampleData.Strings.AsTestingSequence(); - var minima = strings.MinBy(s => s.Length); + var minima = strings.Minima(s => s.Length); Assert.That(MoreEnumerable.Last(minima), Is.EqualTo("az")); } @@ -144,7 +144,7 @@ public void ReturnsMinimum() public void WithComparerReturnsMinimumPerComparer() { using var strings = SampleData.Strings.AsTestingSequence(); - var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + var minima = strings.Minima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.Last(minima), Is.EqualTo("world")); } @@ -152,7 +152,7 @@ public void WithComparerReturnsMinimumPerComparer() public void WithEmptySourceThrows() { using var strings = Enumerable.Empty().AsTestingSequence(); - Assert.That(() => MoreEnumerable.Last(strings.MinBy(s => s.Length)), + Assert.That(() => MoreEnumerable.Last(strings.Minima(s => s.Length)), Throws.InvalidOperationException); } @@ -160,7 +160,7 @@ public void WithEmptySourceThrows() public void WithEmptySourceWithComparerThrows() { using var strings = Enumerable.Empty().AsTestingSequence(); - Assert.That(() => MoreEnumerable.Last(strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer)), + Assert.That(() => MoreEnumerable.Last(strings.Minima(s => s.Length, Comparable.DescendingOrderComparer)), Throws.InvalidOperationException); } } @@ -171,7 +171,7 @@ public class LastOrDefault public void ReturnsMinimum() { using var strings = SampleData.Strings.AsTestingSequence(); - var minima = strings.MinBy(s => s.Length); + var minima = strings.Minima(s => s.Length); Assert.That(MoreEnumerable.LastOrDefault(minima), Is.EqualTo("az")); } @@ -179,7 +179,7 @@ public void ReturnsMinimum() public void WithComparerReturnsMinimumPerComparer() { using var strings = SampleData.Strings.AsTestingSequence(); - var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + var minima = strings.Minima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.LastOrDefault(minima), Is.EqualTo("world")); } @@ -187,7 +187,7 @@ public void WithComparerReturnsMinimumPerComparer() public void WithEmptySourceReturnsDefault() { using var strings = Enumerable.Empty().AsTestingSequence(); - var minima = strings.MinBy(s => s.Length); + var minima = strings.Minima(s => s.Length); Assert.That(MoreEnumerable.LastOrDefault(minima), Is.Null); } @@ -195,7 +195,7 @@ public void WithEmptySourceReturnsDefault() public void WithEmptySourceWithComparerReturnsDefault() { using var strings = Enumerable.Empty().AsTestingSequence(); - var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + var minima = strings.Minima(s => s.Length, Comparable.DescendingOrderComparer); Assert.That(MoreEnumerable.LastOrDefault(minima), Is.Null); } } @@ -212,7 +212,7 @@ public class Take public string[] ReturnsMinima(int count) { using var strings = SampleData.Strings.AsTestingSequence(); - return strings.MinBy(s => s.Length).Take(count).ToArray(); + return strings.Minima(s => s.Length).Take(count).ToArray(); } [TestCase(0, ExpectedResult = new string[0] )] @@ -222,7 +222,7 @@ public string[] ReturnsMinima(int count) public string[] WithComparerReturnsMinimaPerComparer(int count) { using var strings = SampleData.Strings.AsTestingSequence(); - return strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer) + return strings.Minima(s => s.Length, Comparable.DescendingOrderComparer) .Take(count) .ToArray(); } @@ -240,7 +240,7 @@ public class TakeLast public string[] ReturnsMinima(int count) { using var strings = SampleData.Strings.AsTestingSequence(); - return strings.MinBy(s => s.Length).TakeLast(count).ToArray(); + return strings.Minima(s => s.Length).TakeLast(count).ToArray(); } [TestCase(0, ExpectedResult = new string[0] )] @@ -250,7 +250,7 @@ public string[] ReturnsMinima(int count) public string[] WithComparerReturnsMinimaPerComparer(int count) { using var strings = SampleData.Strings.AsTestingSequence(); - return strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer) + return strings.Minima(s => s.Length, Comparable.DescendingOrderComparer) .TakeLast(count) .ToArray(); } diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index f492ce171..b7fcf096d 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -3401,7 +3401,6 @@ public static IEnumerable LeftJoin( [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] public static partial class MaxByExtension { - /// /// Returns the maximal elements of the given sequence, based on /// the given projection. @@ -3418,6 +3417,8 @@ public static partial class MaxByExtension /// The sequence of maximal elements, according to the projection. /// or is null + [Obsolete($"Use {nameof(ExtremaMembers.Maxima)} instead.")] + [ExcludeFromCodeCoverage] public static IExtremaEnumerable MaxBy(this IEnumerable source, Func selector) => MoreEnumerable.MaxBy(source, selector); @@ -3439,12 +3440,63 @@ public static IExtremaEnumerable MaxBy(this IEnumerable< /// , /// or is null + [Obsolete($"Use {nameof(ExtremaMembers.Maxima)} instead.")] + [ExcludeFromCodeCoverage] public static IExtremaEnumerable MaxBy(this IEnumerable source, Func selector, IComparer? comparer) => MoreEnumerable.MaxBy(source, selector, comparer); } + /// Maxima extension. + + [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] + public static partial class MaximaExtension + { + + /// + /// Returns the maximal elements of the given sequence, based on + /// the given projection. + /// + /// + /// This overload uses the default comparer for the projected type. + /// This operator uses deferred execution. The results are evaluated + /// and cached on first use to returned sequence. + /// + /// Type of the source sequence + /// Type of the projected element + /// Source sequence + /// Selector to use to pick the results to compare + /// The sequence of maximal elements, according to the projection. + /// or is null + + public static IExtremaEnumerable Maxima(this IEnumerable source, + Func selector) + => MoreEnumerable.Maxima(source, selector); + + /// + /// Returns the maximal elements of the given sequence, based on + /// the given projection and the specified comparer for projected values. + /// + /// + /// This operator uses deferred execution. The results are evaluated + /// and cached on first use to returned sequence. + /// + /// Type of the source sequence + /// Type of the projected element + /// Source sequence + /// Selector to use to pick the results to compare + /// Comparer to use to compare projected values + /// The sequence of maximal elements, according to the projection. + /// , + /// or is null + + public static IExtremaEnumerable Maxima(this IEnumerable source, + Func selector, IComparer? comparer) + => MoreEnumerable.Maxima(source, selector, comparer); + + } + /// MinBy extension. [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] @@ -3466,6 +3518,8 @@ public static partial class MinByExtension /// The sequence of minimal elements, according to the projection. /// or is null + [Obsolete($"Use {ExtremaMembers.Minima} instead.")] + [ExcludeFromCodeCoverage] public static IExtremaEnumerable MinBy(this IEnumerable source, Func selector) => MoreEnumerable.MinBy(source, selector); @@ -3487,12 +3541,62 @@ public static IExtremaEnumerable MinBy(this IEnumerable< /// , /// or is null + [Obsolete($"Use {nameof(ExtremaMembers.Minima)} instead.")] + [ExcludeFromCodeCoverage] public static IExtremaEnumerable MinBy(this IEnumerable source, Func selector, IComparer? comparer) => MoreEnumerable.MinBy(source, selector, comparer); } + /// Minima extension. + + [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] + public static partial class MinimaExtension + { + /// + /// Returns the minimal elements of the given sequence, based on + /// the given projection. + /// + /// + /// This overload uses the default comparer for the projected type. + /// This operator uses deferred execution. The results are evaluated + /// and cached on first use to returned sequence. + /// + /// Type of the source sequence + /// Type of the projected element + /// Source sequence + /// Selector to use to pick the results to compare + /// The sequence of minimal elements, according to the projection. + /// or is null + + public static IExtremaEnumerable Minima(this IEnumerable source, + Func selector) + => MoreEnumerable.Minima(source, selector); + + /// + /// Returns the minimal elements of the given sequence, based on + /// the given projection and the specified comparer for projected values. + /// + /// + /// This operator uses deferred execution. The results are evaluated + /// and cached on first use to returned sequence. + /// + /// Type of the source sequence + /// Type of the projected element + /// Source sequence + /// Selector to use to pick the results to compare + /// Comparer to use to compare projected values + /// The sequence of minimal elements, according to the projection. + /// , + /// or is null + + public static IExtremaEnumerable Minima(this IEnumerable source, + Func selector, IComparer? comparer) + => MoreEnumerable.Minima(source, selector, comparer); + + } + /// Move extension. [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] diff --git a/MoreLinq/ExtremaMembers.cs b/MoreLinq/ExtremaMembers.cs new file mode 100644 index 000000000..b976927b1 --- /dev/null +++ b/MoreLinq/ExtremaMembers.cs @@ -0,0 +1,25 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2023 Atif Aziz. 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 +{ + static class ExtremaMembers + { + public const string Minima = nameof(MoreEnumerable.Minima); + public const string Maxima = nameof(MoreEnumerable.Maxima); + } +} diff --git a/MoreLinq/MaxBy.cs b/MoreLinq/MaxBy.cs index 919c98fdd..9c105ef5c 100644 --- a/MoreLinq/MaxBy.cs +++ b/MoreLinq/MaxBy.cs @@ -18,157 +18,11 @@ namespace MoreLinq { using System; - using System.Collections; using System.Collections.Generic; - using System.Linq; - - /// - /// Exposes the enumerator, which supports iteration over a sequence of - /// some extremum property (maximum or minimum) of a specified type. - /// - /// The type of objects to enumerate. - - public interface IExtremaEnumerable : IEnumerable - { - /// - /// Returns a specified number of contiguous elements from the start of - /// the sequence. - /// - /// The number of elements to return. - /// - /// An that contains the specified number - /// of elements from the start of the input sequence. - /// - - IEnumerable Take(int count); - - /// - /// Returns a specified number of contiguous elements at the end of the - /// sequence. - /// - /// The number of elements to return. - /// - /// An that contains the specified number - /// of elements at the end of the input sequence. - /// - - IEnumerable TakeLast(int count); - } + using System.Diagnostics.CodeAnalysis; static partial class MoreEnumerable { - /// - /// Returns the first element of a sequence. - /// - /// - /// The type of the elements of . - /// The input sequence. - /// - /// The input sequence is empty. - /// - /// The first element of the input sequence. - /// - - public static T First(this IExtremaEnumerable source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - return source.Take(1).AsEnumerable().First(); - } - - /// - /// Returns the first element of a sequence, or a default value if the - /// sequence contains no elements. - /// - /// - /// The type of the elements of . - /// The input sequence. - /// - /// Default value of type if source is empty; - /// otherwise, the first element in source. - /// - - public static T? FirstOrDefault(this IExtremaEnumerable source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - return source.Take(1).AsEnumerable().FirstOrDefault(); - } - - /// - /// Returns the last element of a sequence. - /// - /// - /// The type of the elements of . - /// The input sequence. - /// - /// The input sequence is empty. - /// - /// The last element of the input sequence. - /// - - public static T Last(this IExtremaEnumerable source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - return source.TakeLast(1).AsEnumerable().Last(); - } - - /// - /// Returns the last element of a sequence, or a default value if the - /// sequence contains no elements. - /// - /// - /// The type of the elements of . - /// The input sequence. - /// - /// Default value of type if source is empty; - /// otherwise, the last element in source. - /// - - public static T? LastOrDefault(this IExtremaEnumerable source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - return source.TakeLast(1).AsEnumerable().LastOrDefault(); - } - - /// - /// Returns the only element of a sequence, and throws an exception if - /// there is not exactly one element in the sequence. - /// - /// - /// The type of the elements of . - /// The input sequence. - /// - /// The input sequence contains more than one element. - /// - /// The single element of the input sequence. - /// - -#pragma warning disable CA1720 // Identifier contains type name - public static T Single(this IExtremaEnumerable source) -#pragma warning restore CA1720 // Identifier contains type name - { - if (source == null) throw new ArgumentNullException(nameof(source)); - return source.Take(2).AsEnumerable().Single(); - } - - /// - /// Returns the only element of a sequence, or a default value if the - /// sequence is empty; this method throws an exception if there is more - /// than one element in the sequence. - /// - /// - /// The type of the elements of . - /// The input sequence. - /// - /// The single element of the input sequence, or default value of type - /// if the sequence contains no elements. - /// - - public static T? SingleOrDefault(this IExtremaEnumerable source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - return source.Take(2).AsEnumerable().SingleOrDefault(); - } - /// /// Returns the maximal elements of the given sequence, based on /// the given projection. @@ -185,10 +39,17 @@ public static T Single(this IExtremaEnumerable source) /// The sequence of maximal elements, according to the projection. /// or is null + [Obsolete($"Use {nameof(ExtremaMembers.Maxima)} instead.")] + [ExcludeFromCodeCoverage] +#if !NET6_0_OR_GREATER public static IExtremaEnumerable MaxBy(this IEnumerable source, Func selector) +#else + public static IExtremaEnumerable MaxBy(IEnumerable source, + Func selector) +#endif { - return source.MaxBy(selector, null); + return MaxBy(source, selector, null); } /// @@ -208,161 +69,17 @@ public static IExtremaEnumerable MaxBy(this IEnumerable< /// , /// or is null + [Obsolete($"Use {nameof(ExtremaMembers.Maxima)} instead.")] + [ExcludeFromCodeCoverage] +#if !NET6_0_OR_GREATER public static IExtremaEnumerable MaxBy(this IEnumerable source, Func selector, IComparer? comparer) +#else + public static IExtremaEnumerable MaxBy(IEnumerable source, + Func selector, IComparer? comparer) +#endif { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (selector == null) throw new ArgumentNullException(nameof(selector)); - - comparer ??= Comparer.Default; - return new ExtremaEnumerable(source, selector, comparer.Compare); - } - - sealed class ExtremaEnumerable : IExtremaEnumerable - { - readonly IEnumerable _source; - readonly Func _selector; - readonly Func _comparer; - - public ExtremaEnumerable(IEnumerable source, Func selector, Func comparer) - { - _source = source; - _selector = selector; - _comparer = comparer; - } - - public IEnumerator GetEnumerator() => - ExtremaBy(_source, Extrema.First, null, _selector, _comparer).GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); - - public IEnumerable Take(int count) => - count switch - { - 0 => Enumerable.Empty(), - 1 => ExtremaBy(_source, Extremum.First, 1 , _selector, _comparer), - _ => ExtremaBy(_source, Extrema.First , count, _selector, _comparer) - }; - - public IEnumerable TakeLast(int count) => - count switch - { - 0 => Enumerable.Empty(), - 1 => ExtremaBy(_source, Extremum.Last, 1 , _selector, _comparer), - _ => ExtremaBy(_source, Extrema.Last , count, _selector, _comparer) - }; - - static class Extrema - { - public static readonly Extrema? , T> First = new FirstExtrema(); - public static readonly Extrema?, T> Last = new LastExtrema(); - - sealed class FirstExtrema : Extrema?, T> - { - public override List? New() => null; - public override void Restart(ref List? store) => store = null; - public override IEnumerable GetEnumerable(List? store) => store ?? Enumerable.Empty(); - - public override void Add(ref List? store, int? limit, T item) - { - if (limit == null || store is null || store.Count < limit) - (store ??= new List()).Add(item); - } - } - - sealed class LastExtrema : Extrema?, T> - { - public override Queue? New() => null; - public override void Restart(ref Queue? store) => store = null; - public override IEnumerable GetEnumerable(Queue? store) => store ?? Enumerable.Empty(); - - public override void Add(ref Queue? store, int? limit, T item) - { - if (limit is { } n && store is { } queue && queue.Count == n) - _ = queue.Dequeue(); - (store ??= new Queue()).Enqueue(item); - } - } - } - - sealed class Extremum : Extrema<(bool, T), T> - { - public static readonly Extrema<(bool, T), T> First = new Extremum(false); - public static readonly Extrema<(bool, T), T> Last = new Extremum(true); - - readonly bool _poppable; - Extremum(bool poppable) => _poppable = poppable; - - public override (bool, T) New() => default; - public override void Restart(ref (bool, T) store) => store = default; - - public override IEnumerable GetEnumerable((bool, T) store) => - store is (true, var item) ? Enumerable.Repeat(item, 1) : Enumerable.Empty(); - - public override void Add(ref (bool, T) store, int? limit, T item) - { - if (!_poppable && store is (true, _)) - return; - store = (true, item); - } - } - } - - // > In mathematical analysis, the maxima and minima (the respective - // > plurals of maximum and minimum) of a function, known collectively - // > as extrema (the plural of extremum), ... - // > - // > - https://en.wikipedia.org/wiki/Maxima_and_minima - - static IEnumerable ExtremaBy( - this IEnumerable source, - Extrema extrema, int? limit, - Func selector, Func comparer) - { - foreach (var item in Extrema()) - yield return item; - - IEnumerable Extrema() - { - using var e = source.GetEnumerator(); - - if (!e.MoveNext()) - return new List(); - - var store = extrema.New(); - extrema.Add(ref store, limit, e.Current); - var extremaKey = selector(e.Current); - - while (e.MoveNext()) - { - var item = e.Current; - var key = selector(item); - switch (comparer(key, extremaKey)) - { - case > 0: - extrema.Restart(ref store); - extrema.Add(ref store, limit, item); - extremaKey = key; - break; - case 0: - extrema.Add(ref store, limit, item); - break; - default: - break; - } - } - - return extrema.GetEnumerable(store); - } - } - - abstract class Extrema - { - public abstract TStore New(); - public abstract void Restart(ref TStore store); - public abstract IEnumerable GetEnumerable(TStore store); - public abstract void Add(ref TStore store, int? limit, T item); + return source.Maxima(selector, comparer); } } } diff --git a/MoreLinq/Maxima.cs b/MoreLinq/Maxima.cs new file mode 100644 index 000000000..60150a433 --- /dev/null +++ b/MoreLinq/Maxima.cs @@ -0,0 +1,368 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2008 Jonathan Skeet. 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; + using System.Collections.Generic; + using System.Linq; + + /// + /// Exposes the enumerator, which supports iteration over a sequence of + /// some extremum property (maximum or minimum) of a specified type. + /// + /// The type of objects to enumerate. + + public interface IExtremaEnumerable : IEnumerable + { + /// + /// Returns a specified number of contiguous elements from the start of + /// the sequence. + /// + /// The number of elements to return. + /// + /// An that contains the specified number + /// of elements from the start of the input sequence. + /// + + IEnumerable Take(int count); + + /// + /// Returns a specified number of contiguous elements at the end of the + /// sequence. + /// + /// The number of elements to return. + /// + /// An that contains the specified number + /// of elements at the end of the input sequence. + /// + + IEnumerable TakeLast(int count); + } + + static partial class MoreEnumerable + { + /// + /// Returns the first element of a sequence. + /// + /// + /// The type of the elements of . + /// The input sequence. + /// + /// The input sequence is empty. + /// + /// The first element of the input sequence. + /// + + public static T First(this IExtremaEnumerable source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + return source.Take(1).AsEnumerable().First(); + } + + /// + /// Returns the first element of a sequence, or a default value if the + /// sequence contains no elements. + /// + /// + /// The type of the elements of . + /// The input sequence. + /// + /// Default value of type if source is empty; + /// otherwise, the first element in source. + /// + + public static T? FirstOrDefault(this IExtremaEnumerable source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + return source.Take(1).AsEnumerable().FirstOrDefault(); + } + + /// + /// Returns the last element of a sequence. + /// + /// + /// The type of the elements of . + /// The input sequence. + /// + /// The input sequence is empty. + /// + /// The last element of the input sequence. + /// + + public static T Last(this IExtremaEnumerable source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + return source.TakeLast(1).AsEnumerable().Last(); + } + + /// + /// Returns the last element of a sequence, or a default value if the + /// sequence contains no elements. + /// + /// + /// The type of the elements of . + /// The input sequence. + /// + /// Default value of type if source is empty; + /// otherwise, the last element in source. + /// + + public static T? LastOrDefault(this IExtremaEnumerable source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + return source.TakeLast(1).AsEnumerable().LastOrDefault(); + } + + /// + /// Returns the only element of a sequence, and throws an exception if + /// there is not exactly one element in the sequence. + /// + /// + /// The type of the elements of . + /// The input sequence. + /// + /// The input sequence contains more than one element. + /// + /// The single element of the input sequence. + /// + +#pragma warning disable CA1720 // Identifier contains type name + public static T Single(this IExtremaEnumerable source) +#pragma warning restore CA1720 // Identifier contains type name + { + if (source == null) throw new ArgumentNullException(nameof(source)); + return source.Take(2).AsEnumerable().Single(); + } + + /// + /// Returns the only element of a sequence, or a default value if the + /// sequence is empty; this method throws an exception if there is more + /// than one element in the sequence. + /// + /// + /// The type of the elements of . + /// The input sequence. + /// + /// The single element of the input sequence, or default value of type + /// if the sequence contains no elements. + /// + + public static T? SingleOrDefault(this IExtremaEnumerable source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + return source.Take(2).AsEnumerable().SingleOrDefault(); + } + + /// + /// Returns the maximal elements of the given sequence, based on + /// the given projection. + /// + /// + /// This overload uses the default comparer for the projected type. + /// This operator uses deferred execution. The results are evaluated + /// and cached on first use to returned sequence. + /// + /// Type of the source sequence + /// Type of the projected element + /// Source sequence + /// Selector to use to pick the results to compare + /// The sequence of maximal elements, according to the projection. + /// or is null + + public static IExtremaEnumerable Maxima(this IEnumerable source, + Func selector) + { + return source.Maxima(selector, null); + } + + /// + /// Returns the maximal elements of the given sequence, based on + /// the given projection and the specified comparer for projected values. + /// + /// + /// This operator uses deferred execution. The results are evaluated + /// and cached on first use to returned sequence. + /// + /// Type of the source sequence + /// Type of the projected element + /// Source sequence + /// Selector to use to pick the results to compare + /// Comparer to use to compare projected values + /// The sequence of maximal elements, according to the projection. + /// , + /// or is null + + public static IExtremaEnumerable Maxima(this IEnumerable source, + Func selector, IComparer? comparer) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (selector == null) throw new ArgumentNullException(nameof(selector)); + + comparer ??= Comparer.Default; + return new ExtremaEnumerable(source, selector, comparer.Compare); + } + + sealed class ExtremaEnumerable : IExtremaEnumerable + { + readonly IEnumerable _source; + readonly Func _selector; + readonly Func _comparer; + + public ExtremaEnumerable(IEnumerable source, Func selector, Func comparer) + { + _source = source; + _selector = selector; + _comparer = comparer; + } + + public IEnumerator GetEnumerator() => + ExtremaBy(_source, Extrema.First, null, _selector, _comparer).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + + public IEnumerable Take(int count) => + count switch + { + 0 => Enumerable.Empty(), + 1 => ExtremaBy(_source, Extremum.First, 1 , _selector, _comparer), + _ => ExtremaBy(_source, Extrema.First , count, _selector, _comparer) + }; + + public IEnumerable TakeLast(int count) => + count switch + { + 0 => Enumerable.Empty(), + 1 => ExtremaBy(_source, Extremum.Last, 1 , _selector, _comparer), + _ => ExtremaBy(_source, Extrema.Last , count, _selector, _comparer) + }; + + static class Extrema + { + public static readonly Extrema? , T> First = new FirstExtrema(); + public static readonly Extrema?, T> Last = new LastExtrema(); + + sealed class FirstExtrema : Extrema?, T> + { + public override List? New() => null; + public override void Restart(ref List? store) => store = null; + public override IEnumerable GetEnumerable(List? store) => store ?? Enumerable.Empty(); + + public override void Add(ref List? store, int? limit, T item) + { + if (limit == null || store is null || store.Count < limit) + (store ??= new List()).Add(item); + } + } + + sealed class LastExtrema : Extrema?, T> + { + public override Queue? New() => null; + public override void Restart(ref Queue? store) => store = null; + public override IEnumerable GetEnumerable(Queue? store) => store ?? Enumerable.Empty(); + + public override void Add(ref Queue? store, int? limit, T item) + { + if (limit is { } n && store is { } queue && queue.Count == n) + _ = queue.Dequeue(); + (store ??= new Queue()).Enqueue(item); + } + } + } + + sealed class Extremum : Extrema<(bool, T), T> + { + public static readonly Extrema<(bool, T), T> First = new Extremum(false); + public static readonly Extrema<(bool, T), T> Last = new Extremum(true); + + readonly bool _poppable; + Extremum(bool poppable) => _poppable = poppable; + + public override (bool, T) New() => default; + public override void Restart(ref (bool, T) store) => store = default; + + public override IEnumerable GetEnumerable((bool, T) store) => + store is (true, var item) ? Enumerable.Repeat(item, 1) : Enumerable.Empty(); + + public override void Add(ref (bool, T) store, int? limit, T item) + { + if (!_poppable && store is (true, _)) + return; + store = (true, item); + } + } + } + + // > In mathematical analysis, the maxima and minima (the respective + // > plurals of maximum and minimum) of a function, known collectively + // > as extrema (the plural of extremum), ... + // > + // > - https://en.wikipedia.org/wiki/Maxima_and_minima + + static IEnumerable ExtremaBy( + this IEnumerable source, + Extrema extrema, int? limit, + Func selector, Func comparer) + { + foreach (var item in Extrema()) + yield return item; + + IEnumerable Extrema() + { + using var e = source.GetEnumerator(); + + if (!e.MoveNext()) + return new List(); + + var store = extrema.New(); + extrema.Add(ref store, limit, e.Current); + var extremaKey = selector(e.Current); + + while (e.MoveNext()) + { + var item = e.Current; + var key = selector(item); + switch (comparer(key, extremaKey)) + { + case > 0: + extrema.Restart(ref store); + extrema.Add(ref store, limit, item); + extremaKey = key; + break; + case 0: + extrema.Add(ref store, limit, item); + break; + default: + break; + } + } + + return extrema.GetEnumerable(store); + } + } + + abstract class Extrema + { + public abstract TStore New(); + public abstract void Restart(ref TStore store); + public abstract IEnumerable GetEnumerable(TStore store); + public abstract void Add(ref TStore store, int? limit, T item); + } + } +} diff --git a/MoreLinq/MinBy.cs b/MoreLinq/MinBy.cs index 625d698df..54773b8ef 100644 --- a/MoreLinq/MinBy.cs +++ b/MoreLinq/MinBy.cs @@ -19,6 +19,7 @@ namespace MoreLinq { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; static partial class MoreEnumerable { @@ -38,10 +39,17 @@ static partial class MoreEnumerable /// The sequence of minimal elements, according to the projection. /// or is null + [Obsolete($"Use {ExtremaMembers.Minima} instead.")] + [ExcludeFromCodeCoverage] +#if !NET6_0_OR_GREATER public static IExtremaEnumerable MinBy(this IEnumerable source, Func selector) +#else + public static IExtremaEnumerable MinBy(IEnumerable source, + Func selector) +#endif { - return source.MinBy(selector, null); + return MinBy(source, selector, null); } /// @@ -61,14 +69,17 @@ public static IExtremaEnumerable MinBy(this IEnumerable< /// , /// or is null + [Obsolete($"Use {nameof(ExtremaMembers.Minima)} instead.")] + [ExcludeFromCodeCoverage] +#if !NET6_0_OR_GREATER public static IExtremaEnumerable MinBy(this IEnumerable source, Func selector, IComparer? comparer) +#else + public static IExtremaEnumerable MinBy(IEnumerable source, + Func selector, IComparer? comparer) +#endif { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (selector == null) throw new ArgumentNullException(nameof(selector)); - - comparer ??= Comparer.Default; - return new ExtremaEnumerable(source, selector, (x, y) => -Math.Sign(comparer.Compare(x, y))); + return source.Minima(selector, comparer); } } } diff --git a/MoreLinq/Minima.cs b/MoreLinq/Minima.cs new file mode 100644 index 000000000..ba9f584e7 --- /dev/null +++ b/MoreLinq/Minima.cs @@ -0,0 +1,74 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2008 Jonathan Skeet. 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 + { + /// + /// Returns the minimal elements of the given sequence, based on + /// the given projection. + /// + /// + /// This overload uses the default comparer for the projected type. + /// This operator uses deferred execution. The results are evaluated + /// and cached on first use to returned sequence. + /// + /// Type of the source sequence + /// Type of the projected element + /// Source sequence + /// Selector to use to pick the results to compare + /// The sequence of minimal elements, according to the projection. + /// or is null + + public static IExtremaEnumerable Minima(this IEnumerable source, + Func selector) + { + return source.Minima(selector, null); + } + + /// + /// Returns the minimal elements of the given sequence, based on + /// the given projection and the specified comparer for projected values. + /// + /// + /// This operator uses deferred execution. The results are evaluated + /// and cached on first use to returned sequence. + /// + /// Type of the source sequence + /// Type of the projected element + /// Source sequence + /// Selector to use to pick the results to compare + /// Comparer to use to compare projected values + /// The sequence of minimal elements, according to the projection. + /// , + /// or is null + + public static IExtremaEnumerable Minima(this IEnumerable source, + Func selector, IComparer? comparer) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (selector == null) throw new ArgumentNullException(nameof(selector)); + + comparer ??= Comparer.Default; + return new ExtremaEnumerable(source, selector, (x, y) => -Math.Sign(comparer.Compare(x, y))); + } + } +} diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index 4397916e4..f46e55965 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -52,10 +52,10 @@ - Lag - Lead - LeftJoin - - MaxBy + - Maxima - Memoize (EXPERIMENTAL) - Merge (EXPERIMENTAL) - - MinBy + - Minima - Move - OrderBy - OrderedMerge diff --git a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt index c2a9e629e..8b26bf00b 100644 --- a/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -8,18 +8,36 @@ *REMOVED*static MoreLinq.MoreEnumerable.Prepend(this System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.DistinctBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.DistinctBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.IEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.MaxBy(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.MaxBy(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.MinBy(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +*REMOVED*static MoreLinq.MoreEnumerable.MinBy(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.SkipLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.TakeLast(this System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.HashSet! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.HashSet! *REMOVED*static MoreLinq.MoreEnumerable.Windowed(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! +MoreLinq.Extensions.MaximaExtension +MoreLinq.Extensions.MinimaExtension static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! +static MoreLinq.Extensions.MaximaExtension.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.Extensions.MaximaExtension.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.Extensions.MinimaExtension.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.Extensions.MinimaExtension.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.DistinctBy(System.Collections.Generic.IEnumerable! source, System.Func! keySelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.DistinctBy(System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.MaxBy(System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.MaxBy(System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.MinBy(System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.MinBy(System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! static MoreLinq.MoreEnumerable.Prepend(System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.SkipLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.TakeLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index fa0a2a28d..baeef302c 100644 --- a/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -7,9 +7,19 @@ *REMOVED*static MoreLinq.MoreEnumerable.Concat(this System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.Prepend(this System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! *REMOVED*static MoreLinq.MoreEnumerable.Windowed(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! +MoreLinq.Extensions.MaximaExtension +MoreLinq.Extensions.MinimaExtension static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! +static MoreLinq.Extensions.MaximaExtension.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.Extensions.MaximaExtension.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.Extensions.MinimaExtension.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.Extensions.MinimaExtension.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! static MoreLinq.MoreEnumerable.Prepend(System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! diff --git a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt index 85be22323..0a76e35e0 100644 --- a/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt +++ b/MoreLinq/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt @@ -11,11 +11,21 @@ *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source) -> System.Collections.Generic.HashSet! *REMOVED*static MoreLinq.MoreEnumerable.ToHashSet(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer) -> System.Collections.Generic.HashSet! *REMOVED*static MoreLinq.MoreEnumerable.Windowed(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable!>! +MoreLinq.Extensions.MaximaExtension +MoreLinq.Extensions.MinimaExtension static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.Extensions.BatchExtension.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! +static MoreLinq.Extensions.MaximaExtension.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.Extensions.MaximaExtension.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.Extensions.MinimaExtension.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.Extensions.MinimaExtension.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! static MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable! head, T tail) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size, System.Func! resultSelector) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.Batch(this System.Collections.Generic.IEnumerable! source, int size) -> System.Collections.Generic.IEnumerable! +static MoreLinq.MoreEnumerable.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.Maxima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector) -> MoreLinq.IExtremaEnumerable! +static MoreLinq.MoreEnumerable.Minima(this System.Collections.Generic.IEnumerable! source, System.Func! selector, System.Collections.Generic.IComparer? comparer) -> MoreLinq.IExtremaEnumerable! static MoreLinq.MoreEnumerable.Prepend(System.Collections.Generic.IEnumerable! source, TSource value) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.SkipLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! static MoreLinq.MoreEnumerable.TakeLast(System.Collections.Generic.IEnumerable! source, int count) -> System.Collections.Generic.IEnumerable! diff --git a/README.md b/README.md index de6b4e622..ac9da48de 100644 --- a/README.md +++ b/README.md @@ -361,14 +361,32 @@ Performs a left outer join between two sequences. This method has 4 overloads. -### MaxBy +### ~~MaxBy~~ + +:warning: **This method is obsolete. Use [`Maxima`](#maxima) instead.** + +Returns the maxima (maximal elements) of the given sequence, based on the +given projection. + +This method has 2 overloads. + +### Maxima Returns the maxima (maximal elements) of the given sequence, based on the given projection. This method has 2 overloads. -### MinBy +### ~~MinBy~~ + +:warning: **This method is obsolete. Use [`Maxima`](#maxima) instead.** + +Returns the minima (minimal elements) of the given sequence, based on the +given projection. + +This method has 2 overloads. + +### Minima Returns the minima (minimal elements) of the given sequence, based on the given projection. diff --git a/bld/ExtensionsGenerator/Program.cs b/bld/ExtensionsGenerator/Program.cs index 87dba483d..762eae7e7 100644 --- a/bld/ExtensionsGenerator/Program.cs +++ b/bld/ExtensionsGenerator/Program.cs @@ -120,7 +120,6 @@ from md in cd.DescendantNodes().OfType() where md.ParameterList.Parameters.Count > 0 && md.ParameterList.Parameters.First().Modifiers.Any(m => m.Value is "this") && md.Modifiers.Any(m => m.Value is "public") - && md.AttributeLists.SelectMany(al => al.Attributes).All(a => a.Name.ToString() != "Obsolete") // // Build a dictionary of type abbreviations (e.g. TSource -> a, // TResult -> b, etc.) for the method's type parameters. If the From 17c6201082c8a0979d13a44035cfcd8df83a8652 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 25 Oct 2023 22:00:12 +0200 Subject: [PATCH 19/24] Remove redundant IsExternalInit package --- MoreLinq.Test/MoreLinq.Test.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/MoreLinq.Test/MoreLinq.Test.csproj b/MoreLinq.Test/MoreLinq.Test.csproj index 97e4d7bc2..8eff0287b 100644 --- a/MoreLinq.Test/MoreLinq.Test.csproj +++ b/MoreLinq.Test/MoreLinq.Test.csproj @@ -22,10 +22,6 @@ all - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - runtime; build; native; contentfiles; analyzers all From 91d3996df623934cf5a666bd34a4b039056aaa98 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 25 Oct 2023 22:51:27 +0200 Subject: [PATCH 20/24] Update all packages to latest versions --- MoreLinq.Test/.editorconfig | 6 ++++++ MoreLinq.Test/MoreLinq.Test.csproj | 14 +++++++------- MoreLinq/MoreLinq.csproj | 2 +- .../MoreLinq.ExtensionsGenerator.csproj | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/MoreLinq.Test/.editorconfig b/MoreLinq.Test/.editorconfig index d9548d45a..ffc9fb911 100644 --- a/MoreLinq.Test/.editorconfig +++ b/MoreLinq.Test/.editorconfig @@ -37,3 +37,9 @@ dotnet_diagnostic.IDE0047.severity = suggestion # IDE0022: Use expression/block body for methods dotnet_diagnostic.IDE0022.severity = none + +# NUnit1030: The type of parameter provided by the TestCaseSource does not match the type of the parameter in the Test method +dotnet_diagnostic.NUnit1030.severity = suggestion + +# Nunit1029: The number of parameters provided by the TestCaseSource does not match the number of parameters in the Test method +dotnet_diagnostic.NUnit1029.severity = suggestion diff --git a/MoreLinq.Test/MoreLinq.Test.csproj b/MoreLinq.Test/MoreLinq.Test.csproj index 8eff0287b..77e2b5cbc 100644 --- a/MoreLinq.Test/MoreLinq.Test.csproj +++ b/MoreLinq.Test/MoreLinq.Test.csproj @@ -17,7 +17,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -27,23 +27,23 @@ all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + - + diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index f46e55965..10a1bbd24 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -161,7 +161,7 @@ all - + diff --git a/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj b/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj index 9e19b8bfb..e810d01e0 100644 --- a/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj +++ b/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj @@ -6,7 +6,7 @@ - + From c01e64649860ac8f49c539b893c982095acbadb9 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 25 Oct 2023 23:44:48 +0200 Subject: [PATCH 21/24] Fix "MinBy" successor in read-me doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac9da48de..98d40de8a 100644 --- a/README.md +++ b/README.md @@ -379,7 +379,7 @@ This method has 2 overloads. ### ~~MinBy~~ -:warning: **This method is obsolete. Use [`Maxima`](#maxima) instead.** +:warning: **This method is obsolete. Use [`Minima`](#minima) instead.** Returns the minima (minimal elements) of the given sequence, based on the given projection. From 2c3f004c4be8562c4f7be1377d4ef7a25c5aec2f Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 26 Oct 2023 21:11:24 +0200 Subject: [PATCH 22/24] List required PolySharp polyfills explicitly --- MoreLinq/MoreLinq.csproj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index 10a1bbd24..4ed9e868b 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -164,6 +164,18 @@ + + + System.Index; + System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute; + System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute; + System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute; + System.Diagnostics.CodeAnalysis.MemberNotNullAttribute; + System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; + System.Runtime.CompilerServices.CallerArgumentExpressionAttribute; + + + From 02098976df09e8bdd9e9acb45c401071ee91d68c Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sat, 28 Oct 2023 15:20:43 +0200 Subject: [PATCH 23/24] Don't validate content of "Interleave" arg This is a squashed merge of PR #1031 that fixes #1029. --- MoreLinq.Test/InterleaveTest.cs | 14 -------------- MoreLinq/Interleave.cs | 2 -- 2 files changed, 16 deletions(-) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index 84db32566..5d6384d8e 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -50,20 +50,6 @@ public void TestInterleaveDisposesOnErrorAtGetEnumerator() Throws.BreakException); } - /// - /// Verify that Interleave early throw ArgumentNullException when an element - /// of otherSequences is null. - /// - [Test] - public void TestInterleaveEarlyThrowOnNullElementInOtherSequences() - { - var sequenceA = Enumerable.Range(1, 1); - var otherSequences = new IEnumerable[] { null! }; - - Assert.That(() => sequenceA.Interleave(otherSequences), - Throws.ArgumentNullException("otherSequences")); - } - /// /// Verify that interleaving disposes those enumerators that it managed /// to open successfully diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index c053341d5..501a8f667 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -54,8 +54,6 @@ public static IEnumerable Interleave(this IEnumerable sequence, params { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); if (otherSequences == null) throw new ArgumentNullException(nameof(otherSequences)); - if (otherSequences.Any(s => s == null)) - throw new ArgumentNullException(nameof(otherSequences), "One or more sequences passed to Interleave was null."); return Impl(otherSequences.Prepend(sequence)); From 092a40d82a1b280568ffa006d9a210bdec0792cd Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sat, 28 Oct 2023 15:53:05 +0200 Subject: [PATCH 24/24] Fix exception thrown for invalid "Pad"/"PadStart" width This is a squashed merge of PR #1030 that: - fixes #1027 - fixes #1028 --- MoreLinq.Test/PadStartTest.cs | 35 ++++++++++++++++------------------- MoreLinq.Test/PadTest.cs | 19 ++++++++++++++++--- MoreLinq/Pad.cs | 4 ++-- MoreLinq/PadStart.cs | 4 ++-- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/MoreLinq.Test/PadStartTest.cs b/MoreLinq.Test/PadStartTest.cs index 78d45ef4d..5e84f6e3d 100644 --- a/MoreLinq.Test/PadStartTest.cs +++ b/MoreLinq.Test/PadStartTest.cs @@ -17,21 +17,32 @@ namespace MoreLinq.Test { - using NUnit.Framework; using System; using System.Collections.Generic; + using NUnit.Framework; + using NUnit.Framework.Interfaces; [TestFixture] public class PadStartTest { - // PadStart(source, width) + static readonly IEnumerable PadStartWithNegativeWidthCases = + from e in new (string Name, TestDelegate Delegate)[] + { + ("DefaultPadding" , static () => new object[0].PadStart(-1)), + ("Padding" , static () => new object[0].PadStart(-1, -2)), + ("PaddingSelector", static () => new object[0].PadStart(-1, BreakingFunc.Of())), + } + select new TestCaseData(e.Delegate).SetName(e.Name); - [Test] - public void PadStartWithNegativeWidth() + [TestCaseSource(nameof(PadStartWithNegativeWidthCases))] + public void PadStartWithNegativeWidth(TestDelegate @delegate) { - Assert.That(() => new int[0].PadStart(-1), Throws.ArgumentException("width")); + Assert.That(@delegate, Throws.ArgumentOutOfRangeException("width") + .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(-1)); } + // PadStart(source, width) + [Test] public void PadStartIsLazy() { @@ -61,12 +72,6 @@ public void ReferenceTypeElements(ICollection source, int width, IEnume // PadStart(source, width, padding) - [Test] - public void PadStartWithPaddingWithNegativeWidth() - { - Assert.That(() => new int[0].PadStart(-1, 1), Throws.ArgumentException("width")); - } - [Test] public void PadStartWithPaddingIsLazy() { @@ -94,14 +99,6 @@ public void ReferenceTypeElements(ICollection source, int width, IEnumer } } - // PadStart(source, width, paddingSelector) - - [Test] - public void PadStartWithSelectorWithNegativeWidth() - { - Assert.That(() => new int[0].PadStart(-1, x => x), Throws.ArgumentException("width")); - } - [Test] public void PadStartWithSelectorIsLazy() { diff --git a/MoreLinq.Test/PadTest.cs b/MoreLinq.Test/PadTest.cs index cbe323d28..9a1f61949 100644 --- a/MoreLinq.Test/PadTest.cs +++ b/MoreLinq.Test/PadTest.cs @@ -17,15 +17,28 @@ namespace MoreLinq.Test { + using System; + using System.Collections.Generic; using NUnit.Framework; + using NUnit.Framework.Interfaces; [TestFixture] public class PadTest { - [Test] - public void PadNegativeWidth() + static readonly IEnumerable PadNegativeWidthCases = + from e in new (string Name, TestDelegate Delegate)[] + { + ("DefaultPadding" , static () => new object[0].Pad(-1)), + ("Padding" , static () => new object[0].Pad(-1, -2)), + ("PaddingSelector", static () => new object[0].Pad(-1, BreakingFunc.Of())), + } + select new TestCaseData(e.Delegate).SetName(e.Name); + + [TestCaseSource(nameof(PadNegativeWidthCases))] + public void PadNegativeWidth(TestDelegate @delegate) { - Assert.That(() => new object[0].Pad(-1), Throws.ArgumentException("width")); + Assert.That(@delegate, Throws.ArgumentOutOfRangeException("width") + .And.Property(nameof(ArgumentOutOfRangeException.ActualValue)).EqualTo(-1)); } [Test] diff --git a/MoreLinq/Pad.cs b/MoreLinq/Pad.cs index 9fc728c35..eeb4dda77 100644 --- a/MoreLinq/Pad.cs +++ b/MoreLinq/Pad.cs @@ -77,7 +77,7 @@ static partial class MoreEnumerable public static IEnumerable Pad(this IEnumerable source, int width, TSource padding) { if (source == null) throw new ArgumentNullException(nameof(source)); - if (width < 0) throw new ArgumentException(null, nameof(width)); + if (width < 0) throw new ArgumentOutOfRangeException(nameof(width), width, null); return PadImpl(source, width, padding, null); } @@ -109,7 +109,7 @@ public static IEnumerable Pad(this IEnumerable source { if (source == null) throw new ArgumentNullException(nameof(source)); if (paddingSelector == null) throw new ArgumentNullException(nameof(paddingSelector)); - if (width < 0) throw new ArgumentException(null, nameof(width)); + if (width < 0) throw new ArgumentOutOfRangeException(nameof(width), width, null); return PadImpl(source, width, default, paddingSelector); } diff --git a/MoreLinq/PadStart.cs b/MoreLinq/PadStart.cs index 2979da6b3..5e9e380d1 100644 --- a/MoreLinq/PadStart.cs +++ b/MoreLinq/PadStart.cs @@ -76,7 +76,7 @@ static partial class MoreEnumerable public static IEnumerable PadStart(this IEnumerable source, int width, TSource padding) { if (source == null) throw new ArgumentNullException(nameof(source)); - if (width < 0) throw new ArgumentException(null, nameof(width)); + if (width < 0) throw new ArgumentOutOfRangeException(nameof(width), width, null); return PadStartImpl(source, width, padding, null); } @@ -110,7 +110,7 @@ public static IEnumerable PadStart(this IEnumerable s { if (source == null) throw new ArgumentNullException(nameof(source)); if (paddingSelector == null) throw new ArgumentNullException(nameof(paddingSelector)); - if (width < 0) throw new ArgumentException(null, nameof(width)); + if (width < 0) throw new ArgumentOutOfRangeException(nameof(width), width, null); return PadStartImpl(source, width, default, paddingSelector); }