Как вызвать словарь<K, V>.TryGetValue(), где K: предикат<T>, V: enum

Я имею Dictionary<Predicate<double>, SomeEnum>:

var dic = new Dictionary<Predicate<double>, SomeEnum>
{
    { (d) => d < 10, SomeEnum.Foo },
    { (d) => d > 90, SomeEnum.Bar }
};

Я хочу позвонить TryGetValue(K, out V) против этого так:

dic.TryGetValue(99)

и получить

SomeStruct.Bar

Но первый парам для TryGetValue() является Predicate<T>, не просто T, Как я могу делать то, что я хочу?

Я нашел только грязный обходной путь:

var kpv = dic.FirstOrDefault(p => p.Key(99));
if (kpv.Key != null)
    var result = kpv.Value;

Есть ли другие способы?

Или как правильно реализовать мою идею? - объявить ключ не как константу, а как сегмент.

4 ответа

Решение

Здесь есть пара неправильных вещей:

Predicate<double> не подходит для использования в качестве TKey, Ключ для словаря должен идентифицировать значение, а не вычислять значение.

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

Посмотрите этот пример кода для иллюстрации:

Predicate<double> fn_1 = d => d == 34.0d;
Predicate<double> fn_2 = d => d == 34.0d;

// Note: There are not equal
if (fn_1 == fn_2)
    Console.WriteLine("These are Equal?");

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

Не злоупотребляйте KeyValuePair как взломать, чтобы не иметь Tuple<T1,T2>, Было бы довольно легко создать класс, который имеет Predicate и SomeStruct. Посмотрите:

public class MySegment
{   
     public Predicate<double> Predicate {get;set;}
     public SomeStruct Result {get;set;}
}

Чтобы просмотреть последовательность предикатов и найти совпадающие, они будут выглядеть так:

...
List<MySegment> list = new List<MySegment>();
...
list.Add(new MySegment { Predicate = d => d < 10, Result = SomeStruct.Foo });
list.Add(new MySegment { Predicate = d => d > 90, Result = SomeStruct.Bar });

...

public IEnumerable<SomeStruct> GetResults(double input)
{ 
    foreach (var item in list)
        if (item.Predicate(input))
             yield return item.Result;
}

Если ваш список предикатов не слишком длинный, вы можете просто добавить их в List<KeyValuePair<Predicate<T>, V>> и затем выполните запрос LINQ:

var lt10 = new KeyValuePair<Predicate<Double>, SomeStruct>(d => d < 10, SomeStruct.Foo);
var gt90 = new KeyValuePair<Predicate<Double>, SomeStruct>(d => d > 90, SomeStruct.Bar);
var predicates = new List<KeyValuePair<Predicate<Double>, SomeStruct>>() { lt10, gt90 };

var result = predicates.FirstOrDefault(p => p.Key(99));

Вам лучше использовать SomeStruct? вместо SomeStructкроме того, с тех пор FirstOrDefault даст однозначный результат, если он не совпадает ни с одним.

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

Это невозможно сделать с помощью словаря, поскольку он использует хеш-значения, чтобы быстро определить, где искать конкретный ключ.

Как вы обнаружили, вы можете вызывать предикаты напрямую, но это потребует вызова функций O(n), что не лучше, чем использование List или даже большого оператора if/then/else.

Если ваша коллекция потенциальных предикатов слишком длинная, чтобы это можно было использовать, вам необходимо создать собственную структуру данных, соответствующую вашим целям. Если вы планируете определять значения только на основе целочисленных диапазонов, это не должно быть сложным, но оно может выйти из-под контроля, если ваши предикаты станут более сложными.

Кстати, язык F#, который имеет встроенную поддержку для такого рода определений с использованием выражений совпадений. Я не знаю, как обстоят дела с компиляцией веток, но я предполагаю, что это довольно умно.

редактировать

Вот пример использования выражения соответствия в F# для чего-то вроде этого:

// Define the "choose" function
let choose value = 
    match value with
    | v when v < 10 -> 1
    | v when v > 90 -> 2
    | _ -> 0

// Test the "choose" function
let choice1 = choose 5
let choice2 = choose 15
let choice3 = choose 95

Код выше дает следующие значения:

choice1 = 1 
choice2 = 0 
choice3 = 2

Я никогда раньше не работал с F#, поэтому вам придется поискать, как использовать функцию из F# в программе на C#.

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

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