From 88e87bbccf3dcd4425fb2ca466c6c14a86d07a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Thu, 19 Jan 2023 17:03:02 +0800 Subject: [PATCH 1/5] Copy-avoidance optimizations Support [ReadOnly]Span, because unlike arrays, it cannot be cast to IEnumerable without an extra copy. Extend arrays to I[ReadOnly]List. For arrays this interface behaves identically, but it can also support other implementations without an extra copy. Add an InPlace version of SelectVariation to save a copy if the caller allows modifications on the input data. --- src/Numerics/Combinatorics.cs | 196 ++++++++++++++++++++++++++++++++-- 1 file changed, 188 insertions(+), 8 deletions(-) diff --git a/src/Numerics/Combinatorics.cs b/src/Numerics/Combinatorics.cs index 0f60a66f0..8d44c5ca7 100644 --- a/src/Numerics/Combinatorics.cs +++ b/src/Numerics/Combinatorics.cs @@ -152,7 +152,25 @@ public static int[] GeneratePermutation(int n, System.Random randomSource = null /// /// 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) + public static void SelectPermutationInplace(this IList data, System.Random randomSource = null) + { + var random = randomSource ?? SystemRandomSource.Default; + + // Fisher-Yates Shuffling + for (int i = data.Count - 1; i > 0; i--) + { + int swapIndex = random.Next(i + 1); + (data[i], data[swapIndex]) = (data[swapIndex], data[i]); + } + } + + /// + /// 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(this Span data, System.Random randomSource = null) { var random = randomSource ?? SystemRandomSource.Default; @@ -184,6 +202,26 @@ public static IEnumerable SelectPermutation(this IEnumerable data, Syst } } + /// + /// 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 ReadOnlySpan 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. /// @@ -257,12 +295,12 @@ public static bool[] GenerateCombination(int n, int k, System.Random randomSourc /// 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(); + IReadOnlyList array = data as IReadOnlyList ?? 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."); + if (elementsToChoose > array.Count) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), $"elementsToChoose must be smaller than or equal to data.Count."); - bool[] mask = GenerateCombination(array.Length, elementsToChoose, randomSource); + bool[] mask = GenerateCombination(array.Count, elementsToChoose, randomSource); for (int i = 0; i < mask.Length; i++) { @@ -273,6 +311,29 @@ public static IEnumerable SelectCombination(this IEnumerable data, int } } + /// + /// 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 ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) + { + if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); + if (elementsToChoose > data.Length) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), $"elementsToChoose must be smaller than or equal to data.Count."); + + bool[] mask = GenerateCombination(data.Length, elementsToChoose, randomSource); + + for (int i = 0; i < mask.Length; i++) + { + if (mask[i]) + { + yield return data[i]; + } + } + } + /// /// Generates a random combination, with repetition, by randomly selecting k of N elements. /// @@ -307,8 +368,8 @@ public static IEnumerable SelectCombinationWithRepetition(this IEnumerable { 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); + IReadOnlyList array = data as IReadOnlyList ?? data.ToArray(); + int[] mask = GenerateCombinationWithRepetition(array.Count, elementsToChoose, randomSource); for (int i = 0; i < mask.Length; i++) { @@ -319,6 +380,28 @@ public static IEnumerable SelectCombinationWithRepetition(this IEnumerable } } + /// + /// 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 ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) + { + if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); + + int[] mask = GenerateCombinationWithRepetition(data.Length, elementsToChoose, randomSource); + + for (int i = 0; i < mask.Length; i++) + { + for (int j = 0; j < mask[i]; j++) + { + yield return data[i]; + } + } + } + /// /// Generate a random variation, without repetition, by randomly selecting k of n elements with order. /// Implemented using partial Fisher-Yates Shuffling. @@ -406,6 +489,7 @@ public static BigInteger[] GenerateVariation(BigInteger n, int k, System.Random /// /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. /// Implemented using partial Fisher-Yates Shuffling. + /// This algorithm copies rather than modifies your original data. If you want to avoid copying, use the -InPlace version. /// /// The data source to choose from. /// Number of elements (k) to choose from the set. Each element is chosen at most once. @@ -428,6 +512,83 @@ public static IEnumerable SelectVariation(this IEnumerable data, int el } } + /// + /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. + /// Implemented using partial Fisher-Yates Shuffling. + /// This algorithm copies rather than modifies your original data. If you want to avoid copying, use the -InPlace version. + /// + /// 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 ReadOnlySpan 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]; + } + } + + /// + /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. + /// Implemented using partial Fisher-Yates Shuffling. + /// This algorithm modifies rather than copies your original data. If you want to avoid modifications, use the non-InPlace version. + /// + /// 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 SelectVariationInPlace(this IList data, int elementsToChoose, System.Random randomSource = null) + { + var random = randomSource ?? SystemRandomSource.Default; + + if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); + if (elementsToChoose > data.Count) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "elementsToChoose must be smaller than or equal to data.Count."); + + // Partial Fisher-Yates Shuffling + for (int i = data.Count - 1; i >= data.Count - elementsToChoose; i--) + { + int swapIndex = random.Next(i + 1); + yield return data[swapIndex]; + data[swapIndex] = data[i]; + } + } + + /// + /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. + /// Implemented using partial Fisher-Yates Shuffling. + /// This algorithm modifies rather than copies your original data. If you want to avoid modifications, use the non-InPlace version. + /// + /// 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 Span 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. /// @@ -458,13 +619,32 @@ public static IEnumerable SelectVariationWithRepetition(this IEnumerable array = data as IReadOnlyList ?? data.ToArray(); + int[] indices = GenerateVariationWithRepetition(array.Count, elementsToChoose, randomSource); for (int i = 0; i < indices.Length; i++) { yield return array[indices[i]]; } } + + /// + /// 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 ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) + { + if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); + + int[] indices = GenerateVariationWithRepetition(data.Length, elementsToChoose, randomSource); + + for (int i = 0; i < indices.Length; i++) + { + yield return data[indices[i]]; + } + } } } From 057f4a0ba59962bb94dbf107c0cd82a0f59badf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Thu, 19 Jan 2023 17:48:50 +0800 Subject: [PATCH 2/5] [ReadOnly]Span does not support iterators. Return arrays instead. --- src/Numerics/Combinatorics.cs | 68 ++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/src/Numerics/Combinatorics.cs b/src/Numerics/Combinatorics.cs index 8d44c5ca7..1b2d4d6d1 100644 --- a/src/Numerics/Combinatorics.cs +++ b/src/Numerics/Combinatorics.cs @@ -32,6 +32,7 @@ using System.Linq; using MathNet.Numerics.Random; using System.Numerics; +using System.Net; namespace MathNet.Numerics { @@ -208,18 +209,13 @@ public static IEnumerable SelectPermutation(this IEnumerable data, Syst /// /// 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 ReadOnlySpan data, System.Random randomSource = null) + public static T[] SelectPermutation(this ReadOnlySpan data, System.Random randomSource = null) { + //Return array but not IEnumerable because ReadOnlySpan does not support iterators 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]; - } + array.SelectPermutationInplace(random); + return array; } /// @@ -318,20 +314,23 @@ public static IEnumerable SelectCombination(this IEnumerable data, int /// 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 ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) + public static T[] SelectCombination(this ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) { + //Return array but not IEnumerable because ReadOnlySpan does not support iterators if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); if (elementsToChoose > data.Length) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), $"elementsToChoose must be smaller than or equal to data.Count."); bool[] mask = GenerateCombination(data.Length, elementsToChoose, randomSource); - + T[] output = new T[elementsToChoose]; + elementsToChoose = 0; for (int i = 0; i < mask.Length; i++) { if (mask[i]) { - yield return data[i]; + output[elementsToChoose++] = data[i]; } } + return output; } /// @@ -387,19 +386,22 @@ public static IEnumerable SelectCombinationWithRepetition(this IEnumerable /// 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 ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) + public static T[] SelectCombinationWithRepetition(this ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) { + //Return array but not IEnumerable because ReadOnlySpan does not support iterators if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); int[] mask = GenerateCombinationWithRepetition(data.Length, elementsToChoose, randomSource); - + T[] output = new T[elementsToChoose]; + elementsToChoose = 0; for (int i = 0; i < mask.Length; i++) { for (int j = 0; j < mask[i]; j++) { - yield return data[i]; + output[elementsToChoose++] = data[i]; } } + return output; } /// @@ -521,21 +523,25 @@ public static IEnumerable SelectVariation(this IEnumerable data, int el /// 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 ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) + public static T[] SelectVariation(this ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) { + //Return array but not IEnumerable because ReadOnlySpan does not support iterators 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."); - + T[] output = new T[elementsToChoose]; + int iEnd = array.Length - elementsToChoose; + elementsToChoose = 0; // Partial Fisher-Yates Shuffling - for (int i = array.Length - 1; i >= array.Length - elementsToChoose; i--) + for (int i = array.Length - 1; i >= iEnd; i--) { int swapIndex = random.Next(i + 1); - yield return array[swapIndex]; + output[elementsToChoose++] = array[swapIndex]; array[swapIndex] = array[i]; } + return output; } /// @@ -572,21 +578,24 @@ public static IEnumerable SelectVariationInPlace(this IList data, int e /// 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 Span data, int elementsToChoose, System.Random randomSource = null) + public static T[] SelectVariationInPlace(this Span data, int elementsToChoose, System.Random randomSource = null) { + //Return array but not IEnumerable because Span does not support iterators 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."); - + if (elementsToChoose > data.Length) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "elementsToChoose must be smaller than or equal to data.Count."); + T[] output = new T[elementsToChoose]; + int iEnd = data.Length - elementsToChoose; + elementsToChoose = 0; // Partial Fisher-Yates Shuffling - for (int i = array.Length - 1; i >= array.Length - elementsToChoose; i--) + for (int i = data.Length - 1; i >= iEnd; i--) { int swapIndex = random.Next(i + 1); - yield return array[swapIndex]; - array[swapIndex] = array[i]; + output[elementsToChoose++] = data[swapIndex]; + data[swapIndex] = data[i]; } + return output; } /// @@ -635,16 +644,17 @@ public static IEnumerable SelectVariationWithRepetition(this IEnumerableNumber 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 ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) + public static T[] SelectVariationWithRepetition(this ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) { if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); int[] indices = GenerateVariationWithRepetition(data.Length, elementsToChoose, randomSource); - + T[] output = new T[elementsToChoose]; for (int i = 0; i < indices.Length; i++) { - yield return data[indices[i]]; + output[i] = data[indices[i]]; } + return output; } } } From 351ed2b5fb21566cbcd8103d4a54edd1b96006d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Thu, 19 Jan 2023 18:13:43 +0800 Subject: [PATCH 3/5] [ReadOnly]Span is only supported by .Net Core 2.1 and above. Add conditional compilation symbols. --- src/Numerics/Combinatorics.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Numerics/Combinatorics.cs b/src/Numerics/Combinatorics.cs index 1b2d4d6d1..83200480a 100644 --- a/src/Numerics/Combinatorics.cs +++ b/src/Numerics/Combinatorics.cs @@ -32,7 +32,6 @@ using System.Linq; using MathNet.Numerics.Random; using System.Numerics; -using System.Net; namespace MathNet.Numerics { @@ -164,7 +163,7 @@ public static void SelectPermutationInplace(this IList data, System.Random (data[i], data[swapIndex]) = (data[swapIndex], data[i]); } } - +#if NETCOREAPP2_1_OR_GREATER /// /// 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. @@ -182,7 +181,7 @@ public static void SelectPermutationInplace(this Span data, System.Random (data[i], data[swapIndex]) = (data[swapIndex], data[i]); } } - +#endif /// /// Select a random permutation from a data sequence by returning the provided data in random order. /// Implemented using Fisher-Yates Shuffling. @@ -202,7 +201,7 @@ public static IEnumerable SelectPermutation(this IEnumerable data, Syst array[k] = array[i]; } } - +#if NETCOREAPP2_1_OR_GREATER /// /// Select a random permutation from a data sequence by returning the provided data in random order. /// Implemented using Fisher-Yates Shuffling. @@ -217,7 +216,7 @@ public static T[] SelectPermutation(this ReadOnlySpan data, System.Random array.SelectPermutationInplace(random); return array; } - +#endif /// /// Generate a random combination, without repetition, by randomly selecting some of N elements. /// @@ -306,7 +305,7 @@ public static IEnumerable SelectCombination(this IEnumerable data, int } } } - +#if NETCOREAPP2_1_OR_GREATER /// /// Select a random combination, without repetition, from a data sequence by selecting k elements in original order. /// @@ -332,7 +331,7 @@ public static T[] SelectCombination(this ReadOnlySpan data, int elementsTo } return output; } - +#endif /// /// Generates a random combination, with repetition, by randomly selecting k of N elements. /// @@ -378,7 +377,7 @@ public static IEnumerable SelectCombinationWithRepetition(this IEnumerable } } } - +#if NETCOREAPP2_1_OR_GREATER /// /// Select a random combination, with repetition, from a data sequence by selecting k elements in original order. /// @@ -403,7 +402,7 @@ public static T[] SelectCombinationWithRepetition(this ReadOnlySpan data, } return output; } - +#endif /// /// Generate a random variation, without repetition, by randomly selecting k of n elements with order. /// Implemented using partial Fisher-Yates Shuffling. @@ -513,7 +512,7 @@ public static IEnumerable SelectVariation(this IEnumerable data, int el array[swapIndex] = array[i]; } } - +#if NETCOREAPP2_1_OR_GREATER /// /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. /// Implemented using partial Fisher-Yates Shuffling. @@ -543,7 +542,7 @@ public static T[] SelectVariation(this ReadOnlySpan data, int elementsToCh } return output; } - +#endif /// /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. /// Implemented using partial Fisher-Yates Shuffling. @@ -568,7 +567,7 @@ public static IEnumerable SelectVariationInPlace(this IList data, int e data[swapIndex] = data[i]; } } - +#if NETCOREAPP2_1_OR_GREATER /// /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. /// Implemented using partial Fisher-Yates Shuffling. @@ -597,7 +596,7 @@ public static T[] SelectVariationInPlace(this Span data, int elementsToCho } return output; } - +#endif /// /// Generate a random variation, with repetition, by randomly selecting k of n elements with order. /// @@ -636,7 +635,7 @@ public static IEnumerable SelectVariationWithRepetition(this IEnumerable /// Select a random variation, with repetition, from a data sequence by randomly selecting k elements in random order. /// @@ -656,5 +655,6 @@ public static T[] SelectVariationWithRepetition(this ReadOnlySpan data, in } return output; } +#endif } } From 4d2bff3c2c16212cd117c8a0d522bcce5603422a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Fri, 20 Jan 2023 22:31:08 +0800 Subject: [PATCH 4/5] Supports specifying a segment of input or output. Simplify some essentially same algorithms. --- src/Numerics/Combinatorics.cs | 279 ++++++++++++++++++++++++++-------- 1 file changed, 213 insertions(+), 66 deletions(-) diff --git a/src/Numerics/Combinatorics.cs b/src/Numerics/Combinatorics.cs index 83200480a..f8a4d93d3 100644 --- a/src/Numerics/Combinatorics.cs +++ b/src/Numerics/Combinatorics.cs @@ -32,6 +32,7 @@ using System.Linq; using MathNet.Numerics.Random; using System.Numerics; +using System.Xml; namespace MathNet.Numerics { @@ -135,15 +136,7 @@ public static double Permutations(int n) 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; + return Enumerable.Range(0,n).SelectPermutation(randomSource).ToArray(); } /// @@ -151,25 +144,38 @@ public static int[] GeneratePermutation(int n, System.Random randomSource = null /// 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. + /// Elements before this index won't be reordered + /// Elements after this index won't be reordered /// The random number generator to use. Optional; the default random source will be used if null. - public static void SelectPermutationInplace(this IList data, System.Random randomSource = null) + public static void SelectPermutationInplace(this IList data, int startIndex, int endIndex, System.Random randomSource = null) { var random = randomSource ?? SystemRandomSource.Default; // Fisher-Yates Shuffling - for (int i = data.Count - 1; i > 0; i--) + for (int i = endIndex; i > startIndex; i--) { - int swapIndex = random.Next(i + 1); + int swapIndex = random.Next(startIndex, i + 1); (data[i], data[swapIndex]) = (data[swapIndex], data[i]); } } -#if NETCOREAPP2_1_OR_GREATER + /// /// 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(this IList data, System.Random randomSource = null) + { + data.SelectPermutationInplace(0, data.Count - 1, randomSource); + } +#if NETCOREAPP2_1_OR_GREATER + /// + /// Select a random permutation, without repetition, from a data span by reordering the provided span in-place. + /// Implemented using Fisher-Yates Shuffling. The provided data span will be modified. + /// + /// The data span to be reordered. The span 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(this Span data, System.Random randomSource = null) { var random = randomSource ?? SystemRandomSource.Default; @@ -184,39 +190,55 @@ public static void SelectPermutationInplace(this Span data, System.Random #endif /// /// Select a random permutation from a data sequence by returning the provided data in random order. - /// Implemented using Fisher-Yates Shuffling. + /// Implemented using Fisher-Yates Shuffling. The provided data array will be modified but not copied. /// /// The data elements to be reordered. + /// Elements before this index won't be reordered + /// Elements after this index won't 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) + public static IEnumerable SelectPermutationWithoutCopy(this IList data, int startIndex, int endIndex, 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--) + for (int i = endIndex; i >= startIndex; i--) { - int k = random.Next(i + 1); - yield return array[k]; - array[k] = array[i]; + int k = random.Next(startIndex, i + 1); + yield return data[k]; + data[k] = data[i]; } } #if NETCOREAPP2_1_OR_GREATER /// /// Select a random permutation from a data sequence by returning the provided data in random order. - /// Implemented using Fisher-Yates Shuffling. + /// Implemented using Fisher-Yates Shuffling. The provided data memory will be modified but not copied. /// /// The data elements to be reordered. /// The random number generator to use. Optional; the default random source will be used if null. - public static T[] SelectPermutation(this ReadOnlySpan data, System.Random randomSource = null) + public static IEnumerable SelectPermutationWithoutCopy(this Memory data, System.Random randomSource = null) { - //Return array but not IEnumerable because ReadOnlySpan does not support iterators var random = randomSource ?? SystemRandomSource.Default; - T[] array = data.ToArray(); - array.SelectPermutationInplace(random); - return array; + + // Fisher-Yates Shuffling + for (int i = data.Length - 1; i >= 0; i--) + { + int k = random.Next(i + 1); + yield return data.Span[k]; + data.Span[k] = data.Span[i]; + } } #endif + /// + /// Select a random permutation from a data sequence by returning the provided data in random order. + /// Implemented using Fisher-Yates Shuffling. The provided data array will be copied but not modified. + /// + /// 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) + { + return data.ToArray().SelectPermutationWithoutCopy(0, data.Count() - 1, randomSource); + } + /// /// Generate a random combination, without repetition, by randomly selecting some of N elements. /// @@ -234,7 +256,6 @@ public static bool[] GenerateCombination(int n, System.Random randomSource = nul { mask[i] = random.NextBoolean(); } - return mask; } @@ -305,6 +326,31 @@ public static IEnumerable SelectCombination(this IEnumerable data, int } } } + + /// + /// Select a random combination, without repetition, from a data sequence by selecting k elements in original order. + /// + /// The data source to choose from. + /// Elements before this index won't be chosen. + /// Number of elements in the data segment to be chosen 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 IReadOnlyList data, int startIndex, int segmentLength, int elementsToChoose, System.Random randomSource = null) + { + if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); + if (elementsToChoose > segmentLength) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), $"elementsToChoose must be smaller than or equal to segmentLength."); + + bool[] mask = GenerateCombination(segmentLength, elementsToChoose, randomSource); + + for (int i = 0; i < mask.Length; i++) + { + if (mask[i]) + { + yield return data[startIndex + i]; + } + } + } #if NETCOREAPP2_1_OR_GREATER /// /// Select a random combination, without repetition, from a data sequence by selecting k elements in original order. @@ -313,23 +359,63 @@ public static IEnumerable SelectCombination(this IEnumerable data, int /// 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 T[] SelectCombination(this ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) + public static IEnumerable SelectCombination(this ReadOnlyMemory data, int elementsToChoose, System.Random randomSource = null) { - //Return array but not IEnumerable because ReadOnlySpan does not support iterators if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); - if (elementsToChoose > data.Length) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), $"elementsToChoose must be smaller than or equal to data.Count."); + if (elementsToChoose > data.Length) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), $"elementsToChoose must be smaller than or equal to data.Length."); bool[] mask = GenerateCombination(data.Length, elementsToChoose, randomSource); - T[] output = new T[elementsToChoose]; - elementsToChoose = 0; + for (int i = 0; i < mask.Length; i++) { if (mask[i]) { - output[elementsToChoose++] = data[i]; + yield return data.Span[i]; + } + } + } + + /// + /// Select a random combination, without repetition, from a data sequence by selecting k elements in original order. + /// + /// The data source to choose from. + /// The chosen combination, in the original order. Number of elements to choose is determined by its Length + /// The random number generator to use. Optional; the default random source will be used if null. + public static void SelectCombination(this ReadOnlySpan data, Span output, System.Random randomSource = null) + { + if (output.Length > data.Length) throw new ArgumentOutOfRangeException("output.Length", $"output.Length must be smaller than or equal to data.Length."); + + bool[] mask = GenerateCombination(data.Length, output.Length, randomSource); + int startIndex = 0; + for (int i = 0; i < mask.Length; i++) + { + if (mask[i]) + { + output[startIndex++] = data[i]; + } + } + } + + /// + /// 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 chosen combination, in the original order. + /// Chosen elements will be copied to output starting from this index. + /// The random number generator to use. Optional; the default random source will be used if null. + public static void SelectCombination(this ReadOnlySpan data, int elementsToChoose, IList output, int startIndex = 0, System.Random randomSource = null) + { + if (elementsToChoose > data.Length) throw new ArgumentOutOfRangeException("elementsToChoose", $"elementsToChoose must be smaller than or equal to data.Length."); + + bool[] mask = GenerateCombination(data.Length, elementsToChoose, randomSource); + for (int i = 0; i < mask.Length; i++) + { + if (mask[i]) + { + output[startIndex++] = data[i]; } } - return output; } #endif /// @@ -377,6 +463,30 @@ public static IEnumerable SelectCombinationWithRepetition(this IEnumerable } } } + + /// + /// Select a random combination, with repetition, from a data sequence by selecting k elements in original order. + /// + /// The data source to choose from. + /// Elements before this index won't be chosen. + /// Number of elements in the data segment to be chosen 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 IReadOnlyList data, int startIndex, int segmentLength, int elementsToChoose, System.Random randomSource = null) + { + if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); + + int[] mask = GenerateCombinationWithRepetition(segmentLength, elementsToChoose, randomSource); + + for (int i = 0; i < mask.Length; i++) + { + for (int j = 0; j < mask[i]; j++) + { + yield return data[startIndex + i]; + } + } + } #if NETCOREAPP2_1_OR_GREATER /// /// Select a random combination, with repetition, from a data sequence by selecting k elements in original order. @@ -385,22 +495,60 @@ public static IEnumerable SelectCombinationWithRepetition(this IEnumerable /// 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 T[] SelectCombinationWithRepetition(this ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) + public static IEnumerable SelectCombinationWithRepetition(this ReadOnlyMemory data, int elementsToChoose, System.Random randomSource = null) { - //Return array but not IEnumerable because ReadOnlySpan does not support iterators if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); int[] mask = GenerateCombinationWithRepetition(data.Length, elementsToChoose, randomSource); - T[] output = new T[elementsToChoose]; - elementsToChoose = 0; + for (int i = 0; i < mask.Length; i++) { for (int j = 0; j < mask[i]; j++) { - output[elementsToChoose++] = data[i]; + yield return data.Span[i]; + } + } + } + + /// + /// Select a random combination, with repetition, from a data sequence by selecting k elements in original order. + /// + /// The data source to choose from. + /// The chosen combination, in the original order. Number of elements to choose is determined by its Length + /// 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 void SelectCombinationWithRepetition(this ReadOnlySpan data, Span output, System.Random randomSource = null) + { + int[] mask = GenerateCombinationWithRepetition(data.Length, output.Length, randomSource); + int startIndex = 0; + for (int i = 0; i < mask.Length; i++) + { + for (int j = 0; j < mask[i]; j++) + { + output[startIndex++] = data[i]; + } + } + } + + /// + /// 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. + /// The chosen combination, in the original order. + /// Chosen elements will be copied to output starting from this index. + /// 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 void SelectCombinationWithRepetition(this ReadOnlySpan data, int elementsToChoose, IList output, int startIndex = 0, System.Random randomSource = null) + { + int[] mask = GenerateCombinationWithRepetition(data.Length, elementsToChoose, randomSource); + for (int i = 0; i < mask.Length; i++) + { + for (int j = 0; j < mask[i]; j++) + { + output[startIndex++] = data[i]; } } - return output; } #endif /// @@ -489,8 +637,31 @@ public static BigInteger[] GenerateVariation(BigInteger n, int k, System.Random /// /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. - /// Implemented using partial Fisher-Yates Shuffling. - /// This algorithm copies rather than modifies your original data. If you want to avoid copying, use the -InPlace version. + /// Implemented using partial Fisher-Yates Shuffling. The provided data array will be modified but not copied. + /// + /// 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 SelectVariationWithoutCopy(this IList data,int startIndex,int endIndex, int elementsToChoose, System.Random randomSource = null) + { + var random = randomSource ?? SystemRandomSource.Default; + + if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); + if (elementsToChoose > data.Count) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "elementsToChoose must be smaller than or equal to data.Count."); + + // Partial Fisher-Yates Shuffling + for (int i = endIndex; i >= endIndex - elementsToChoose+1; i--) + { + int swapIndex = random.Next(i + 1); + yield return data[swapIndex]; + data[swapIndex] = data[i]; + } + } + + /// + /// 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 provided data array will be copied but not modified. /// /// The data source to choose from. /// Number of elements (k) to choose from the set. Each element is chosen at most once. @@ -543,30 +714,6 @@ public static T[] SelectVariation(this ReadOnlySpan data, int elementsToCh return output; } #endif - /// - /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. - /// Implemented using partial Fisher-Yates Shuffling. - /// This algorithm modifies rather than copies your original data. If you want to avoid modifications, use the non-InPlace version. - /// - /// 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 SelectVariationInPlace(this IList data, int elementsToChoose, System.Random randomSource = null) - { - var random = randomSource ?? SystemRandomSource.Default; - - if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); - if (elementsToChoose > data.Count) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "elementsToChoose must be smaller than or equal to data.Count."); - - // Partial Fisher-Yates Shuffling - for (int i = data.Count - 1; i >= data.Count - elementsToChoose; i--) - { - int swapIndex = random.Next(i + 1); - yield return data[swapIndex]; - data[swapIndex] = data[i]; - } - } #if NETCOREAPP2_1_OR_GREATER /// /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. From 7f235a8f506dc438577fada0060212d1e59ebdea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E5=8D=9A=E6=8B=89=E9=85=B1?= Date: Fri, 20 Jan 2023 22:33:44 +0800 Subject: [PATCH 5/5] Supports specifying a segment of input or output. Simplify some essentially same algorithms. --- src/Numerics/Combinatorics.cs | 180 ++++++++++++---------------------- 1 file changed, 61 insertions(+), 119 deletions(-) diff --git a/src/Numerics/Combinatorics.cs b/src/Numerics/Combinatorics.cs index f8a4d93d3..ca643a793 100644 --- a/src/Numerics/Combinatorics.cs +++ b/src/Numerics/Combinatorics.cs @@ -32,7 +32,6 @@ using System.Linq; using MathNet.Numerics.Random; using System.Numerics; -using System.Xml; namespace MathNet.Numerics { @@ -136,7 +135,7 @@ public static double Permutations(int n) 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)."); - return Enumerable.Range(0,n).SelectPermutation(randomSource).ToArray(); + return Enumerable.Range(0, n).SelectPermutation(randomSource).ToArray(); } /// @@ -256,6 +255,7 @@ public static bool[] GenerateCombination(int n, System.Random randomSource = nul { mask[i] = random.NextBoolean(); } + return mask; } @@ -565,24 +565,7 @@ public static int[] GenerateVariation(int n, int k, System.Random randomSource = 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; + return Enumerable.Range(0,n).SelectPermutation(randomSource).Take(k).ToArray(); } /// @@ -637,31 +620,7 @@ public static BigInteger[] GenerateVariation(BigInteger n, int k, System.Random /// /// 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 provided data array will be modified but not copied. - /// - /// 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 SelectVariationWithoutCopy(this IList data,int startIndex,int endIndex, int elementsToChoose, System.Random randomSource = null) - { - var random = randomSource ?? SystemRandomSource.Default; - - if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); - if (elementsToChoose > data.Count) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "elementsToChoose must be smaller than or equal to data.Count."); - - // Partial Fisher-Yates Shuffling - for (int i = endIndex; i >= endIndex - elementsToChoose+1; i--) - { - int swapIndex = random.Next(i + 1); - yield return data[swapIndex]; - data[swapIndex] = data[i]; - } - } - - /// - /// 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 provided data array will be copied but not modified. + /// 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. @@ -669,81 +628,12 @@ public static IEnumerable SelectVariationWithoutCopy(this IList data,in /// 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."); + if (elementsToChoose > data.Count()) 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]; - } + return data.SelectPermutation(randomSource).Take(elementsToChoose); } -#if NETCOREAPP2_1_OR_GREATER - /// - /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. - /// Implemented using partial Fisher-Yates Shuffling. - /// This algorithm copies rather than modifies your original data. If you want to avoid copying, use the -InPlace version. - /// - /// 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 T[] SelectVariation(this ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) - { - //Return array but not IEnumerable because ReadOnlySpan does not support iterators - 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."); - T[] output = new T[elementsToChoose]; - int iEnd = array.Length - elementsToChoose; - elementsToChoose = 0; - // Partial Fisher-Yates Shuffling - for (int i = array.Length - 1; i >= iEnd; i--) - { - int swapIndex = random.Next(i + 1); - output[elementsToChoose++] = array[swapIndex]; - array[swapIndex] = array[i]; - } - return output; - } -#endif -#if NETCOREAPP2_1_OR_GREATER - /// - /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. - /// Implemented using partial Fisher-Yates Shuffling. - /// This algorithm modifies rather than copies your original data. If you want to avoid modifications, use the non-InPlace version. - /// - /// 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 T[] SelectVariationInPlace(this Span data, int elementsToChoose, System.Random randomSource = null) - { - //Return array but not IEnumerable because Span does not support iterators - var random = randomSource ?? SystemRandomSource.Default; - - if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); - if (elementsToChoose > data.Length) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "elementsToChoose must be smaller than or equal to data.Count."); - T[] output = new T[elementsToChoose]; - int iEnd = data.Length - elementsToChoose; - elementsToChoose = 0; - // Partial Fisher-Yates Shuffling - for (int i = data.Length - 1; i >= iEnd; i--) - { - int swapIndex = random.Next(i + 1); - output[elementsToChoose++] = data[swapIndex]; - data[swapIndex] = data[i]; - } - return output; - } -#endif /// /// Generate a random variation, with repetition, by randomly selecting k of n elements with order. /// @@ -782,6 +672,27 @@ public static IEnumerable SelectVariationWithRepetition(this IEnumerable + /// Select a random variation, with repetition, from a data sequence by randomly selecting k elements in random order. + /// + /// The data source to choose from. + /// Elements before this index won't be chosen. + /// Number of elements in the data segment to be chosen 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 IReadOnlyList data, int startIndex, int segmentLength, int elementsToChoose, System.Random randomSource = null) + { + if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); + + int[] indices = GenerateVariationWithRepetition(segmentLength, elementsToChoose, randomSource); + + for (int i = 0; i < indices.Length; i++) + { + yield return data[startIndex + indices[i]]; + } + } #if NETCOREAPP2_1_OR_GREATER /// /// Select a random variation, with repetition, from a data sequence by randomly selecting k elements in random order. @@ -790,17 +701,48 @@ public static IEnumerable SelectVariationWithRepetition(this IEnumerableNumber 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 T[] SelectVariationWithRepetition(this ReadOnlySpan data, int elementsToChoose, System.Random randomSource = null) + public static IEnumerable SelectVariationWithRepetition(this ReadOnlyMemory data, int elementsToChoose, System.Random randomSource = null) { if (elementsToChoose < 0) throw new ArgumentOutOfRangeException(nameof(elementsToChoose), "Value must not be negative (zero is ok)."); int[] indices = GenerateVariationWithRepetition(data.Length, elementsToChoose, randomSource); - T[] output = new T[elementsToChoose]; + + for (int i = 0; i < indices.Length; i++) + { + yield return data.Span[indices[i]]; + } + } + + /// + /// Select a random variation, with repetition, from a data sequence by randomly selecting k elements in random order. + /// + /// The data source to choose from. + /// The chosen variation with repetition, in random order. Number of elements to choose is determined by its Length + /// The random number generator to use. Optional; the default random source will be used if null. + public static void SelectVariationWithRepetition(this ReadOnlySpan data, Span output, System.Random randomSource = null) + { + int[] indices = GenerateVariationWithRepetition(data.Length, output.Length, randomSource); for (int i = 0; i < indices.Length; i++) { output[i] = data[indices[i]]; } - return output; + } + + /// + /// 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 chosen variation with repetition, in random order. + /// Chosen elements will be copied to output starting from this index. + /// The random number generator to use. Optional; the default random source will be used if null. + public static void SelectVariationWithRepetition(this ReadOnlySpan data, int elementsToChoose, IList output, int startIndex = 0, System.Random randomSource = null) + { + int[] indices = GenerateVariationWithRepetition(data.Length, elementsToChoose, randomSource); + for (int i = 0; i < indices.Length; i++) + { + output[startIndex + i] = data[indices[i]]; + } } #endif }