From d18b25121a05973b05d00fb3997b75f0b5109af3 Mon Sep 17 00:00:00 2001 From: Orace Date: Tue, 5 Nov 2019 17:43:14 +0100 Subject: [PATCH 1/3] Fix #645 --- MoreLinq.Test/SubsetTest.cs | 15 +++++++++++++++ MoreLinq/Subsets.cs | 4 +++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/MoreLinq.Test/SubsetTest.cs b/MoreLinq.Test/SubsetTest.cs index cc4725a2d..f02f4ef0d 100644 --- a/MoreLinq.Test/SubsetTest.cs +++ b/MoreLinq.Test/SubsetTest.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace MoreLinq.Test { using System; @@ -116,6 +118,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); + var expected = new[] {new int[0]}; + + CollectionAssert.AreEquivalent(expected, actual); + } + /// /// Verify that the number of subsets for a given subset-size is correct. /// diff --git a/MoreLinq/Subsets.cs b/MoreLinq/Subsets.cs index 1491903bd..cd0ea2dda 100644 --- a/MoreLinq/Subsets.cs +++ b/MoreLinq/Subsets.cs @@ -108,7 +108,9 @@ public static IEnumerable> Subsets(this IEnumerable sequence, int // preconditions. This however, needs to be carefully considered - and perhaps // may change after further thought and review. - return new SubsetGenerator(sequence, subsetSize); + return subsetSize == 0 + ? (IEnumerable>) new[] {new List()} + : new SubsetGenerator(sequence, subsetSize); } /// From 3ab29e7d792cbc1d496df7725cd78741e919bb26 Mon Sep 17 00:00:00 2001 From: Orace Date: Tue, 5 Nov 2019 21:31:30 +0100 Subject: [PATCH 2/3] Add Combinations. --- MoreLinq.Test/CombinationsTest.cs | 133 ++++++++++++++++++++++++++++++ MoreLinq/Combinations.cs | 38 +++++++++ MoreLinq/Extensions.g.cs | 28 +++++++ README.md | 7 ++ 4 files changed, 206 insertions(+) create mode 100644 MoreLinq.Test/CombinationsTest.cs create mode 100644 MoreLinq/Combinations.cs diff --git a/MoreLinq.Test/CombinationsTest.cs b/MoreLinq.Test/CombinationsTest.cs new file mode 100644 index 000000000..072de0bbb --- /dev/null +++ b/MoreLinq.Test/CombinationsTest.cs @@ -0,0 +1,133 @@ +using System.Collections.Generic; + +namespace MoreLinq.Test +{ + using System; + using NUnit.Framework; + + /// + /// Tests of the Combinations() family of extension methods. + /// + [TestFixture] + public class CombinationsTest + { + /// + /// Verify that Combinations() behaves in a lazy manner. + /// + [Test] + public void TestCombinationsIsLazy() + { + new BreakingSequence().Combinations(); + } + + /// + /// Verify that the only Combinations of an empty sequence is the empty sequence. + /// + [Test] + public void TestEmptySequenceCombinations() + { + var sequence = Enumerable.Repeat(0, 0); + var result = sequence.Combinations(); + + Assert.That(result.Single(), Is.EqualTo(sequence)); + } + + /// + /// Verify that Combinations are returned in increasing size, starting with the empty set. + /// + [Test] + public void TestCombinationsInIncreasingOrder() + { + const int count = 5; + var sequence = Enumerable.Range(1, count); + var result = sequence.Combinations(); + + var prevCombinations = Enumerable.Empty(); + foreach (var Combinations in result) + { + Assert.GreaterOrEqual(Combinations.Count, prevCombinations.Count()); + prevCombinations = Combinations; + } + } + + /// + /// Verify that the number of Combinations returned is correct, but don't verify the Combinations contents. + /// + [TestCase(0, ExpectedResult = 1)] + [TestCase(1, ExpectedResult = 2)] + [TestCase(2, ExpectedResult = 5)] + [TestCase(3, ExpectedResult = 16)] + [TestCase(4, ExpectedResult = 65)] + public int TestAllCombinationsExpectedCount(int sourceSize) + { + return Enumerable.Range(1, sourceSize).Combinations().Count(); + } + + private int[][][] Expected { get; } = + { + new[] + { + new int[] { } + }, + + new[] + { + new[] {1}, new[] {2}, new[] {3}, new[] {4} + }, + + new[] + { + new[] {1, 2}, new[] {1, 3}, new[] {1, 4}, + new[] {2, 1}, new[] {2, 3}, new[] {2, 4}, + new[] {3, 1}, new[] {3, 2}, new[] {3, 4}, + new[] {4, 1}, new[] {4, 2}, new[] {4, 3} + }, + + new[] + { + new[] {1, 2, 3}, new[] {1, 2, 4}, new[] {1, 3, 2}, new[] {1, 3, 4}, new[] {1, 4, 2}, new[] {1, 4, 3}, + new[] {2, 1, 3}, new[] {2, 1, 4}, new[] {2, 3, 1}, new[] {2, 3, 4}, new[] {2, 4, 1}, new[] {2, 4, 3}, + new[] {3, 1, 2}, new[] {3, 1, 4}, new[] {3, 2, 1}, new[] {3, 2, 4}, new[] {3, 4, 1}, new[] {3, 4, 2}, + new[] {4, 1, 2}, new[] {4, 1, 3}, new[] {4, 2, 1}, new[] {4, 2, 3}, new[] {4, 3, 1}, new[] {4, 3, 2} + }, + + new[] + { + new[] {1, 2, 3, 4}, new[] {1, 2, 4, 3}, new[] {1, 3, 2, 4}, new[] {1, 3, 4, 2}, new[] {1, 4, 2, 3}, new[] {1, 4, 3, 2}, + new[] {2, 1, 3, 4}, new[] {2, 1, 4, 3}, new[] {2, 3, 1, 4}, new[] {2, 3, 4, 1}, new[] {2, 4, 1, 3}, new[] {2, 4, 3, 1}, + new[] {3, 1, 2, 4}, new[] {3, 1, 4, 2}, new[] {3, 2, 1, 4}, new[] {3, 2, 4, 1}, new[] {3, 4, 1, 2}, new[] {3, 4, 2, 1}, + new[] {4, 1, 2, 3}, new[] {4, 1, 3, 2}, new[] {4, 2, 1, 3}, new[] {4, 2, 3, 1}, new[] {4, 3, 1, 2}, new[] {4, 3, 2, 1} + } + }; + + /// + /// Verify that the complete Combinations results for a known set are correct. + /// + [Test] + public void TestAllCombinationsExpectedResults() + { + var sequence = Enumerable.Range(1, 4); + var actual = sequence.Combinations().ToList(); + var expected = Expected.SelectMany(a => a); + + CollectionAssert.AreEquivalent(expected, actual); + } + + /// + /// Verify that the partial Combinations results for a known set are correct. + /// + [Test] + public void TestAllPartialCombinationsExpectedResults() + { + var sequence = Enumerable.Range(1, 4).ToList(); + + var i = 1; + foreach (var expected in Expected.Skip(1)) + { + var actual = sequence.Combinations(i).ToList(); + CollectionAssert.AreEquivalent(expected, actual); + i++; + } + } + } +} diff --git a/MoreLinq/Combinations.cs b/MoreLinq/Combinations.cs new file mode 100644 index 000000000..82a4438bc --- /dev/null +++ b/MoreLinq/Combinations.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MoreLinq +{ + public static partial class MoreEnumerable + { + /// + /// Generate all the possible combination of the items from the input sequence. + /// + /// The type of the elements in the sequence + /// Sequence for which to produce combination + /// A sequence of all combination from the input sequence + /// Thrown if is + public static IEnumerable> Combinations(this IEnumerable sequence) + { + if (sequence == null) throw new ArgumentNullException(nameof(sequence)); + + return sequence.Subsets().SelectMany(Permutations); + } + + /// + /// Generate all the possible combination of items from the input . + /// + /// The type of the elements in the sequence + /// Sequence for which to produce combination + /// The combinations size + /// A sequence of all combination from the input sequence + /// Thrown if is + public static IEnumerable> Combinations(this IEnumerable sequence, int size) + { + if (sequence == null) throw new ArgumentNullException(nameof(sequence)); + + return sequence.Subsets(size).SelectMany(Permutations); + } + } +} diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index 0b1211058..7c2d02efc 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -1061,6 +1061,34 @@ public static IEnumerable Choose(this IEnumerable source } + /// Combinations extension. + + [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] + public static partial class CombinationsExtension + { + /// + /// Generate all the possible combination of the items from the input sequence. + /// + /// The type of the elements in the sequence + /// Sequence for which to produce combination + /// A sequence of all combination from the input sequence + /// Thrown if is + public static IEnumerable> Combinations(this IEnumerable sequence) + => MoreEnumerable.Combinations(sequence); + + /// + /// Generate all the possible combination of items from the input . + /// + /// The type of the elements in the sequence + /// Sequence for which to produce combination + /// The combinations size + /// A sequence of all combination from the input sequence + /// Thrown if is + public static IEnumerable> Combinations(this IEnumerable sequence, int size) + => MoreEnumerable.Combinations(sequence, size); + + } + /// CompareCount extension. [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] diff --git a/README.md b/README.md index d6923b81d..691f39e57 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,13 @@ sequence of result elements for source elements where the function returns a couple (2-tuple) having a `true` as its first element and result as the second. +### Combinations + +Returns a sequence of all of the combinations of any (or given) size that are +part of the input sequence. + +This method has 2 overloads. + ### CompareCount Compares two sequences and returns an integer that indicates whether the From cbbcedb858e29dd4ef8d540c40ac48df9982e664 Mon Sep 17 00:00:00 2001 From: Orace Date: Tue, 5 Nov 2019 22:28:06 +0100 Subject: [PATCH 3/3] Added headers in Combinations and CombinationsTest --- MoreLinq.Test/CombinationsTest.cs | 20 +++++++++++++++++--- MoreLinq/Combinations.cs | 23 ++++++++++++++++++++--- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/MoreLinq.Test/CombinationsTest.cs b/MoreLinq.Test/CombinationsTest.cs index 072de0bbb..8645e9fbc 100644 --- a/MoreLinq.Test/CombinationsTest.cs +++ b/MoreLinq.Test/CombinationsTest.cs @@ -1,8 +1,22 @@ -using System.Collections.Generic; +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2019 Pierre Lando. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#endregion namespace MoreLinq.Test { - using System; using NUnit.Framework; /// @@ -19,7 +33,7 @@ public void TestCombinationsIsLazy() { new BreakingSequence().Combinations(); } - + /// /// Verify that the only Combinations of an empty sequence is the empty sequence. /// diff --git a/MoreLinq/Combinations.cs b/MoreLinq/Combinations.cs index 82a4438bc..03e381206 100644 --- a/MoreLinq/Combinations.cs +++ b/MoreLinq/Combinations.cs @@ -1,9 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Linq; +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2019 Pierre Lando. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#endregion namespace MoreLinq { + using System; + using System.Collections.Generic; + using System.Linq; + public static partial class MoreEnumerable { ///