Можно ли разделить IEnumerable на два с помощью логических критериев без двух запросов?
Можно ли разделить IEnumerable<T>
на два IEnumerable<T>
с помощью LINQ и только на один запрос/оператор LINQ?
Я хочу избежать повторения через IEnumerable<T>
дважды. Например, можно ли комбинировать последние два оператора ниже, поэтому allValues проходит только один раз?
IEnumerable<MyObj> allValues = ...
List<MyObj> trues = allValues.Where( val => val.SomeProp ).ToList();
List<MyObj> falses = allValues.Where( val => !val.SomeProp ).ToList();
Ответы
Ответ 1
Вы можете использовать это:
var groups = allValues.GroupBy(val => val.SomeProp);
Для немедленной оценки, как в вашем примере:
var groups = allValues.GroupBy(val => val.SomeProp)
.ToDictionary(g => g.Key, g => g.ToList());
List<MyObj> trues = groups[true];
List<MyObj> falses = groups[false];
Ответ 2
Некоторые люди любят словари, но я предпочитаю Lookups из-за поведения, когда отсутствует ключ.
IEnumerable<MyObj> allValues = ...
ILookup<bool, MyObj> theLookup = allValues.ToLookup(val => val.SomeProp);
//does not throw when there are not any true elements.
List<MyObj> trues = theLookup[true].ToList();
//does not throw when there are not any false elements.
List<MyObj> falses = theLookup[false].ToList();
К сожалению, этот подход перечисляет дважды - один раз для создания поиска, затем один раз для создания списков.
Если вам действительно не нужны списки, вы можете перейти к одной итерации:
IEnumerable<MyObj> trues = theLookup[true];
IEnumerable<MyObj> falses = theLookup[false];
Ответ 3
Скопируйте метод расширения пасты для вашего удобства.
public static void Fork<T>(
this IEnumerable<T> source,
Func<T, bool> pred,
out IEnumerable<T> matches,
out IEnumerable<T> nonMatches)
{
var groupedByMatching = source.ToLookup(pred);
matches = groupedByMatching[true];
nonMatches = groupedByMatching[false];
}
Или используя кортежи в С# 7.0
public static (IEnumerable<T> matches, IEnumerable<T> nonMatches) Fork<T>(
this IEnumerable<T> source,
Func<T, bool> pred)
{
var groupedByMatching = source.ToLookup(pred);
return (groupedByMatching[true], groupedByMatching[false]);
}
// Ex.
var numbers = new [] { 1, 2, 3, 4, 5, 6, 7, 8 };
var (numbersLessThanEqualFour, numbersMoreThanFour) = numbers.Fork(x => x <= 4);