То, что вы пытаетесь сделать здесь, можно описать на высоком уровне как перекрестное соединение из произвольного числа последовательностей .
Когда вы выполняете несколько предложений from
в LINQ таким образом, это перекрестное соединение. Все, кроме первого, это звонок на SelectMany
. Чтобы поддерживать произвольное количество входов, вам нужно пройти через них. Однако простой цикл foreach
не сработает, поскольку первую последовательность нужно обрабатывать немного по-другому.
иначе как? Две вещи. Во-первых, не на что звонить SelectMany
. Если вы называете это в пустом списке, вы получаете другой пустой список. Таким образом, первый элемент в loops
устанавливает базовый список. Реализация, которую я привел ниже , начинается с пустой последовательности, но она есть только в том случае, если в loops
не найдено элементов. Он заменяется, когда первый элемент найден. И, во-вторых, по крайней мере, в этой реализации вам необходимо создать новый список из первого элемента, тогда как последующие объединения должны создать следующий элемент и создать новый список.
Еще одна вещь, которую нужно иметь в виду, это то, что вам нужен тип результата, который может обрабатывать произвольное количество элементов. Для этого я выбрал IEnumerable<List<Step>>
- я выбрал List<Step>
в качестве типа элемента, потому что каждый индекс каждого списка будет соответствовать этому индексу параметра loops
, так что вы можете индексировать их оба напрямую. Например, элемент [5]
каждого результата будет получен из loops[5]
.
Чтобы это произошло, я написал эквивалент цикла foreach
, используя IEnumerator<T>
напрямую. Для первого элемента в loops
он создает список из Step
объектов для каждого результата, который вы хотите вернуть.
Для каждого последующего элемента в loops
он выполняет перекрестное соединение, используя SelectMany
, и объединяет их в новые списки, содержащие элементы с левой стороны и каждый элемент с правой стороны.
Перечислитель предоставляет свойство Current
. Это освобождает итерации от привязки к данному индексу. Вы заметите, что переменная current
используется там, где раньше была loops[n]
.
Стоит отметить, что вызовы ToList
необходимы для принудительного вычисления, потому что переменная current
не захватывается лямбдами так же, как переменная диапазона в цикле foreach
.
Вот как это выглядит:
public static IEnumerable<List<Step>> CreateSteps(IEnumerable<MinToMax> loops)
{
IEnumerable<List<Step>> sequence = Enumerable.Empty<List<Step>>();
using (IEnumerator<MinToMax> enumerator = loops.GetEnumerator())
{
if (enumerator.MoveNext())
{
MinToMax current = enumerator.Current;
sequence = Enumerable
.Repeat(current.Minimum, (current.Maximum - current.Minimum) / current.Increment + 1)
.Select((tr, ti) => new List<Step>() { new Step() { Value = tr + current.Increment * ti, IsActive = true } })
.ToList();
while (enumerator.MoveNext())
{
current = enumerator.Current;
sequence = sequence
.SelectMany(
ctx => Enumerable
.Repeat(current.Minimum, (current.Maximum - current.Minimum) / current.Increment + 1)
.Select((tr, ti) => new Step() { Value = tr + current.Increment * ti, IsActive = true }),
(list, value) => new List<Step>(list) { value }
)
.ToList();
}
}
}
return sequence;
}
Я также изменил параметр loops
на IEnumerable<MinToMax>
, потому что ничего о том, как метод использует его, не требует, чтобы он был списком. С другой стороны, я оставил элементы в возвращенной последовательности как List
s, потому что это облегчит корреляцию их элементов с исходным списком, как я упоминал ранее.
Существует больше возможностей рефакторинга, например, извлечение выражения, переданного в Enumerable.Repeat
, в метод, чтобы его можно было повторно использовать.
Я не видел, как ты это использовал, поэтому я быстро все это сложил. Он показал одинаковые результаты для обеих реализаций с тремя элементами в loops
, которые вы предоставили, и он показал правильные результаты для моего с двумя и четырьмя входами.
static void Main(string[] args)
{
List<MinToMax> loops = GetValuesIntoSteps();
foreach (List<Step> loop in CreateSteps(loops))
{
foreach (Step step in loop)
{
Console.Write($"{step.Value} ");
}
Console.WriteLine();
}
}
Надеюсь, это поможет.