Генерация всех возможных комбинаций - PullRequest
61 голосов
/ 22 июня 2010

Учитывая 2 массива Array1 = {a,b,c...n} и Array2 = {10,20,15....x}, как я могу генерировать все возможные комбинации в виде строк a (i) b (j) c (k) n (p) , где

1 <= i <= 10,  1 <= j <= 20 , 1 <= k <= 15,  .... 1 <= p <= x

Например:

a1 b1 c1 .... n1  
a1 b1 c1..... n2  
......  
......  
a10 b20 c15 nx (last combination)

Таким образом, во всем общем количестве комбинаций = произведение элементов на array2 = (10 X 20 X 15 X ..X x)

Похоже на декартово произведение, в котором второй массив определяетверхний предел для каждого элемента в первом массиве.

Пример с фиксированными числами,

    Array x =  [a,b,c]
    Array y =  [3,2,4] 

Таким образом, у нас будет 3 * 2 * 4 = 24 комбинации.Результаты должны быть:

    a1 b1 c1  
    a1 b1 c2  
    a1 b1 c3  
    a1 b1 c4  

    a1 b2 c1  
    a1 b2 c2  
    a1 b2 c3  
    a1 b2 c4


    a2 b1 c1  
    a2 b1 c2  
    a2 b1 c3  
    a2 b1 c4  

    a2 b2 c1  
    a2 b2 c2  
    a2 b2 c3  
    a2 b2 c4


    a3 b1 c1  
    a3 b1 c2  
    a3 b1 c3  
    a3 b1 c4  

    a3 b2 c1  
    a3 b2 c2  
    a3 b2 c3  
    a3 b2 c4 (last)

Ответы [ 11 ]

145 голосов
/ 23 июня 2010

Конечно. Это немного сложно сделать с LINQ, но, безусловно, возможно, используя только стандартные операторы запросов.

ОБНОВЛЕНИЕ: Это тема моего блога в понедельник 28 июня 2010 года ; спасибо за отличный вопрос Кроме того, комментатор в моем блоге отметил, что есть еще более элегантный запрос, чем тот, который я дал. Я обновлю код здесь, чтобы использовать его.

Самое сложное - сделать декартово произведение произвольного числа последовательностей. «Застегивание» в письмах тривиально по сравнению с этим. Вы должны изучить это, чтобы убедиться, что вы понимаете, как это работает. Каждая часть достаточно проста, но для того, чтобы соединить их вместе, нужно привыкнуть:

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
    IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>()};
    return sequences.Aggregate(
        emptyProduct,
        (accumulator, sequence) => 
            from accseq in accumulator 
            from item in sequence 
            select accseq.Concat(new[] {item})                          
        );
 }

Чтобы объяснить, как это работает, сначала поймите, что делает операция «накапливать». Самая простая операция накопления - «сложить все в этой последовательности вместе». То, как вы делаете это: начать с нуля. Для каждого элемента в последовательности текущее значение аккумулятора равно сумме элемента и предыдущего значения аккумулятора. Мы делаем то же самое, за исключением того, что вместо накопления суммы, основанной на сумме на данный момент и текущей позиции, мы накапливаем декартово произведение на ходу.

Способ, которым мы собираемся это сделать, - воспользоваться тем, что у нас уже есть оператор в LINQ, который вычисляет декартово произведение двух вещей:

from x in xs
from y in ys
do something with each possible (x, y)

Повторно беря декартово произведение аккумулятора со следующим элементом во входной последовательности и немного склеивая результаты, мы можем генерировать декартово произведение по ходу работы.

Так что подумайте о стоимости аккумулятора. Для наглядности я собираюсь показать значение аккумулятора в виде результатов операторов последовательности, которые он содержит. Это не то, что на самом деле содержит аккумулятор . На самом деле аккумулятор содержит операторы , которые дают эти результаты. Вся операция здесь просто создает массивное дерево операторов последовательности, результатом которого является декартово произведение. Но окончательный декартовой продукт сам по себе фактически не вычисляется до тех пор, пока не будет выполнен запрос. В иллюстративных целях я покажу, каковы результаты на каждом этапе пути, но помните, это на самом деле содержит операторов , которые производят эти результаты.

Предположим, мы берем декартово произведение последовательности последовательностей {{1, 2}, {3, 4}, {5, 6}}. Аккумулятор запускается как последовательность, содержащая одну пустую последовательность: { { } }

При первом накоплении аккумулятор равен {{}}, а элемент равен {1, 2}. Мы делаем это:

from accseq in accumulator
from item in sequence 
select accseq.Concat(new[] {item})

Итак, мы берем декартово произведение { { } } с {1, 2}, и для каждой пары мы объединяем: у нас есть пара ({ }, 1), поэтому мы объединяем { } и {1}, чтобы получить {1} , У нас есть пара ({ }, 2}), поэтому мы объединяем { } и {2}, чтобы получить {2}. Поэтому мы имеем {{1}, {2}} как результат.

Таким образом, при втором накоплении аккумулятор равен {{1}, {2}}, а элемент равен {3, 4}. Опять же, мы вычисляем декартово произведение этих двух последовательностей, чтобы получить:

 {({1}, 3), ({1}, 4), ({2}, 3), ({2}, 4)}

а затем из этих предметов соединить второй с первым. Таким образом, результатом является последовательность {{1, 3}, {1, 4}, {2, 3}, {2, 4}}, что мы и хотим.

Теперь мы снова накапливаем. Мы берем декартово произведение аккумулятора с {5, 6}, чтобы получить

 {({ 1, 3}, 5), ({1, 3}, 6), ({1, 4}, 5), ...

, а затем объединить второй элемент с первым, чтобы получить:

{{1, 3, 5}, {1, 3, 6}, {1, 4, 5}, {1, 4, 6} ... }

и мы закончили. Мы накопили декартово произведение.

Теперь, когда у нас есть функция полезности, которая может взять декартово произведение произвольного числа последовательностей, остальное легко сравнить:

var arr1 = new[] {"a", "b", "c"};
var arr2 = new[] { 3, 2, 4 };
var result = from cpLine in CartesianProduct(
                 from count in arr2 select Enumerable.Range(1, count)) 
             select cpLine.Zip(arr1, (x1, x2) => x2 + x1);

И теперь у нас есть последовательность последовательностей строк, одна последовательность строк на строку:

foreach (var line in result)
{
    foreach (var s in line)
        Console.Write(s);
    Console.WriteLine();
}

Легко, peasy!

20 голосов
/ 22 июня 2010
using System;
using System.Text;

public static string[] GenerateCombinations(string[] Array1, int[] Array2)
{
    if(Array1 == null) throw new ArgumentNullException("Array1");
    if(Array2 == null) throw new ArgumentNullException("Array2");
    if(Array1.Length != Array2.Length)
        throw new ArgumentException("Must be the same size as Array1.", "Array2");

    if(Array1.Length == 0)
        return new string[0];

    int outputSize = 1;
    var current = new int[Array1.Length];
    for(int i = 0; i < current.Length; ++i)
    {
        if(Array2[i] < 1)
            throw new ArgumentException("Contains invalid values.", "Array2");
        if(Array1[i] == null)
            throw new ArgumentException("Contains null values.", "Array1");
        outputSize *= Array2[i];
        current[i] = 1;
    }

    var result = new string[outputSize];
    for(int i = 0; i < outputSize; ++i)
    {
        var sb = new StringBuilder();
        for(int j = 0; j < current.Length; ++j)
        {
            sb.Append(Array1[j]);
            sb.Append(current[j].ToString());
            if(j != current.Length - 1)
                sb.Append(' ');
        }
        result[i] = sb.ToString();
        int incrementIndex = current.Length - 1;
        while(incrementIndex >= 0 && current[incrementIndex] == Array2[incrementIndex])
        {
                current[incrementIndex] = 1;
                --incrementIndex;
        }
        if(incrementIndex >= 0)
            ++current[incrementIndex];
    }
    return result;
}
13 голосов
/ 23 июня 2010

Альтернативное решение:

Шаг первый: прочитайте мою серию статей о том, как генерировать все строки, которые соответствуют контекстно-зависимой грамматике:

http://blogs.msdn.com/b/ericlippert/archive/tags/grammars/

Шаг второй: определите грамматику, которая генерирует нужный вам язык.Например, вы можете определить грамматику:

S: a A b B c C
A: 1 | 2 | 3
B: 1 | 2
C: 1 | 2 | 3 | 4

Очевидно, что вы легко можете сгенерировать эту строку определения грамматики из двух ваших массивов.Затем введите это в код, который генерирует все строки в данной грамматике, и все готово;вы получите все возможности.(Не обязательно в том порядке, в котором вы хотите их видеть, заметьте.)

3 голосов
/ 03 июля 2010

Для сравнения, вот способ сделать это с Python

from itertools import product
X=["a", "b", "c"]
Y=[3, 4, 2]
terms = (["%s%s"%(x,i+1) for i in range(y)] for x,y in zip(X,Y))
for item in product(*terms):
    print " ".join(item)
2 голосов
/ 12 августа 2011

Для другого решения, не основанного на linq, вы можете использовать:

public class CartesianProduct<T>
    {
        int[] lengths;
        T[][] arrays;
        public CartesianProduct(params  T[][] arrays)
        {
            lengths = arrays.Select(k => k.Length).ToArray();
            if (lengths.Any(l => l == 0))
                throw new ArgumentException("Zero lenght array unhandled.");
            this.arrays = arrays;
        }
        public IEnumerable<T[]> Get()
        {
            int[] walk = new int[arrays.Length];
            int x = 0;
            yield return walk.Select(k => arrays[x++][k]).ToArray();
            while (Next(walk))
            {
                x = 0;
                yield return walk.Select(k => arrays[x++][k]).ToArray();
            }

        }
        private bool Next(int[] walk)
        {
            int whoIncrement = 0;
            while (whoIncrement < walk.Length)
            {
                if (walk[whoIncrement] < lengths[whoIncrement] - 1)
                {
                    walk[whoIncrement]++;
                    return true;
                }
                else
                {
                    walk[whoIncrement] = 0;
                    whoIncrement++;
                }
            }
            return false;
        }
    }

Пример использования вы можете найти здесь .

1 голос
/ 22 июня 2010

finalResult - желаемый массив.Предполагается, что оба массива имеют одинаковый размер.

char[] Array1 = { 'a', 'b', 'c' };
int[] Array2 = { 3, 2, 4 };

var finalResult = new List<string>();
finalResult.Add(String.Empty);
for(int i=0; i<Array1.Length; i++)
{
    var tmp = from a in finalResult
              from b in Enumerable.Range(1,Array2[i])
              select String.Format("{0} {1}{2}",a,Array1[i],b).Trim();
    finalResult = tmp.ToList();
}

Я думаю, этого будет достаточно.

1 голос
/ 22 июня 2010

Если я правильно понял, вам нужно что-то вроде Декартово произведение . Если это так, то вот как вы можете это сделать с помощью LINQ. Может быть не точный ответ, но попытайтесь понять идею


    char[] Array1 = { 'a', 'b', 'c' };
    string[] Array2 = { "10", "20", "15" };

    var result = from i in Array1
                 from j in Array2
                   select i + j;

Эти статьи могут помочь

1 голос
/ 22 июня 2010

Я не хочу давать вам полный исходный код.Итак, вот идея.

Вы можете сгенерировать элементы следующим образом:

Я предполагаю A=(a1, a2, ..., an) и B=(b1, b2, ..., bn) (так что A и B каждый содержит nэлементы).

Тогда сделайте это рекурсивно!Напишите метод, который принимает A и B и выполняет свои функции:

Если A и B каждый содержит только один элемент (называемый an соответственно bn),просто выполните итерацию от 1 до bn и объедините an с вашей итерирующей переменной.

Если A и B содержат более одного элемента, возьмите первые элементы (a1 или * 1027)*), выполните итерацию от 1 до bn и выполните для каждого шага итерации:

  • вызовите метод рекурсивно с подполями A и B, начиная со второго элемента, то есть A'=(a2, a3, ..., an) соответственно B'=(b2, b3, ..., bn).Для каждого элемента, сгенерированного рекурсивным вызовом, объедините a1, переменную итерации и сгенерированный элемент из рекурсивного вызова.

Здесь вы можете найти аналогичный пример того, какчтобы генерировать вещи в C #, вам просто нужно адаптировать его к вашим потребностям.

0 голосов
/ 13 октября 2015

Для другого решения, не основанного на linq, более эффективное:

static IEnumerable<T[]> CartesianProduct<T>(T[][] arrays) {
    int[] lengths;
    lengths = arrays.Select(a => a.Length).ToArray();
    int Len = arrays.Length;
    int[] inds = new int[Len];
    int Len1 = Len - 1;
    while (inds[0] != lengths[0]) {
        T[] res = new T[Len];
        for (int i = 0; i != Len; i++) {
            res[i] = arrays[i][inds[i]];
        }
        yield return res;
        int j = Len1;
        inds[j]++;
        while (j > 0 && inds[j] == lengths[j]) {
            inds[j--] = 0;
            inds[j]++;
        }
    }
}
0 голосов
/ 09 июня 2014

Я только что обнаружил эту публикацию CodeProject, которая включает в себя пространство имен Facets.Combinatorics, содержащее некоторый полезный код для обработки перестановок, комбинаций и вариаций в C #.

http://www.codeproject.com/Articles/26050/Permutations-Combinations-and-Variations-using-C-G

...