Я создаю очень большой список объектов, которые представляют собой все возможные комбинации Min, Max и Increment в более чем одном наборе коллекций.
Однако это работает, моя проблема в том, что я не могу легко исключить одну или несколько коллекций элементов, не переписав запрос LINQ. Порядок операторов «от» в запросе выводится из порядка в переданном списке «циклов». Запрос и окончательная проекция в список «Шаговая модель» в этом примере предполагают обработку трех наборов элементов. Несмотря на то, что операторы from являются LINQ, они по-прежнему выглядят как повторяющийся код, который можно использовать в цикле For. Я не могу понять, как это сделать, учитывая, что проекция объединена в этом одном запросе. ПРИМЕЧАНИЕ. Я могу добавить больше коллекций, усугубляющих проблему!
public static IEnumerable<StepModel> CreateSteps(List<MinToMax> loops)
{
var steps =
from item1 in Enumerable
.Repeat(loops[0].Minimum, (loops[0].Maximum - loops[0].Minimum) / loops[0].Increment + 1)
.Select((tr, ti) => tr + loops[0].Increment * ti)
from item2 in Enumerable
.Repeat(loops[1].Minimum, (loops[1].Maximum - loops[1].Minimum) / loops[1].Increment + 1)
.Select((tr, ti) => tr + loops[1].Increment * ti)
from item3 in Enumerable
.Repeat(loops[2].Minimum, (loops[2].Maximum - loops[2].Minimum) / loops[2].Increment + 1)
.Select((tr, ti) => tr + loops[2].Increment * ti)
select new StepModel
{
ItemValues1 = new Step { Value = item1, IsActive = true },
ItemValues2 = new Step { Value = item2, IsActive = true },
ItemValues3 = new Step { Value = item3, IsActive = true },
};
return steps;
}
public class MinToMax
{
public int Minimum { get; set; }
public int Maximum { get; set; }
public int Increment { get; set; }
public bool IsActive { get; set; } = true;
}
public class Step
{
public int Value { get; set; }
public bool IsActive { get; set; } = true;
}
public class StepModel
{
public Step ItemValues1 { get; set; }
public Step ItemValues2 { get; set; }
public Step ItemValues3 { get; set; }
}
public class ItemSteps
{
public MinToMax ItemStep1 { get; } = new MinToMax();
public MinToMax ItemStep2 { get; } = new MinToMax();
public MinToMax ItemStep3 { get; } = new MinToMax();
}
public static List<MinToMax> GetValuesIntoSteps()
{
var list = new List<MinToMax>();
var itemValues = new ItemSteps();
itemValues.ItemStep1.Minimum = 10;
itemValues.ItemStep1.Maximum = 100;
itemValues.ItemStep1.Increment = 10;
if (itemValues.ItemStep1.IsActive)
{
list.Add(itemValues.ItemStep1);
}
itemValues.ItemStep2.Minimum = 3;
itemValues.ItemStep2.Maximum = 30;
itemValues.ItemStep2.Increment = 3;
if (itemValues.ItemStep2.IsActive)
{
list.Add(itemValues.ItemStep2);
}
itemValues.ItemStep3.Minimum = 15;
itemValues.ItemStep3.Maximum = 75;
itemValues.ItemStep3.Increment = 5;
if (itemValues.ItemStep3.IsActive)
{
list.Add(itemValues.ItemStep3);
}
return list;
}
То, что вы пытаетесь здесь сделать, можно описать на высоком уровне как перекрестные соединения или произвольное количество последовательностей.
Когда вы делаете несколько предложений 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();
}
}
Надеюсь, это поможет.
Это работает отлично! Я уверен, что это поможет и многим другим. Огромное спасибо.