Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combinations #647

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions MoreLinq.Test/CombinationsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#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 NUnit.Framework;

/// <summary>
/// Tests of the Combinations() family of extension methods.
/// </summary>
[TestFixture]
public class CombinationsTest
{
/// <summary>
/// Verify that Combinations() behaves in a lazy manner.
/// </summary>
[Test]
public void TestCombinationsIsLazy()
{
new BreakingSequence<int>().Combinations();
}

/// <summary>
/// Verify that the only Combinations of an empty sequence is the empty sequence.
/// </summary>
[Test]
public void TestEmptySequenceCombinations()
{
var sequence = Enumerable.Repeat(0, 0);
var result = sequence.Combinations();

Assert.That(result.Single(), Is.EqualTo(sequence));
}

/// <summary>
/// Verify that Combinations are returned in increasing size, starting with the empty set.
/// </summary>
[Test]
public void TestCombinationsInIncreasingOrder()
{
const int count = 5;
var sequence = Enumerable.Range(1, count);
var result = sequence.Combinations();

var prevCombinations = Enumerable.Empty<int>();
foreach (var Combinations in result)
{
Assert.GreaterOrEqual(Combinations.Count, prevCombinations.Count());
prevCombinations = Combinations;
}
}

/// <summary>
/// Verify that the number of Combinations returned is correct, but don't verify the Combinations contents.
/// </summary>
[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}
}
};

/// <summary>
/// Verify that the complete Combinations results for a known set are correct.
/// </summary>
[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);
}

/// <summary>
/// Verify that the partial Combinations results for a known set are correct.
/// </summary>
[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++;
}
}
}
}
15 changes: 15 additions & 0 deletions MoreLinq.Test/SubsetTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Generic;

namespace MoreLinq.Test
{
using System;
Expand Down Expand Up @@ -116,6 +118,19 @@ public void TestAllSubsetsExpectedResults()
Assert.That(subset, Is.EqualTo(expectedSubsets[index++]));
}

/// <summary>
/// See issue #645
/// </summary>
[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);
}

/// <summary>
/// Verify that the number of subsets for a given subset-size is correct.
/// </summary>
Expand Down
55 changes: 55 additions & 0 deletions MoreLinq/Combinations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#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
{
/// <summary>
/// Generate all the possible combination of the items from the input sequence.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence</typeparam>
/// <param name="sequence">Sequence for which to produce combination</param>
/// <returns>A sequence of all combination from the input sequence</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="sequence"/> is <see langword="null"/></exception>
public static IEnumerable<IList<T>> Combinations<T>(this IEnumerable<T> sequence)
{
if (sequence == null) throw new ArgumentNullException(nameof(sequence));

return sequence.Subsets().SelectMany(Permutations);
}

/// <summary>
/// Generate all the possible combination of <paramref name="size"/> items from the input <paramref name="sequence"/>.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence</typeparam>
/// <param name="sequence">Sequence for which to produce combination</param>
/// <param name="size">The combinations size</param>
/// <returns>A sequence of all combination from the input sequence</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="sequence"/> is <see langword="null"/></exception>
public static IEnumerable<IList<T>> Combinations<T>(this IEnumerable<T> sequence, int size)
{
if (sequence == null) throw new ArgumentNullException(nameof(sequence));

return sequence.Subsets(size).SelectMany(Permutations);
}
}
}
28 changes: 28 additions & 0 deletions MoreLinq/Extensions.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,34 @@ public static IEnumerable<TResult> Choose<T, TResult>(this IEnumerable<T> source

}

/// <summary><c>Combinations</c> extension.</summary>

[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
public static partial class CombinationsExtension
{
/// <summary>
/// Generate all the possible combination of the items from the input sequence.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence</typeparam>
/// <param name="sequence">Sequence for which to produce combination</param>
/// <returns>A sequence of all combination from the input sequence</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="sequence"/> is <see langword="null"/></exception>
public static IEnumerable<IList<T>> Combinations<T>(this IEnumerable<T> sequence)
=> MoreEnumerable.Combinations(sequence);

/// <summary>
/// Generate all the possible combination of <paramref name="size"/> items from the input <paramref name="sequence"/>.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence</typeparam>
/// <param name="sequence">Sequence for which to produce combination</param>
/// <param name="size">The combinations size</param>
/// <returns>A sequence of all combination from the input sequence</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="sequence"/> is <see langword="null"/></exception>
public static IEnumerable<IList<T>> Combinations<T>(this IEnumerable<T> sequence, int size)
=> MoreEnumerable.Combinations(sequence, size);

}

/// <summary><c>CompareCount</c> extension.</summary>

[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
Expand Down
4 changes: 3 additions & 1 deletion MoreLinq/Subsets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ public static IEnumerable<IList<T>> Subsets<T>(this IEnumerable<T> sequence, int
// preconditions. This however, needs to be carefully considered - and perhaps
// may change after further thought and review.

return new SubsetGenerator<T>(sequence, subsetSize);
return subsetSize == 0
? (IEnumerable<IList<T>>) new[] {new List<T>()}
: new SubsetGenerator<T>(sequence, subsetSize);
}

/// <summary>
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down