2009-08-08 12 views
24

C'è qualche modo in C#. NET 2.0! combinare più predicti?Combina più predicti

Diciamo che ho il seguente codice.

List<string> names = new List<string>(); 
names.Add("Jacob"); 
names.Add("Emma"); 
names.Add("Michael"); 
names.Add("Isabella"); 
names.Add("Ethan"); 
names.Add("Emily"); 

List<string> filteredNames = names.FindAll(StartsWithE); 

static bool StartsWithE(string s) 
{ 
    if (s.StartsWith("E")) 
    { 
     return true; 
    } 
    else 
    { 
     return false; 
    } 
} 

Questo mi dà:

Emma 
Ethan 
Emily 

Quindi questa è roba abbastanza freddo, ma so che voglio essere in grado di filtrare l'utilizzo di più predicati.

quindi voglio essere in grado di dire qualcosa di simile:

List<string> filteredNames = names.FindAll(StartsWithE OR StartsWithI); 

al fine di ottenere:

Emma 
Isabella 
Ethan 
Emily 

Come posso raggiungere questo obiettivo? Attualmente sto solo filtrando l'elenco completo due volte e combinando i risultati in seguito. Ma sfortunatamente questo è abbastanza inefficiente e, ancora più importante, perdo l'ordinamento originale, che non è accettabile nella mia situazione.

Devo anche essere in grado di scorrere su un numero qualsiasi di filtri/predicati poiché ci può essere molto.

Anche in questo caso si ha la necessità di essere una soluzione .NET 2.0 purtroppo non riesco a utilizzare una versione più recente del quadro

Grazie mille.

risposta

46

ne dite:

public static Predicate<T> Or<T>(params Predicate<T>[] predicates) 
{ 
    return delegate (T item) 
    { 
     foreach (Predicate<T> predicate in predicates) 
     { 
      if (predicate(item)) 
      { 
       return true; 
      } 
     } 
     return false; 
    }; 
} 

E per completezza:

public static Predicate<T> And<T>(params Predicate<T>[] predicates) 
{ 
    return delegate (T item) 
    { 
     foreach (Predicate<T> predicate in predicates) 
     { 
      if (!predicate(item)) 
      { 
       return false; 
      } 
     } 
     return true; 
    }; 
} 

Quindi chiamare con:

List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE, StartsWithI)); 

Un'altra alternativa sarebbe quella di utilizzare i delegati multicast e poi dividerle usando GetInvocationList(), quindi fai la stessa cosa. Poi si potrebbe fare:

List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE+StartsWithI)); 

Io non sono un grande fan del secondo approccio anche se - ci si sente come un po 'di un abuso di multicasting.

+0

Grazie! Funziona perfettamente! – eric

+0

Molto utile. Necessari predicati multipli per un filtro su ICollectionView. – pStan

+0

L'intera funzione può essere abbreviata in 'return item => predicates.All (predicate => predicate (item));' – NibblyPig

0

È possibile creare un terzo predicato che integra internamente i risultati. Penso che potresti farlo al volo usando un'espressione lambda. Qualcosa di simile a questo (questa non è un'espressione lambda come io non sono troppo buono con quello snytax):

static bool StartsWithEorI(string s) 
{ 
    return StartsWithE(s) || StartsWithI(s); 
} 
+0

Certo che potrei ma come in questo esempio c'è un numero enorme di combinazioni. nemmeno pensando di combinare tre filtri ... Anche di nuovo purtroppo posso usare SOLO .NET 2.0 – eric

+0

Ah che è sfortunato. Sono sicuro che se tu avessi delegati anonimi o espressioni lambda, ti darebbe "al volo" unendo il potere che vuoi. – AaronLS

0

Si potrebbe avvolgere il metodo predicato in una classe e hanno il costruttore accetta un array di stringhe per testare :

class StartsWithPredicate 
{ 
    private string[] _startStrings; 
    public StartsWithPredicate(params string[] startStrings) 
    { 
     _startStrings = startStrings; 
    } 
    public bool StartsWith(string s) 
    { 
     foreach (var test in _startStrings) 
     { 
      if (s.StartsWith(test)) 
      { 
       return true; 
      } 
     } 
     return false; 
    } 
} 

allora si può fare una chiamata in questo modo:

List<string> filtered = names.FindAll((new StartsWithPredicate("E", "I")).StartsWith); 

in questo modo è possibile verificare per qualsiasi combinazione di stringhe di input senza la necessità di ampliare la base di codice con le nuove varianti del metodo StartsWith.

1

In .NET 2.0, ci sono delegati anonimi che si può usare lì:

List<string> filteredNames = names.FindAll(
    delegate(string s) { return StartsWithE(s) OR StartsWithI(s); } 
); 

In realtà, è possibile utilizzarlo per sostituire le funzioni così:

List<string> filteredNames = names.FindAll(
    delegate(string s) { return s.StartsWith("E") || s.StartsWith("I"); } 
); 
22

te immagino potrebbe scrivere qualcosa del genere:

Func<string, bool> predicate1 = s => s.StartsWith("E"); 
Func<string, bool> predicate2 = s => s.StartsWith("I"); 
Func<string, bool> combinedOr = s => (predicate1(s) || predicate2(s)); 
Func<string, bool> combinedAnd = s => (predicate1(s) && predicate2(s)); 

... e così via.

+0

Bella soluzione :) –

2

Ho appena trovato una soluzione simile a questo problema, che potrebbe essere anche utile. Ho ampliato il metodo FindAll per gli elenchi, che mi permette di impilare predicati negli elenchi di cui avevo bisogno:

public static class ExtensionMethods 
{ 
    public static List<T> FindAll<T> (this List<T> list, List<Predicate<T>> predicates) 
    { 
     List<T> L = new List<T>(); 
     foreach (T item in list) 
     { 
      foreach (Predicate<T> p in predicates) 
      { 
       if (!(p (item))) break; 
      } 
      L.Add (item); 
     } 
     return L; 
    } 
} 

Si restituisce una lista con solo gli elementi che corrispondono tutte le date predicati. Ovviamente può essere facilmente modificato in OR tutti i predicati invece di AND. Ma con questo solo si può assemblare una buona varietà di combinazioni logiche.

Usage:

{ 
    List<Predicate<int>> P = new List<Predicate<int>>(); 
    P.Add (j => j > 100); 
    P.Add (j => j % 5 == 0 || j % 7 == 0); 
    P.Add (j => j < 1000); 

    List<int> L = new List<int>() { 0, 1, 2, ... 999, 1000 } 
    List<int> result = L.FindAll (P); 

    // result will contain: 105, 110, 112, 115, 119, 120, ... 994, 995 
} 

Mi c'è voluto più mi piacerebbe ammettere a venire con questo. Ma delegati uomo ... delegati ...

0

Se si utilizza LINQ per db, ho trovato un'ottima soluzione here. Funzionando perfettamente. Non sono sicuro se funzionerà anche su .Net 2.0 o no.

0

Dopo aver utilizzato questo modello estesamente con il metodo dell'array 'params' sopra, sono rimasto incuriosito dall'apprendere di recente il delegato Multicast. Poiché i delegati supportano intrinsecamente un elenco (o multicast), è possibile saltare il pattern params [] e fornire semplicemente un singolo delegato alla funzione Test(). Dovrai chiamare GetInvokationList sul Predicato fornito <>. Vedere questo: Multicast delegate of type Func (with return value)?