From f7b06e2955492e590ff47decace92fed93b2a527 Mon Sep 17 00:00:00 2001 From: Daniel Sword Date: Mon, 19 Nov 2018 19:59:37 -0800 Subject: [PATCH 1/7] Combinatorics: add a method to invert a permutation. --- src/Numerics/Combinatorics.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Numerics/Combinatorics.cs b/src/Numerics/Combinatorics.cs index 63e66c9d9..fe0503d8c 100644 --- a/src/Numerics/Combinatorics.cs +++ b/src/Numerics/Combinatorics.cs @@ -146,6 +146,20 @@ public static int[] GeneratePermutation(int n, System.Random randomSource = null return indices; } + /// + /// Inverts a permutation, returning a new permutation. + /// No checks are performed to validate the input (apart from null checks). + /// + /// Permutation to invert. + /// The inverted permutation. + public static int[] InvertPermutation(int[] permutation) + { + if (permutation == null) throw new ArgumentNullException(nameof(permutation)); + var result = Enumerable.Range(0, permutation.Length).ToArray(); + Sorting.Sort(permutation, result); + return result; + } + /// /// Select a random permutation, without repetition, from a data array by reordering the provided array in-place. /// Implemented using Fisher-Yates Shuffling. The provided data array will be modified. From 83681d2b8cba5bddef235e4bd3359e267cb11273 Mon Sep 17 00:00:00 2001 From: Daniel Sword Date: Mon, 19 Nov 2018 20:00:54 -0800 Subject: [PATCH 2/7] Combinatorics: add a test class for new combinatoric methods. --- .../CombinatoricsTests/PermutationTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs diff --git a/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs b/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs new file mode 100644 index 000000000..95ff4bb5e --- /dev/null +++ b/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace MathNet.Numerics.Tests.CombinatoricsTests +{ + [TestFixture] + public class PermutationTests + { + private IEqualityComparer _comparer = new ArrayEqualityComparer(); + class ArrayEqualityComparer : IEqualityComparer + { + public bool Equals(int[] x, int[] y) + { + return StructuralComparisons.StructuralEqualityComparer.Equals(x, y); + } + + public int GetHashCode(int[] obj) + { + return StructuralComparisons.StructuralEqualityComparer.GetHashCode(obj); + } + } + + } +} From 2ca6fefeef76fa2df8290889f721f7a0d64a93f7 Mon Sep 17 00:00:00 2001 From: Daniel Sword Date: Mon, 19 Nov 2018 20:01:30 -0800 Subject: [PATCH 3/7] Combinatorics: add test for InvertPermutation method. --- .../CombinatoricsTests/PermutationTests.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs b/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs index 95ff4bb5e..67d1f4a29 100644 --- a/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs +++ b/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs @@ -25,5 +25,18 @@ public int GetHashCode(int[] obj) } } + + [TestCase(2,5,0,1,4,3)] + public void InversePermutationInverts(params int[] permutation) + { + var identityPermutation = Enumerable.Range(0, permutation.Length).ToArray(); + var permuted = permutation.Select(p => identityPermutation[p]).ToArray(); + var inversePermutation = Combinatorics.InvertPermutation(permutation); + var shouldBeIdentity = inversePermutation.Select(p => permuted[p]).ToArray(); + + bool shouldBeTrue = _comparer.Equals(identityPermutation, shouldBeIdentity); + + Assert.IsTrue(shouldBeTrue); + } } } From adfa6c4837c808f96daa501330151198c504e415 Mon Sep 17 00:00:00 2001 From: Daniel Sword Date: Mon, 19 Nov 2018 20:02:15 -0800 Subject: [PATCH 4/7] Combinatorics: add method for lexicographic generation of all permutations of a set. --- src/Numerics/Combinatorics.cs | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/Numerics/Combinatorics.cs b/src/Numerics/Combinatorics.cs index fe0503d8c..ff62df10c 100644 --- a/src/Numerics/Combinatorics.cs +++ b/src/Numerics/Combinatorics.cs @@ -160,6 +160,43 @@ public static int[] InvertPermutation(int[] permutation) return result; } + /// + /// Lazily generates all permutations lexicographically. + /// Note that the same array is yielded on each iteration, + /// so care is needed when converting to a collection. + /// + /// Number of (distinguishable) elements in the set. + /// A lazy enumeration of all unique permutations of the set. + public static IEnumerable GenerateAllPermutations(int n) + { + // This method follows Algorithm L found in + // Knuth, "Combinatorial Algorithms" The Art of Computer Science vol. 4A, 7.2.1.2 + + if (n < 0) throw new ArgumentOutOfRangeException(nameof(n), Resources.ArgumentNotNegative); + + // We'll repeatedly yield the following array as we generate the permutations. + // The first entry is just the items in lexicographical order. + var a = Enumerable.Range(0, n).ToArray(); + int j, l, swap; + while (true) + { + yield return a; + j = n - 1; + // Following Knuth's notation, the entry a_j is found at position a[j-1]. + while (j > 0 && a[j - 1] >= a[j]) + j--; + if (j == 0) break; // Terminates the algorithm. + l = n; + while (a[j - 1] >= a[l - 1]) + l--; + // Swap aj and al + swap = a[j - 1]; + a[j - 1] = a[l - 1]; + a[l - 1] = swap; + Array.Reverse(a, j, n - j); + } + } + /// /// Select a random permutation, without repetition, from a data array by reordering the provided array in-place. /// Implemented using Fisher-Yates Shuffling. The provided data array will be modified. From 63b8fbc187b325b7a37fc3a4f62eb7ae3ebbca76 Mon Sep 17 00:00:00 2001 From: Daniel Sword Date: Mon, 19 Nov 2018 20:10:15 -0800 Subject: [PATCH 5/7] Combinatorics: add tests for generated permutations. --- .../CombinatoricsTests/PermutationTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs b/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs index 67d1f4a29..eb0e73abb 100644 --- a/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs +++ b/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs @@ -25,6 +25,20 @@ public int GetHashCode(int[] obj) } } + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(5)] + public void NumberOfPermutationsGeneratedMatchesNFactorial(int n) + { + // See the xml comment on lazy enumeration danger. + var permutations = Combinatorics.GenerateAllPermutations(n).Select(pi => pi.AsEnumerable().ToArray()).ToArray(); + var count = permutations.Distinct(_comparer).Count(); + var expected = Combinatorics.Permutations(n); + Assert.AreEqual(expected,count); + } + [TestCase(2,5,0,1,4,3)] public void InversePermutationInverts(params int[] permutation) From e1f1eda4705921c089d4b9649101635f5aa87445 Mon Sep 17 00:00:00 2001 From: Daniel Sword Date: Mon, 19 Nov 2018 20:10:50 -0800 Subject: [PATCH 6/7] Combinatorics: add a method for lexicographic generation of all possible subsets of a set. --- src/Numerics/Combinatorics.cs | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/Numerics/Combinatorics.cs b/src/Numerics/Combinatorics.cs index ff62df10c..ffc12d433 100644 --- a/src/Numerics/Combinatorics.cs +++ b/src/Numerics/Combinatorics.cs @@ -197,6 +197,78 @@ public static IEnumerable GenerateAllPermutations(int n) } } + /// + /// Lazily generates all combinations lexicographically. + /// Note that the same array is yielded on each iteration, + /// so care is needed when converting to a collection. + /// + /// Number of items in the set. + /// Number of items to choose, without replacement. + /// A lazy enumeration of all unique subsets of size k. + public static IEnumerable GenerateAllCombinations(int n, int k) + { + // This method follows Algorithm T found in + // Knuth, "Combinatorial Algorithms," The Art of Computer Science vol. 4A, 7.2.1.3 + + if (n < 0) throw new ArgumentOutOfRangeException(nameof(n), Resources.ArgumentNotNegative); + if (k < 0) throw new ArgumentOutOfRangeException(nameof(k), Resources.ArgumentNotNegative); + + // We'll repeatedly yield the following array as we generate the permutations. + // The first entry is just the items in lexicographical order. + var a = new int[k]; + + var c = Enumerable.Range(0, k + 2).ToArray(); + c[k] = n; + c[k + 1] = 0; + + int x; + int j = k; + if (k <= n) // Returns empty enumerable if k > n; + { + if (k == n) + yield return c.Take(k).ToArray(); + else + while (true) + { + for (int i = 0; i < k; i++) + a[i] = c[i]; + yield return a; + + if (j > 0) + { + x = j; + } + else + { + // Easy case? + if (c[0] + 1 < c[1]) + { + c[0] = c[0] + 1; + continue; + } + + // We differ slightly here from Knuth. + // Instead of j=2, we set to 1. + // But we put j++ at the beginning of the do loop that immediately follows. + j = 1; + + do + { + j++; + c[j - 2] = j - 2; + x = c[j - 1] + 1; + } while (x == c[j]); + + if (j > k) break; + } + + + c[j - 1] = x; + j--; + } + } + } + /// /// Select a random permutation, without repetition, from a data array by reordering the provided array in-place. /// Implemented using Fisher-Yates Shuffling. The provided data array will be modified. From 8d90e7f8231aee9b0921d6fc2d40ef1f976a6566 Mon Sep 17 00:00:00 2001 From: Daniel Sword Date: Mon, 19 Nov 2018 20:11:08 -0800 Subject: [PATCH 7/7] Combinatorics: add tests for generated combinations. --- .../CombinatoricsTests/PermutationTests.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs b/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs index eb0e73abb..c4f88e84b 100644 --- a/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs +++ b/src/Numerics.Tests/CombinatoricsTests/PermutationTests.cs @@ -39,6 +39,21 @@ public void NumberOfPermutationsGeneratedMatchesNFactorial(int n) Assert.AreEqual(expected,count); } + [TestCase(1,1)] + [TestCase(2,1)] + [TestCase(3,3)] + [TestCase(4,2)] + [TestCase(4,3)] + [TestCase(7,3)] + public void NumberOfCombinationsGeneratedMatchesNChooseK(int n,int k) + { + // See the xml comment on lazy enumeration danger. + var combinations = Combinatorics.GenerateAllCombinations(n,k).Select(pi => pi.AsEnumerable().ToArray()).ToArray(); + var count = combinations.Distinct(_comparer).Count(); + var expected = Combinatorics.Combinations(n,k); + Assert.AreEqual(expected, count); + } + [TestCase(2,5,0,1,4,3)] public void InversePermutationInverts(params int[] permutation)