Pytanie linq Except i custom IEqualityComparer


Próbuję zaimplementować niestandardowego porównywalnika na dwóch listach ciągów i użyć metody linq .Except (), aby uzyskać te, które nie są jedną z list. Powód, dla którego robię niestandardowy porównywal, polega na tym, że muszę wykonać "rozmyte" porównanie, tzn. Jeden ciąg na jednej liście może być osadzony w ciągu znaków na drugiej liście.

Zrobiłem następujący porównań

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

Podczas debugowania jedynym punktem przerwania, który trafia, jest metoda GetHashCode (). The Equals () nigdy się nie dotknie. Jakieś pomysły?


18
2018-03-23 15:11


pochodzenie


Dla mnie było to dobre ćwiczenie. W moim przypadku udało mi się uciec public int GetHashCode(string obj) {return obj.ToLower().GetHashCode();} Twoje pytanie jest stare, ale wpadłem na ten sam problem 4 lata później. - T.S.


Odpowiedzi:


Jeśli wszystkie zwrócone kody skrótu są różne, nigdy nie musi się porównywać dla równości.

Zasadniczo problem polega na tym, że twoje hash i koncepcje równości są bardzo różne. Nie jestem do końca pewien, jak to poprawić, ale dopóki tego nie zrobisz, to na pewno nie zadziała.

Musisz się upewnić, że jeśli Equals(a, b) zwraca true, a następnie GetHashCode(a) == GetHashCode(b). (Odwrotność nie musi być prawdą - kolizje hash są dopuszczalne, chociaż oczywiście chcesz mieć jak najmniejszą ich liczbę).


18
2018-03-23 15:13



Zaczynam myśleć, że jest to przypadek próby zastosowania niestandardowego porównania na zbiorze predefiniowanych obiektów (tj. Ciągów znaków). Gdybym miał kolekcje niestandardowych obiektów, prawdopodobnie mógłbym je uruchomić. Myślę, że będę musiał wymyślić lepszą drogę niż to. :( Pozostawię to bez odpowiedzi przez jeden dzień, aby sprawdzić, czy ktokolwiek ma sugestię. - Joe
Zrobiłem to w dwóch etapach. Wygenerowałem listę elementów, które częściowo pasowały przy użyciu pliku zawierającego, a następnie odwróciły się i wykonały oprócz użycia tego podzestawu na pierwszej liście. Dzięki za pomoc. - Joe


Jak zauważył Jon, musisz się upewnić, że kod skrótu dwóch łańcuchów jest równy (zgodnie z twoją regułą porównania). Jest to niestety dość trudne.

Aby zademonstrować problem, Equals(str, "") zwraca true dla wszystkich łańcuchów str, co zasadniczo oznacza, że ​​wszystkie ciągi są równe pustemu ciągowi, w wyniku czego wszystkie ciągi muszą mieć ten sam kod skrótu jako pusty ciąg. Dlatego jedynym sposobem realizacji IEqualityComparer poprawnie jest zwrócić zawsze ten sam kod skrótu:

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

Następnie możesz użyć Except metoda i będzie się zachowywać poprawnie. Jedynym problemem jest to, że (prawdopodobnie) uzyskasz dość nieefektywną implementację, więc jeśli potrzebujesz lepszej wydajności, być może będziesz musiał zaimplementować własne Except. Jednak nie jestem do końca pewny, jak mało wydajna będzie implementacja LINQ i nie jestem pewien, czy faktycznie możliwe jest sprawne wdrożenie reguły porównania.


5
2018-03-23 16:23





Być może problem ten można rozwiązać bez implementacji interfejsu IEqualityComparer. Jon i Thomas mają dobre punkty w implementowaniu tego interfejsu, a równość nie wydaje się definiować twojego problemu. Z Twojego opisu myślę, że możesz to zrobić bez użycia rozszerzenia Z wyjątkiem podczas porównywania. Zamiast tego najpierw zdobądź zapałki, a następnie Zezwalaj. Sprawdź, czy to zadanie dla Ciebie:

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

1
2018-03-24 19:41