Ниже приведены шаги, которые я выполняю, чтобы выполнить декартово произведение произвольного числа массивов целых чисел.
Скажем, у нас есть пять массивов ниже:
var items1 = new int[]{1, 2, 3, 4};
var items2 = new int[]{5, 6, 7, 8};
var items3 = new int[]{9, 10, 11, 12};
var items4 = new int[]{13, 14, 15, 16};
var items5 = new int[]{17, 18, 19, 20};
И давайте скажем, у нас есть класс Set
, который будет содержать элемент декартового произведения:
// I named the property like this to allow further manipulation afterward
class Set
{
public int Item_1 { get; set; }
public int Item_2 { get; set; }
public int Item_3 { get; set; }
public int Item_4 { get; set; }
public int Item_5 { get; set; }
}
Таким образом, добавление массива потребует от нас добавить еще одно свойство в класс Set
. Предполагая, что это не страшно, давайте перейдем к следующему шагу.
Чтобы выполнить декартово произведение этих пяти массивов вручную, мы можем сделать следующее:
Метод 1: использование Выражение запроса Linq
var cp1 =
from i1 in items1
from i2 in items2
from i3 in items3
from i4 in items4
from i5 in items5
select new Set
{
Item_1 = i1,
Item_2 = i2,
Item_3 = i3,
Item_4 = i4,
Item_5 = i5
};
В большинстве случаев этого достаточно для выполнения декартового произведения, но добавление другой операции в операцию потребует изменения запроса. Итак, допустим, у нас есть требование создать декартово запрос продукта, который будет работать для любого количества коллекций, которые мы передаем ему. Перед реализацией этого метода, давайте посмотрим, как мы можем выполнить вышеупомянутый запрос, используя лямбда-выражения:
Метод 2: вручную, но используя лямбда-выражения
var cp2 = items1.SelectMany(
i1 => items2.SelectMany(
i2 => items3.SelectMany(
i3 => items4.SelectMany(
i4 => items5.Select(
i5 => new Set
{
Item_1 = i1,
Item_2 = i2,
Item_3 = i3,
Item_4 = i4,
Item_5 = i5
})))));
Еще раз, это работает, но добавление еще одной коллекции к операции потребует изменения запроса. Но что интересно в этом, так это то, как мы рекурсивно связываем метод SelectMany
. Поскольку в основном Linq работает с деревьями выражений, мы можем динамически построить дерево выражений предыдущего запроса, чтобы реализовать декартово произведение произвольного числа массивов:
Метод 3: Динамическое построение дерева выражений
MethodCallExpression CartesianProductExpression(int[][] items)
{
var pes = new List<ParameterExpression>();
return BuildExpression(items, pes, items.Length);
}
MethodCallExpression BuildExpression(int[][] items, List<ParameterExpression> pes, int numberOfArray)
{
var queryable = items[0].AsQueryable<int>();
ParameterExpression pe = Expression.Parameter(typeof(int), $"i{pes.Count+1}");
pes.Add(pe);
if(pes.Count == numberOfArray)
{
NewExpression newTripleExpression = Expression.New(typeof(Set));
MemberBinding[] bindings = new MemberBinding[pes.Count];
var index = 0;
foreach(var p in pes)
{
bindings[index++] = SetBindings.GetBinding($"Item_{index}", p);
}
Expression selectBody = Expression.MemberInit(newTripleExpression, bindings);
return Expression.Call(
typeof(Queryable),
"Select",
new Type[] { queryable.ElementType, typeof(Set) },
queryable.Expression,
Expression.Lambda<Func<int, Set>>(selectBody, new ParameterExpression[] { pe }));
}
return Expression.Call(
typeof(Queryable),
"SelectMany",
new Type[] { queryable.ElementType, typeof(Set) },
queryable.Expression,
Expression.Lambda<Func<int, IEnumerable<Set>>>(BuildExpression(items.Skip(1).ToArray(), pes, numberOfArray), new ParameterExpression[] { pe }));
}
// ... In another class file
static class SetBindings
{
public static MemberBinding GetBinding(string propertyName, ParameterExpression pe)
{
MemberInfo member = typeof(Set).GetMember(propertyName)[0];
return Expression.Bind(member, pe);
}
}
Этот метод можно использовать следующим образом:
int[][] items = new int[][] {items1, items2, items3, items4, items5};
var queryable = items[0].AsQueryable<int>();
var expression = CartesianProductExpression(items);
IQueryable<Set> cp3 = queryable.Provider.CreateQuery<Set>(expression);
В этом методе добавление другого массива будет включать только добавление другого свойства в класс Set
.
Вот так выглядит результат в Linqpad:
Итак, вернемся к вашему вопросу, вы можете интерполировать это решение в вашем случае использования. Например, вместо:
var _neckItems = _filterList.Where(i => i.Slot == Slot.Necklace).ToArray();
\\...
Вы можете напрямую проецировать на Id
следующим образом:
var _neckItems = _filterList.Where(i => i.Slot == Slot.Necklace).Select(i => i.Id).ToArray()
//...
Затем используйте один из вышеуказанных методов.