Проекция запроса LINQ из нескольких операторов from

Я создаю очень большой список объектов, которые представляют собой все возможные комбинации 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;
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
500
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

То, что вы пытаетесь здесь сделать, можно описать на высоком уровне как перекрестные соединения или произвольное количество последовательностей.

Когда вы делаете несколько предложений 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>, потому что ничего о том, как метод использует его, не требует, чтобы он был списком. С другой стороны, я оставил элементы в возвращаемой последовательности как Lists, потому что это упростит сопоставление их элементов с исходным списком, как я упоминал ранее.

Можно выполнить еще один рефакторинг, например, извлечь выражение, переданное 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();
    }
}

Надеюсь, это поможет.

Это работает отлично! Я уверен, что это поможет и многим другим. Огромное спасибо.

AlwaysLearning 13.06.2019 09:42

Другие вопросы по теме