C#对给定n和给定最大数组长度的一组字符串进行置换

时间:2019-05-23 05:02:52

标签: c# arrays permutation

如何根据设置的长度对字符串集进行排列,我说一个数组中有50个值,并且只想组合以逗号分隔的24个值示例:(string1,string2,string3),但从未重复组合和顺序。我下面有这段代码。

class Program
{
    static void Main(string[] args)
    {
        //var values1 = new[] { 1, 2, 3, 4, 5 };

        //foreach (var permutation in values1.GetPermutations())
        //{
        //    Console.WriteLine(string.Join(", ", permutation));
        //}

        var values2 = new[] { "asd", "das", "sad", "q1we", "asd" };

        foreach (var permutation in values2.GetPermutations())
        {
            Console.WriteLine(string.Join(",", permutation));
        }

        Console.ReadLine();
    }
}

public static class SomeExtensions
{
    public static IEnumerable<IEnumerable<T>> GetPermutations<T>(this IEnumerable<T> enumerable)
    {
        var array = enumerable as T[] ?? enumerable.ToArray();

        var factorials = Enumerable.Range(0, array.Length + 1)
            .Select(Factorial)
            .ToArray();

        for (var i = 0L; i < factorials[array.Length]; i++)
        {
            var sequence = GenerateSequence(i, array.Length - 1, factorials);

            yield return GeneratePermutation(array, sequence);
        }
    }

    private static IEnumerable<T> GeneratePermutation<T>(T[] array, IReadOnlyList<int> sequence)
    {
        var clone = (T[])array.Clone();

        for (int i = 0; i < clone.Length - 1; i++)
        {
            Swap(ref clone[i], ref clone[i + sequence[i]]);
        }

        return clone;
    }

    private static int[] GenerateSequence(long number, int size, IReadOnlyList<long> factorials)
    {
        var sequence = new int[size];

        for (var j = 0; j < sequence.Length; j++)
        {
            var facto = factorials[sequence.Length - j];

            sequence[j] = (int)(number / facto);
            number = (int)(number % facto);
        }

        return sequence;
    }

    static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }

    private static long Factorial(int n)
    {
        long result = n;

        for (int i = 1; i < n; i++)
        {
            result = result * i;
        }

        return result;
    }
}

如何将字符串数组(24个值)组合成100行唯一的组合?您能解释一下如何做以及什么是最好的方式吗?

2 个答案:

答案 0 :(得分:1)

我想我会这样做

public static class StringPermutator
{
    /// <summary>
    /// Class to permutate input values
    /// </summary>
    /// <param name="inputValues">An array of inputs to be permutated</param>
    /// <param name="numberOfResults">The number of outputs we want to have</param>
    /// <param name="maxValuesPerRow">The number of values to be combined in each output row</param>
    /// <returns>An IEnumerable of unique permutated string ouptuts</returns>
    public static IEnumerable<string> Permutate<T>(T[] inputValues, int numberOfResults, int maxValuesPerRow)
    {
        HashSet<string> output = new HashSet<string>();
        Random rnd = new Random();

        //Loop until we have the number of results we want
        while (output.Count < numberOfResults)
        {
            StringBuilder sb = new StringBuilder();
            HashSet<int> usedIndexes = new HashSet<int>();

            //Loop until we have the right number of values in a single row
            while (usedIndexes.Count < maxValuesPerRow)
            {
                int index = rnd.Next(inputValues.Length);
                //Ensure that each index we use is unique and only used once per row
                if (usedIndexes.Add(index))
                    sb.Append(inputValues[index].ToString()).Append(",");
            }

            sb.Length--;    //remove the last comma
            output.Add(sb.ToString());   //value is only added if unique
        }

        return output.ToList();
    }
}

您可以这样称呼它

var result = StringPermutator.Permutate(stringValues, 100, 24);

foreach (var permutation in result)
{
    Console.WriteLine(string.Join(",", permutation));
}

基本上,该类使用HashSet来确保只能输入唯一的值,因此可以确保我们的输出没有重复,并且我们一直循环直到获得正确数量的生成的输出值为止。

在此循环中,我们随机选择要使用的索引,并确保该索引对于每个输出值也是唯一的,然后再次使用HashSet存储使用的索引并循环,直到将正确数量的值组合到单个输出行。

返回是一个可枚举的列表。 这应该适用于任何类型的输入值,而不仅仅是字符串。

编辑:

仅根据评论进行澄清。 如果没有足够的输入来生成所需数量的排列和行组合,则可能会陷入循环。因此,您应该编写一种方法来突破此限制,但是为了使示例简单起见,我没有这样做。

答案 1 :(得分:0)

我建议使用更具确定性的置换生成器。将此与现有的LINQ扩展方法(如Select()Distinct()Take()结合使用,您将获得所需的东西:

var results = values2.Permutations(24)
                     .Select(p => String.Join(",", p))
                     .Distinct()
                     .Take(100);
foreach (var permutation in results)
    Console.WriteLine(permutation);

其中Permutations()被实现为扩展方法。这里的数字24表示每个排列应包含多少个项目。它是 n P k 中的k

Select()调用创建一个字符串字符串,其中包含针对特定排列的所有项目。

Distinct()调用可确保我们仅以唯一的字符串结尾。

Take()调用限制了我们收集的字符串数量。

这是一个使用递归生成排列的天真的实现:

public static IEnumerable<T[]> Permutations<T>(this IEnumerable<T> source, int k)
{
    if (k < 0)
        throw new ArgumentOutOfRangeException(nameof(k), "May not be negative");

    var items = source.ToArray();
    if (k > items.Length)
        throw new ArgumentOutOfRangeException(nameof(k), "May not be bigger than the number of items in source");

    var buffer = new ArraySegment<T>(items, 0, k);
    return Permute(0);

    IEnumerable<T[]> Permute(int depth)
    {
        if (depth == k)
        {
            yield return buffer.ToArray();
            yield break;
        }

        for (int i = depth; i < items.Length; i++)
        {
            Swap(depth, i);
            foreach (var permutation in Permute(depth + 1))
                yield return permutation;
            Swap(depth, i);
        }
    }

    void Swap(int a, int b)
    {
        if (a != b)
        {
            T t = items[a];
            items[a] = items[b];
            items[b] = t;
        }
    }
}

我将它留给您,以用您选择的算法替换实现。