2010-03-23 20 views
18

Sto cercando di implementare un operatore di confronto personalizzato su due liste di stringhe e utilizzare il metodo LINQ .Except() per ottenere quelli che non sono uno uno degli elenchi. La ragione per cui sto facendo un confronto personalizzato è perché ho bisogno di fare un confronto "fuzzy", cioè una stringa su una lista potrebbe essere incorporata all'interno di una stringa sull'altro elenco.LINQ Tranne e personalizzato IEqualityComparer

Ho fatto il seguente di confronto

public class ItemFuzzyMatchComparer : IEqualityComparer<string> 
{ 
    bool IEqualityComparer<string>.Equals(string x, string y) 
    { 
     return (x.Contains(y) || y.Contains(x)); 
    } 

    int IEqualityComparer<string>.GetHashCode(string obj) 
    { 
     if (Object.ReferenceEquals(obj, null)) 
      return 0; 
     return obj.GetHashCode(); 
    } 
} 

Quando il debug, l'unico punto di interruzione che colpisce è nel metodo GetHashCode(). The Equals() non viene mai toccato. Qualche idea?

+0

Per me è stato un buon esercizio. Nel mio caso sono riuscito a ottenere 'public int GetHashCode (string obj) {return obj.ToLower(). GetHashCode();}' La tua domanda è vecchia ma ho avuto lo stesso problema 4 anni dopo. –

risposta

18

Se tutti i codici hash restituiti sono diversi, non è mai ha la necessità di confrontare per l'uguaglianza.

In sostanza il problema è che il vostro hash e uguaglianza concetti sono molto diversi. Non sono del tutto sicuro di come lo correggeresti, ma fino a quando non lo avrai fatto, sicuramente non funzionerà.

È necessario assicurarsi che se Equals(a, b) restituisce true, quindi GetHashCode(a) == GetHashCode(b). (Il rovescio non deve essere vero - le collisioni di hash sono accettabili, anche se ovviamente si vuole avere il minor numero possibile di esse.)

+0

Sto iniziando a pensare che si tratta di un tentativo di applicare un confronto personalizzato su una raccolta di oggetti predefiniti (cioè stringhe). Se dovessi avere raccolte di oggetti personalizzati, allora potrei probabilmente farlo funzionare. Penso che dovrò trovare un modo migliore di quello. :(Lascio questo come senza risposta per un giorno per vedere se qualcun altro ha un suggerimento. – Joe

+0

ho finito per fare in due fasi. Ho generato un elenco di elementi che ha parzialmente abbina usando un contiene, poi si voltò e ha fatto a tranne usando quel sottoinsieme contro il primo elenco.Grazie per il tuo aiuto. – Joe

5

Come Jon ha sottolineato, è necessario assicurarsi che il codice hash di due stringhe uguali (in base alla regola di comparazione). Questo è sfortunatamente abbastanza difficile.

Per dimostrare il problema, Equals(str, "") restituisce true per tutte le stringhe str, che significa essenzialmente che tutte le stringhe sono uguali a una stringa vuota e, di conseguenza, tutte le stringhe devono avere lo stesso codice hash di una stringa vuota. Pertanto, l'unico modo per implementare IEqualityComparer correttamente è quello di restituire sempre lo stesso hash-code:

public class ItemFuzzyMatchComparer : IEqualityComparer<string> { 
    bool IEqualityComparer<string>.Equals(string x, string y) { 
    return (x.Contains(y) || y.Contains(x)); 
    } 
    int IEqualityComparer<string>.GetHashCode(string obj) { 
    if (Object.ReferenceEquals(obj, null)) return 0; 
    return 1; 
    } 
} 

quindi è possibile utilizzare il metodo Except e si comporterà in modo corretto. L'unico problema è che (probabilmente) otterrai un'implementazione piuttosto inefficiente, quindi se hai bisogno di prestazioni migliori, potresti dover implementare il tuo Except. Tuttavia, non sono esattamente sicuro di quanto sia inefficace l'implementazione LINQ e non sono sicuro che sia effettivamente possibile avere un'implementazione efficiente per la regola di confronto.

1

Forse questo problema potrebbe essere risolto senza l'implementazione dell'interfaccia IEqualityComparer. Jon e Thomas hanno buoni punti sull'implementazione di quell'interfaccia e l'uguaglianza non sembra definire il tuo problema. Dalla tua descrizione, penso che potresti farlo senza usare l'estensione Except durante il confronto. Invece, prima prendi le partite, poi fai l'eccezione. Vedere se questo fa il lavoro per voi:

List<String> listOne = new List<string>(){"hard", "fun", "code", "rocks"}; 
List<String> listTwo = new List<string>(){"fund", "ode", "ard"}; 

var fuzzyMatchList = from str in listOne 
         from sr2 in listTwo 
         where str.Contains(sr2) || sr2.Contains(str) 
         select str; 
var exceptList = listOne.Except(fuzzyMatchList); 
Problemi correlati