Генерация всех Возможных Комбинаций

Учитывая 2 массива Array1 = {a,b,c...n} и Array2 = {10,20,15....x} как я могу генерировать всю возможную комбинацию как Строки (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)
66
задан 10 revs, 3 users 85% 15 April 2016 в 14:06
поделиться

7 ответов

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;
}
21
ответ дан 24 November 2019 в 14:50
поделиться

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

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

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

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

.
13
ответ дан 24 November 2019 в 14:50
поделиться

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

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

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

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

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

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

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

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

1
ответ дан 24 November 2019 в 14:50
поделиться

Если я правильно понимаю, вам нужно что-то вроде декартова произведения . Если это так, вот как это можно сделать с помощью 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
ответ дан 24 November 2019 в 14:50
поделиться

Конечно. Это немного сложно сделать с 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();
}

Легко и просто!

151
ответ дан 24 November 2019 в 14:50
поделиться

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
ответ дан 24 November 2019 в 14:50
поделиться

Для сравнения, вот способ сделать это с помощью 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)
3
ответ дан 24 November 2019 в 14:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: