C # Как разделить список на две части с помощью LINQ - PullRequest
0 голосов
/ 10 января 2019

Я пытаюсь разделить список на два списка, используя LINQ, не повторяя «основной» список дважды. Один список должен содержать элементы, для которых условие LINQ равно true , а другой должен содержать все остальные элементы. Это вообще возможно?

Сейчас я просто использую два запроса LINQ, таким образом дважды повторяя (огромный) основной список.

Вот (псевдо) код, который я сейчас использую:

List<EventModel> events = GetAllEvents();

List<EventModel> openEvents = events.Where(e => e.Closer_User_ID == null);
List<EventModel> closedEvents = events.Where(e => e.Closer_User_ID != null);

Можно ли получить одинаковые результаты без повторения исходного списка дважды?

Ответы [ 5 ]

0 голосов
/ 10 января 2019

Это можно сделать одним оператором, преобразовав его в таблицу поиска:

var splitTables = events.Tolookup(event => event.Closer_User_ID == null);

Это вернет последовательность из двух элементов, где каждый элемент является IGrouping<bool, EventModel>. Key указывает, является ли последовательность последовательностью с нулевым Closer_User_Id или нет.

Однако это выглядит довольно мистически. Мой совет - расширить LINQ новой функцией.

Эта функция принимает последовательность любого вида и предикат, который разделяет последовательность на две группы: группу, которая соответствует предикату, и группу, которая не соответствует предикату.

Таким образом, вы можете использовать функцию для разделения всех видов последовательностей IEnumerable на две последовательности.

См. Демистифицированные методы расширения

public static IEnumerable<IGrouping<bool, TSource>> Split<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource,bool> predicate)
{
    return source.ToLookup(item => item.Condition);
}

Использование:

IEnumerable<Person> persons = ...
// divide the persons into adults and non-adults:
var result = persons.Split(person => person.IsAdult);

Результат состоит из двух элементов: один с ключом true имеет всех взрослых.

Хотя использование стало легче читать, у вас все еще есть проблема с обработкой всей последовательности, в то время как на самом деле вы можете использовать только некоторые из полученных элементов

Давайте вернем IEnumerable<KeyValuePair<bool, TSource>>, где логическое значение указывает, соответствует элемент или нет:

public static IEnumerable<KeyValuePair<bool, TSource>> Audit<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource,bool> predicate)
{
    foreach (var sourceItem in source)
    {
        yield return new KeyValuePair<bool, TSource>(predicate(sourceItem, sourceItem));
    }
}

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

IEnumerable<EventModel> eventModels = ...
EventModel firstOpenEvent = eventModels.Audit(event => event.Closer_User_ID == null)
    .Where(splitEvent => splitEvent.Key)
    .FirstOrDefault();

Где указано, что вам нужны только те проверенные объекты, которые прошли аудит (ключ имеет значение true).

Поскольку вам нужен только первый элемент, остальная часть последовательности больше не проверяется

0 голосов
/ 10 января 2019

Однолинейное решение с использованием ForEach метода List:

List<EventModel> events = GetAllEvents();

List<EventModel> openEvents = new List<EventModel>();
List<EventModel> closedEvents = new List<EventModel>();

events.ForEach(x => (x.Closer_User_ID == null ? openEvents : closedEvents).Add(x));
0 голосов
/ 10 января 2019

GroupBy и Single должны выполнить то, что вы ищете:

var groups = events.GroupBy(e => e.Closer_User_ID == null).ToList(); // As others mentioned this needs to be materialized to prevent `events` from being iterated twice.
var openEvents = groups.SingleOrDefault(grp => grp.Key == true)?.ToList() ?? new List<EventModel>();
var closedEvents = groups.SingleOrDefault(grp => grp.Key == false)?.ToList() ?? new List<EventModel>();
0 голосов
/ 10 января 2019

Вы можете использовать ToLookup метод расширения следующим образом:

 List<Foo> items = new List<Foo> { new Foo { Name="A",Condition=true},new Foo { Name = "B", Condition = true },new Foo { Name = "C", Condition = false } };

  var lookupItems = items.ToLookup(item => item.Condition);
        var lstTrueItems = lookupItems[true];
        var lstFalseItems = lookupItems[false];
0 голосов
/ 10 января 2019

Можно обойтись без LINQ. Переключитесь на обычную петлевую систему.

List<EventModel> openEvents = new List<EventModel>();
List<EventModel> closedEvents = new List<EventModel>();

foreach(var e in  events)
{
  if(e.Closer_User_ID == null)
  {
    openEvents.Add(e);
  }
  else
  {
    closedEvents.Add(e);
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...