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