Есть ли лучшая практика для получения элементов / переменных из коллекции на основе различных условий - PullRequest
1 голос
/ 20 февраля 2020

Или вообще как отфильтровать некоторые элементы из коллекции на основе различных и сложных условий за один проход

Допустим, у нас есть коллекция элементов

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 - хороший кандидат для реализации пользовательских условий, просто запишите их переменными.

Спасибо, ждем ваших решений! :)

1 Ответ

0 голосов
/ 20 февраля 2020

Я не уверен, возможно ли это с Linq "из коробки", но если для вас вариант написания собственного расширения один раз, выбор некоторых значений из коллекции с произвольным числом условий может быть позже изложен довольно лаконично.

Например, вы можете написать что-то вроде

var (redCat, blueCat) = cats.FindFirsts(
    x => x.Name == redCatName,
    x => x.Name == blueCatName);

Если вы введете расширение FindFirsts() следующим образом:

public static class FindExtensions
{
    public static T[] FindFirsts<T>(this IEnumerable<T> collection, 
        params Func<T, bool>[] conditions)
    {
        if (conditions.Length == 0)
            return new T[] { };

        var unmatchedConditions = conditions.Length;
        var lookupWork = conditions
            .Select(c => (
                value: default(T),
                found: false,
                cond: c
            ))
            .ToArray();
        foreach (var item in collection) 
        {
            for (var i = 0; i < lookupWork.Length; i++)
            {
                if (!lookupWork[i].found && lookupWork[i].cond(item))
                { 
                    lookupWork[i].found = true;
                    lookupWork[i].value = item;
                    unmatchedConditions--;
                }
            }
            if (unmatchedConditions <= 0)
                break;
        }
        return lookupWork.Select(x => x.value).ToArray();
    }
}

Полное демо можно найти здесь : https://dotnetfiddle.net/QdVJUd

Примечание. Чтобы деконструировать массив результатов (т. Е. Использовать var (redCat, blueCat) = ...), необходимо определить расширение деконструкции. Для этого я позаимствовал код из этой темы .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...