How to call Dictionary<K, V>.TryGetValue() where K : Predicate<T>, V : enum

一曲冷凌霜 提交于 2019-12-08 06:02:15

问题


I have Dictionary<Predicate<double>, SomeEnum>:

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

I want to call TryGetValue(K, out V) against it like this:

dic.TryGetValue(99)

and receive

SomeStruct.Bar

But first param for TryGetValue() is Predicate<T>, not just T. How can I do what I want?

I found only a dirty workaround:

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

Are there other ways?

Or how to implement my idea properly? - declare a key not as a constant but like a segment.


回答1:


There are a couple of things wrong here:

Predicate<double> is not an appropriate type to use as a TKey. The key for a dictionary is supposed to identify a value, not calculate a value.

This wouldn't make any sense using lambdas either. Because they are anonymous, you wouldn't get any equivalence, and won't be able use a dictionary.

See this code sample for an illustration:

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?");

If anything, you could use a list of delegates and execute each one to find the ones that match, but at that point you must expect multiple results. If you only want to get a single result, then you have to consider which order the predicates are stored within your list.

Don't misuse KeyValuePair as a hack for not having Tuple<T1,T2>. It would be fairly easy to create a class that has both a Predicate and a SomeStruct. Look:

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

To go through a sequence of predicates, and find the matching ones would look like this:

...
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;
}



回答2:


If your list of predicates is not too long, you can just add them to a List<KeyValuePair<Predicate<T>, V>> and then perform a LINQ query:

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));

You're better off using SomeStruct? instead of SomeStruct, furthermore, since then FirstOrDefault will give an unambiguous result if it doesn't match any.

If your list is very long, you will want to consider some kind of data structure which permits queries on a range, like an Interval Tree.




回答3:


This cannot be done using a Dictionary, because it relies on hash values to quickly determine where to look for a particular key.

As you've discovered, you can invoke the predicates directly, but that will require O(n) functions to be called, which is no better than using a List, or even a big if/then/else statement.

If your collection of potential predicates is too long for this to be an option, you'll need to create your own data structure to satisfy your purposes. If you're only planning to define values based on integer ranges, this shouldn't be difficult, but it could get out of hand if your predicates get more complex.

On a side note, the F# language, which has built-in support for this sort of definition using Match Expressions. I don't know how it goes about compiling the branches, but I assume it's fairly smart about it.

Edit

Here's an example of using a Match Expression in F# for something like this:

// 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

The code above yields the following values:

choice1 = 1 
choice2 = 0 
choice3 = 2

I've never actually worked with F# before, so you'll have to look around for how to use a function from F# in a C# program.




回答4:


You'll have to loop through your criteria and run each Predicate against the input to see if it matches. I don't see any reason to use a Dictionary here.



来源:https://stackoverflow.com/questions/4307535/how-to-call-dictionaryk-v-trygetvalue-where-k-predicatet-v-enum

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!