// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // // Copyright (c) 2009-2015 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections.Generic; using System.Linq; using IStation.Numerics.Random; namespace IStation.Numerics { /// /// Enumerative Combinatorics and Counting. /// public static class Combinatorics { /// /// Count the number of possible variations without repetition. /// The order matters and each object can be chosen only once. /// /// Number of elements in the set. /// Number of elements to choose from the set. Each element is chosen at most once. /// Maximum number of distinct variations. public static double Variations(int n, int k) { if (k < 0 || n < 0 || k > n) { return 0; } return Math.Floor( 0.5 + Math.Exp( SpecialFunctions.FactorialLn(n) - SpecialFunctions.FactorialLn(n - k))); } /// /// Count the number of possible variations with repetition. /// The order matters and each object can be chosen more than once. /// /// Number of elements in the set. /// Number of elements to choose from the set. Each element is chosen 0, 1 or multiple times. /// Maximum number of distinct variations with repetition. public static double VariationsWithRepetition(int n, int k) { if (k < 0 || n < 0) { return 0; } return Math.Pow(n, k); } /// /// Count the number of possible combinations without repetition. /// The order does not matter and each object can be chosen only once. /// /// Number of elements in the set. /// Number of elements to choose from the set. Each element is chosen at most once. /// Maximum number of combinations. public static double Combinations(int n, int k) { return SpecialFunctions.Binomial(n, k); } /// /// Count the number of possible combinations with repetition. /// The order does not matter and an object can be chosen more than once. /// /// Number of elements in the set. /// Number of elements to choose from the set. Each element is chosen 0, 1 or multiple times. /// Maximum number of combinations with repetition. public static double CombinationsWithRepetition(int n, int k) { if (k < 0 || n < 0 || (n == 0 && k > 0)) { return 0; } if (n == 0 && k == 0) { return 1; } return Math.Floor( 0.5 + Math.Exp( SpecialFunctions.FactorialLn(n + k - 1) - SpecialFunctions.FactorialLn(k) - SpecialFunctions.FactorialLn(n - 1))); } /// /// Count the number of possible permutations (without repetition). /// /// Number of (distinguishable) elements in the set. /// Maximum number of permutations without repetition. public static double Permutations(int n) { return SpecialFunctions.Factorial(n); } /// /// Generate a random permutation, without repetition, by generating the index numbers 0 to N-1 and shuffle them randomly. /// Implemented using Fisher-Yates Shuffling. /// /// An array of length N that contains (in any order) the integers of the interval [0, N). /// Number of (distinguishable) elements in the set. /// The random number generator to use. Optional; the default random source will be used if null. public static int[] GeneratePermutation(int n, System.Random randomSource = null) { if (n < 0) throw new ArgumentOutOfRangeException(nameof(n), "Value must not be negative (zero is ok)."); int[] indices = new int[n]; for (int i = 0; i < indices.Length; i++) { indices[i] = i; } SelectPermutationInplace(indices, randomSource); return indices; } /// /// 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. /// /// The data array to be reordered. The array will be modified by this routine. /// The random number generator to use. Optional; the default random source will be used if null. public static void SelectPermutationInplace(T[] data, System.Random randomSource = null) { var random = randomSource ?? SystemRandomSource.Default; // Fisher-Yates Shuffling for (int i = data.Length - 1; i > 0; i--) { int swapIndex = random.Next(i + 1); T swap = data[i]; data[i] = data[swapIndex]; data[swapIndex] = swap; } } /// /// Select a random permutation from a data sequence by returning the provided data in random order. /// Implemented using Fisher-Yates Shuffling. /// /// The data elements to be reordered. /// The random number generator to use. Optional; the default random source will be used if null. public static IEnumerable SelectPermutation(this IEnumerable data, System.Random randomSource = null) { var random = randomSource ?? SystemRandomSource.Default; T[] array = data.ToArray(); // Fisher-Yates Shuffling for (int i = array.Length - 1; i >= 0; i--) { int k = random.Next(i + 1); yield return array[k]; array[k] = array[i]; } } /// /// Generate a random combination, without repetition, by randomly selecting some of N elements. /// /// Number of elements in the set. /// The random number generator to use. Optional; the default random source will be used if null. /// Boolean mask array of length N, for each item true if it is selected. public static bool[] GenerateCombination(int n, System.Random randomSource = null) { if (n < 0) throw new ArgumentOutOfRangeException(nameof(n), "Value must not be negative (zero is ok)."); var random = randomSource ?? SystemRandomSource.Default; bool[] mask = new bool[n]; for (int i = 0; i < mask.Length; i++) { mask[i] = random.NextBoolean(); } return mask; } /// /// Generate a random combination, without repetition, by randomly selecting k of N elements. /// /// Number of elements in the set. /// Number of elements to choose from the set. Each element is chosen at most once. /// The random number generator to use. Optional; the default random source will be used if null. /// Boolean mask array of length N, for each item true if it is selected. public static bool[] GenerateCombination(int n, int k, System.Random randomSource = null) { if (n < 0) throw new ArgumentOutOfRangeException(nameof(n), "Value must not be negative (zero is ok)."); if (k < 0) throw new ArgumentOutOfRangeException(nameof(k), "Value must not be negative (zero is ok)."); if (k > n) throw new ArgumentOutOfRangeException(nameof(k), $"{"k"} must be smaller than or equal to {"n"}."); var random = randomSource ?? SystemRandomSource.Default; bool[] mask = new bool[n]; if (k*3 < n) { // just pick and try int selectionCount = 0; while (selectionCount < k) { int index = random.Next(n); if (!mask[index]) { mask[index] = true; selectionCount++; } } return mask; } // based on permutation int[] permutation = GeneratePermutation(n, random); for (int i = 0; i < k; i++) { mask[permutation[i]] = true; } return mask; } /// /// Select a random combination, without repetition, from a data sequence by selecting k elements in original order. /// /// The data source to choose from. /// Number of elements (k) to choose from the data set. Each element is chosen at most once. /// The random number generator to use. Optional; the default random source will be used if null. /// The chosen combination, in the original order. public static IEnumerable SelectCombination(this IEnumerable data, int elementsToChoose, System.Random randomSource = null) { T[] array = data as T[] ?? data.ToArray(); if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); if (elementsToChoose > array.Length) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), $"elementsToChoose must be smaller than or equal to data.Count."); bool[] mask = GenerateCombination(array.Length, elementsToChoose, randomSource); for (int i = 0; i < mask.Length; i++) { if (mask[i]) { yield return array[i]; } } } /// /// Generates a random combination, with repetition, by randomly selecting k of N elements. /// /// Number of elements in the set. /// Number of elements to choose from the set. Elements can be chosen more than once. /// The random number generator to use. Optional; the default random source will be used if null. /// Integer mask array of length N, for each item the number of times it was selected. public static int[] GenerateCombinationWithRepetition(int n, int k, System.Random randomSource = null) { if (n < 0) throw new ArgumentOutOfRangeException(nameof(n), "Value must not be negative (zero is ok)."); if (k < 0) throw new ArgumentOutOfRangeException(nameof(k), "Value must not be negative (zero is ok)."); var random = randomSource ?? SystemRandomSource.Default; int[] mask = new int[n]; for (int i = 0; i < k; i++) { mask[random.Next(n)]++; } return mask; } /// /// Select a random combination, with repetition, from a data sequence by selecting k elements in original order. /// /// The data source to choose from. /// Number of elements (k) to choose from the data set. Elements can be chosen more than once. /// The random number generator to use. Optional; the default random source will be used if null. /// The chosen combination with repetition, in the original order. public static IEnumerable SelectCombinationWithRepetition(this IEnumerable data, int elementsToChoose, System.Random randomSource = null) { if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); T[] array = data as T[] ?? data.ToArray(); int[] mask = GenerateCombinationWithRepetition(array.Length, elementsToChoose, randomSource); for (int i = 0; i < mask.Length; i++) { for (int j = 0; j < mask[i]; j++) { yield return array[i]; } } } /// /// Generate a random variation, without repetition, by randomly selecting k of n elements with order. /// Implemented using partial Fisher-Yates Shuffling. /// /// Number of elements in the set. /// Number of elements to choose from the set. Each element is chosen at most once. /// The random number generator to use. Optional; the default random source will be used if null. /// An array of length K that contains the indices of the selections as integers of the interval [0, N). public static int[] GenerateVariation(int n, int k, System.Random randomSource = null) { if (n < 0) throw new ArgumentOutOfRangeException(nameof(n), "Value must not be negative (zero is ok)."); if (k < 0) throw new ArgumentOutOfRangeException(nameof(k), "Value must not be negative (zero is ok)."); if (k > n) throw new ArgumentOutOfRangeException(nameof(k), $"k must be smaller than or equal to n."); var random = randomSource ?? SystemRandomSource.Default; int[] indices = new int[n]; for (int i = 0; i < indices.Length; i++) { indices[i] = i; } // Partial Fisher-Yates Shuffling int[] selection = new int[k]; for (int i = 0, j = indices.Length - 1; i < selection.Length; i++, j--) { int swapIndex = random.Next(j + 1); selection[i] = indices[swapIndex]; indices[swapIndex] = indices[j]; } return selection; } /// /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. /// Implemented using partial Fisher-Yates Shuffling. /// /// The data source to choose from. /// Number of elements (k) to choose from the set. Each element is chosen at most once. /// The random number generator to use. Optional; the default random source will be used if null. /// The chosen variation, in random order. public static IEnumerable SelectVariation(this IEnumerable data, int elementsToChoose, System.Random randomSource = null) { var random = randomSource ?? SystemRandomSource.Default; T[] array = data.ToArray(); if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); if (elementsToChoose > array.Length) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "elementsToChoose must be smaller than or equal to data.Count."); // Partial Fisher-Yates Shuffling for (int i = array.Length - 1; i >= array.Length - elementsToChoose; i--) { int swapIndex = random.Next(i + 1); yield return array[swapIndex]; array[swapIndex] = array[i]; } } /// /// Generate a random variation, with repetition, by randomly selecting k of n elements with order. /// /// Number of elements in the set. /// Number of elements to choose from the set. Elements can be chosen more than once. /// The random number generator to use. Optional; the default random source will be used if null. /// An array of length K that contains the indices of the selections as integers of the interval [0, N). public static int[] GenerateVariationWithRepetition(int n, int k, System.Random randomSource = null) { if (n < 0) throw new ArgumentOutOfRangeException(nameof(n), "Value must not be negative (zero is ok)."); if (k < 0) throw new ArgumentOutOfRangeException(nameof(k), "Value must not be negative (zero is ok)."); var random = randomSource ?? SystemRandomSource.Default; int[] ret = new int[k]; random.NextInt32s(ret, 0, n); return ret; } /// /// Select a random variation, with repetition, from a data sequence by randomly selecting k elements in random order. /// /// The data source to choose from. /// Number of elements (k) to choose from the data set. Elements can be chosen more than once. /// The random number generator to use. Optional; the default random source will be used if null. /// The chosen variation with repetition, in random order. public static IEnumerable SelectVariationWithRepetition(this IEnumerable data, int elementsToChoose, System.Random randomSource = null) { if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); T[] array = data as T[] ?? data.ToArray(); int[] indices = GenerateVariationWithRepetition(array.Length, elementsToChoose, randomSource); for (int i = 0; i < indices.Length; i++) { yield return array[indices[i]]; } } } }