如何迭代不同长度的列表以查找所有排列?

时间:2016-07-31 07:45:20

标签: c# algorithm

这个不应该太难,但我的思想似乎是堆栈溢出(huehue)。我有一系列的列表,我想找到他们可以订购的所有排列。所有列表都有不同的长度。

例如:

列表1:1

清单2:1,2

所有排列都是:

1,1

1,2

在我的情况下,我不会切换数字。 (例如2,1) 写这个最简单的方法是什么?

4 个答案:

答案 0 :(得分:1)

我不能说以下是最简单的方法,但IMO是最有效的方式。它基本上是我对Looking at each combination in jagged array的回答的一般化版本:

public static class Algorithms
{
    public static IEnumerable<T[]> GenerateCombinations<T>(this IReadOnlyList<IReadOnlyList<T>> input)
    {
        var result = new T[input.Count];
        var indices = new int[input.Count];
        for (int pos = 0, index = 0; ;)
        {
            for (; pos < result.Length; pos++, index = 0)
            {
                indices[pos] = index;
                result[pos] = input[pos][index];
            }
            yield return result;
            do
            {
                if (pos == 0) yield break;
                index = indices[--pos] + 1;
            }
            while (index >= input[pos].Count);
        }
    }
}

您可以在链接的答案中看到解释(很快就会模拟嵌套循环)。此外,由于性能原因,它产生内部缓冲区而不克隆它,如果你想存储结果以供以后处理,你需要克隆它。

样本用法:

var list1 = new List<int> { 1 };
var list2 = new List<int> { 1, 2 };
var lists = new[] { list1, list2 };

// Non caching usage
foreach (var combination in lists.GenerateCombinations())
{
    // do something with the combination
}

// Caching usage
var combinations = lists.GenerateCombinations().Select(c => c.ToList()).ToList();

更新 GenerateCombinations是标准的C#iterator方法,实现基本上模仿N嵌套循环(其中Ninput.Count}像这样(伪代码):

  

for(int i 0 = 0; i 0 &lt; input [0] .Count; i 0 ++)
  for(int i 1 = 0; i 1 &lt; input [1] .Count; i 1 ++)
  for(int i 2 = 0; i 2 &lt; input [2] .Count; i 2 ++)
  ...
  for(int i N-1 = 0; i N-1 &lt; input [N-1] .Count; i N-1 ++)
  yield {input [0] [i 0 ],输入[1] [i 1 ],输入[2] [i 2 ], ...,输入[N-1] [i N-1 ]}

或以不同方式显示:

for (indices[0] = 0; indices[0] < input[0].Count; indices[0]++)
{
    result[0] = input[0][indices[0]];
    for (indices[1] = 0; indices[1] < input[1].Count; indices[1]++)
    {
        result[1] = input[1][indices[1]];
        // ...
        for (indices[N-1] = 0; indices[N-1] < input[N-1].Count; indices[N-1]++)
        {
            result[N-1] = input[N-1][indices[N-1]];
            yield return result;
        }
    }
}

答案 1 :(得分:0)

嵌套循环:

List<int> listA = (whatever), listB = (whatever);
var answers = new List<Tuple<int,int>>;
for(int a in listA)
    for(int b in listB)
        answers.add(Tuple.create(a,b));
// do whatever with answers

答案 2 :(得分:0)

试试这个:

Func<IEnumerable<string>, IEnumerable<string>> combine = null;
combine = xs =>
    xs.Skip(1).Any()
    ? xs.First().SelectMany(x => combine(xs.Skip(1)), (x, y) => String.Format("{0}{1}", x, y))
    : xs.First().Select(x => x.ToString());

var strings = new [] { "AB", "12", "$%" };

foreach (var x in combine(strings))
{
    Console.WriteLine(x);
}

这让我:

A1$
A1%
A2$
A2%
B1$
B1%
B2$
B2%

答案 3 :(得分:0)

我创建了以下 IEnumerable<IEnumerable<TValue>> 类来解决这个问题,它允许使用通用 IEnumerable 并且其枚举器返回值的所有排列,每个内部列表中的一个。它可以方便地直接在 foreach 循环中使用。

这是 Michael Liu 对 IEnumerable and Recursion using yield return 的回答的变体

我已经修改了它以返回带有排列而不是单个值的列表。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Permutation
{
    public class ListOfListsPermuter<TValue> : IEnumerable<IEnumerable<TValue>>
    {
        private int count;
        private IEnumerable<TValue>[] listOfLists;

        public ListOfListsPermuter(IEnumerable<IEnumerable<TValue>> listOfLists_)
        {
            if (object.ReferenceEquals(listOfLists_, null))
            {
                throw new ArgumentNullException(nameof(listOfLists_));
            }
            listOfLists =listOfLists_.ToArray();
            count = listOfLists.Count();
            for (int i = 0; i < count; i++)
            {
                if (object.ReferenceEquals(listOfLists[i], null))
                {
                    throw new NullReferenceException(string.Format("{0}[{1}] is null.", nameof(listOfLists_), i));
                }
            }
        }

        // A variant of Michael Liu's answer in StackOverflow
        // https://stackoverflow.com/questions/2055927/ienumerable-and-recursion-using-yield-return
        public IEnumerator<IEnumerable<TValue>> GetEnumerator()
        {
            TValue[] currentList = new TValue[count];
            int level = 0; 
            var enumerators = new Stack<IEnumerator<TValue>>();
            IEnumerator<TValue> enumerator = listOfLists[level].GetEnumerator();
            try
            {
                while (true)
                {
                    if (enumerator.MoveNext())
                    {
                        currentList[level] = enumerator.Current;
                        level++;
                        if (level >= count)
                        {
                            level--;
                            yield return currentList;
                        }
                        else
                        {
                            enumerators.Push(enumerator);
                            enumerator = listOfLists[level].GetEnumerator();
                        }
                    }
                    else
                    {
                        if (level == 0)
                        {
                            yield break;
                        }
                        else
                        {
                            enumerator.Dispose();
                            enumerator = enumerators.Pop();
                            level--;
                        }
                    }
                }
            }
            finally
            {
                // Clean up in case of an exception.
                enumerator?.Dispose();
                while (enumerators.Count > 0) 
                {
                    enumerator = enumerators.Pop();
                    enumerator.Dispose();
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

你可以像这样直接在 foreach 中使用它:

        public static void Main(string[] args)
        {
            var listOfLists = new List<List<string>>()
            {
                { new List<string>() { "A", "B" } },
                { new List<string>() { "C", "D" } }
            };
            var permuter = new ListOfListsPermuter<string>(listOfLists);
            foreach (IEnumerable<string> item in permuter)
            {
                Console.WriteLine("{ \"" + string.Join("\", \"", item) + "\" }");
            }
        }

输出:

{ "A", "C" }
{ "A", "D" }
{ "B", "C" }
{ "B", "D" }