Или вообще как отфильтровать некоторые элементы из коллекции на основе различных и сложных условий за один проход
Допустим, у нас есть коллекция элементов
var cats = new List<Cat>{ new Cat("Fluffy"), new Cat("Meowista"), new Cat("Scratchy")};
И где-то мы используем эту коллекцию
public CatFightResult MarchBoxing(List<Cat> cats, string redCatName, string blueCatName)
{
var redCat = cats.First(cat => cat.Name == redCatName);
var blueCat = cats.First(cat => cat.Name == blueCatName);
var redValue = redCat.FightValue();
var blueValue = blueCat.FightValue();
if (Cat.FightValuesEqualWithEpsilon(redValue, blueValue))
return new CatFightResult{IsDraw: true};
return new CatFightResult{Winner: redValue > blueValue ? redCat : blueCat};
}
Вопрос : Есть ли хороший способ получить несколько переменных из коллекции на основе некоторых условий? Вопрос, вероятно, требует некоторой уникальности в сборе, давайте сначала предположим, что есть некоторые (то есть HashSet
/ Dictionary
)
И предпочтительно :
- ЕДИНСТВЕННЫЙ проход / цикл при сборе (самая важная причина вопроса, как вы можете видеть, есть 2 операции фильтра в вышеописанном методе)
- oneliner или тому подобное, с удобочитаемостью, и чем короче, тем лучше
- generi c way (
IEnumerable<T>
я думаю, или ICollection<T>
) - опечатки подвержены ошибкам и безопасны для изменений / дополнений (минимальное использование реальных условий в коде, желательно проверено
- проверка на ноль / исключение, потому что мое намерение, что ноль является действительным результатом для полученной переменной
Было бы также здорово иметь возможность предоставлять пользовательские условия, что, вероятно, можно было бы сделать с помощью параметров Fun c , но я еще не тестировал.
Есть мои попытки, которые я опубликовал в своем репо https://github.com/phomm/TreeBalancer/blob/master/TreeTraverse/Program.cs
Вот адаптация к примеру с Кошки:
public CatFightResult MarchBoxing(List<Cat> cats, string redCatName, string blueCatName)
{
var redCat = null;
var blueCat = null;
//1 kinda oneliner, but hard to read and not errorprone
foreach (var c in cats) _ = c.Name == redCatName ? redCat = n : n.Name == blueCatName ? blueCat = n : null;
//2 very good try, because errorprone and easy to read (and find mistake in assignment), but not oneliner and not elegant (but fast) redundant fetching and not single pass at all, up to O(N*N) with FirstOrDefault
var filter = new [] { redCatName, blueCatName }.ToDictionary(x => x.Key, x => cats.FirstOrDefault(n => n.Name == x.Key));
redCat = filter[redCatName];
blueCat = filter[blueCatName];
//3 with readability and ckecks for mistakenly written searching keys (dictionary internal dupe key check) , but not oneliner and not actualy single pass
var dic = new Dictionary<int, Func<Cat, Cat>> { { redCatName, n => redCat = n }, { blueCatName, n => blueCat = n } };
cats.All(n => dic.TryGetValue(n.Name, out var func) ? func(n) is null : true);
//4 best approach, BUT not generic (ofc one can write simple generic IEnumerable<T> ForEach extension method, and it would be strong candidate to win)
cats.ForEach(n => _ = n.Name == redCatName ? redCat = n : n.Name == blueCatName ? blueCat = n : null);
//5 nice approach, but not single pass, enumerating collection twice
cats.Zip(cats, (n, s) => n.Name == redCatName ? redCat = n : n.Name == blueCatName ? blueCat = n : null);
//6 the one I prefer best, however it's arguable due to breaking functional approach of Linq, causing side effects
cats.All(n => (n.Name == redCatName ? redCat = n : n.Name == blueCatName ? blueCat = n : null) is null);
}
Al l варианты с троичной операцией не являются легко расширяемыми и относительно подверженными ошибкам, но довольно коротки и Linq-i sh, они также полагаются (некоторые компромиссы с путаницей) на то, что не возвращать / использовать фактические результаты троичной обработки (с отменить "_" или "является нулевым" как bool). Я думаю, что подход с Dictionary of Funcs - хороший кандидат для реализации пользовательских условий, просто запишите их переменными.
Спасибо, ждем ваших решений! :)