2010-11-29 8 views
1

je Dictionary<Predicate<double>, SomeEnum>:Comment appeler Dictionnaire <K, V> .TryGetValue() où K: prédicats <T>, V: ENUM

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

Je veux appeler TryGetValue(K, out V) contre comme ceci:

dic.TryGetValue(99) 

et recevoir

SomeStruct.Bar 

Mais premier param pour TryGetValue() est Predicate<T>, pas seulement T. Comment puis-je faire ce que je veux?

J'ai trouvé qu'une solution sale:

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

Y at-il d'autres moyens?

Ou comment mettre en œuvre correctement mon idée? - déclare une clé non comme une constante mais comme un segment.

+8

Ce n'est pas comment utiliser une table de hachage. – leppie

+0

@leppie: D'accord. Dans mon cas, c'est juste un 'List >' (je ne peux pas utiliser 4.0 et 'List >') - – abatishchev

+0

Ce n'est pas comment utiliser lambdas. –

Répondre

3

Il y a quelques mauvaises choses ici:

Predicate<double> n'est pas un type approprié d'utiliser comme TKey. La clé pour un dictionnaire est supposé identifier une valeur, ne pas calculer une valeur.

Cela n'aurait aucun sens d'utiliser lambdas non plus. Parce qu'ils sont anonymes, vous n'obtiendrez aucune équivalence et vous ne pourrez pas utiliser un dictionnaire.

Voir cet exemple de code pour une 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?"); 

Si quoi que ce soit, vous pouvez utiliser une liste de délégués et d'exécuter chacun pour trouver ceux qui correspondent, mais à ce moment-là, vous devez attendre plusieurs résultats. Si vous voulez seulement obtenir un résultat unique, alors vous devez considérer quels ordre les prédicats sont stockés dans votre liste.

Ne pas abuser KeyValuePair comme un hack pour ne pas avoir Tuple<T1,T2>. Il serait assez facile de créer une classe qui a à la fois un Predicate et un SomeStruct. Regardez:

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

passer par une séquence de prédicats, et trouver ceux correspondant ressemblerait à ceci:

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

+1, pour une description claire des problèmes, bien que d'un point de vue du style je ferais GetResults 'retour de i dans la liste où i.Predicate (entrée) sélectionnez i.Result;'. Juste une question de préférence, cependant. – StriplingWarrior

+0

Merci beaucoup! Une seule note - les prédicats que j'ai un unique (parce que décrit les segments [0; 30], [70,100]) c'est pourquoi j'ai décidé d'utiliser Dictionary ou KVP. – abatishchev

+0

@StriplingWarrior: 'list.Where (item => item.Predicate (input))' fonctionne aussi correctement pour moi – abatishchev

2

Si votre liste de prédicats est pas trop long, vous pouvez simplement les ajouter à un List<KeyValuePair<Predicate<T>, V>> puis effectuez une requête 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)); 

Vous êtes mieux à l'aide SomeStruct? au lieu de SomeStruct, en outre, depuis alors FirstOrDefault donnera un résultat non ambigu s'il ne correspond à aucun résultat.

Si votre liste est très longue, vous voudrez considérer une sorte de structure de données qui permet des requêtes sur une plage, comme une Interval Tree.

+0

On dirait que j'ai décrit la même approche dans ma solution de contournement, non? – abatishchev

+1

Oui, mais en passant à List, vous utiliserez la structure de données correcte, ce qui vous donnera plus de performance. Un 'dictionnaire' n'est pas un bon ajustement ici. Utiliser une 'List' et itéter avec' FirstOrDefault' est une bonne approche pratique, et même optimale pour un petit nombre d'éléments. – codekaizen

+0

Ne compile pas. –

0

Vous devrez parcourir vos critères et exécuter chaque prédicat par rapport à l'entrée pour voir si elle correspond. Je ne vois aucune raison d'utiliser un dictionnaire ici.

+0

J'ai 'Dictionnaire ' juste parce qu'il est plus facile de l'écrire puis 'Liste >' comme je l'ai mentionné déjà dans le premier commentaire à la poste. – abatishchev

2

Cela ne peut pas être fait à l'aide d'un dictionnaire, car il dépend des valeurs de hachage pour déterminer rapidement où rechercher une clé particulière. Comme vous l'avez découvert, vous pouvez appeler les prédicats directement, mais cela nécessitera l'appel des fonctions O (n), ce qui n'est pas mieux que d'utiliser une instruction List ou même une grande instruction if/then/else.

Si votre collection de prédicats potentiels est trop longue pour que cela soit une option, vous devrez créer votre propre structure de données pour satisfaire vos besoins. Si vous prévoyez uniquement de définir des valeurs basées sur des plages d'entiers, cela ne devrait pas être difficile, mais cela pourrait devenir incontrôlable si vos prédicats deviennent plus complexes. Sur une note de côté, le langage F #, qui intègre la prise en charge de ce type de définition à l'aide de Match Expressions. Je ne sais pas comment ça se passe dans la compilation des succursales, mais je suppose que c'est assez intelligent à ce sujet.

Modifier

Voici un exemple d'utilisation d'une expression de match en F # quelque chose comme ceci:

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

Le code ci-dessus donne les valeurs suivantes:

choice1 = 1 
choice2 = 0 
choice3 = 2 

I » Je n'ai jamais travaillé avec F # auparavant, donc vous devrez chercher comment utiliser une fonction de F # dans un programme C#.

+0

Il semble que F # est vraiment ce dont j'ai besoin. Merci de m'avoir indiqué! – abatishchev

+0

Je n'imagine même pas comment cela devrait ressembler à F # et comment répondre à une question à ce sujet. Pourriez-vous s'il vous plaît m'aider? Comment décrire en notation F le problème? – abatishchev

+0

@abatischev: voir mes modifications. – StriplingWarrior

Questions connexes