2010-09-30 3 views
5

Я следую за previous post на stackoverflow об удалении дубликатов из списка на C#.удаление дубликатов из списка C#

Если <T> некоторые определяемые пользователем типа, как:

class Contact 
{ 
    public string firstname; 
    public string lastname; 
    public string phonenum; 
} 

Предложенное (HashMap) не удаляет дубликаты. Я думаю, мне нужно переопределить некоторый метод сравнения двух объектов, не так ли?

ответ

18

A HashSet<T>делает удаляет дубликаты, потому что это набор ... но только тогда, когда ваш тип определяет равенство соответствующим образом.

Я подозреваю, по «продублировать» вы имеете в виду «объект с одинаковыми значениями поля к другому объекту» - вам необходимо изменить Equals/GetHashCode для этого на работу, и/или осуществлять IEquatable<Contact> ... или вы могли бы обеспечить IEqualityComparer<Contact> к конструктору HashSet<T>.

Вместо использования HashSet<T> вы могли просто вызвать метод расширения Distinct LINQ. Например:

list = list.Distinct().ToList(); 

Но опять-таки вам необходимо предоставить соответствующее определение равенства, так или иначе.

Вот пример реализации. Обратите внимание, как я сделал его неизменным (равенство нечетно с изменяемыми типами, потому что два объекта могут быть равны одной минуте и не равны следующей) и сделали поля private, с общедоступными свойствами. Наконец, я запечатал класс - неизменяемые типы, как правило, должны быть запечатаны, и это облегчает обсуждение равенства.

using System; 
using System.Collections.Generic; 

public sealed class Contact : IEquatable<Contact> 
{ 
    private readonly string firstName; 
    public string FirstName { get { return firstName; } } 

    private readonly string lastName; 
    public string LastName { get { return lastName; } } 

    private readonly string phoneNumber; 
    public string PhoneNumber { get { return phoneNumber; } } 

    public Contact(string firstName, string lastName, string phoneNumber) 
    { 
     this.firstName = firstName; 
     this.lastName = lastName; 
     this.phoneNumber = phoneNumber; 
    } 

    public override bool Equals(object other) 
    { 
     return Equals(other as Contact); 
    } 

    public bool Equals(Contact other) 
    { 
     if (object.ReferenceEquals(other, null)) 
     { 
      return false; 
     } 
     if (object.ReferenceEquals(other, this)) 
     { 
      return true; 
     } 
     return FirstName == other.FirstName && 
       LastName == other.LastName && 
       PhoneNumber == other.PhoneNumber; 
    } 

    public override int GetHashCode() 
    { 
     // Note: *not* StringComparer; EqualityComparer<T> 
     // copes with null; StringComparer doesn't. 
     var comparer = EqualityComparer<string>.Default; 

     // Unchecked to allow overflow, which is fine 
     unchecked 
     { 
      int hash = 17; 
      hash = hash * 31 + comparer.GetHashCode(FirstName); 
      hash = hash * 31 + comparer.GetHashCode(LastName); 
      hash = hash * 31 + comparer.GetHashCode(PhoneNumber); 
      return hash; 
     } 
    } 
} 

EDIT: Хорошо, в ответ на просьбы о объяснении GetHashCode() реализации:

  • Мы хотим объединить хэш-коду свойств этого объекта
  • Мы не проверять для ничтожества в любом месте, поэтому мы должны предположить, что некоторые из них могут быть нулевыми. EqualityComparer<T>.Default всегда обрабатывает это, что приятно ... поэтому я использую это, чтобы получить хэш-код каждого поля.
  • «Добавление и умножение» подхода к объединению нескольких хеш-кодов в один из них является стандартным, рекомендованным Джошем Блохом. Существует множество других алгоритмов хэширования общего назначения, но это отлично подходит для большинства приложений.
  • Я не знаю, по умолчанию ли вы компилируете в проверенном контексте, поэтому я поставил вычисление в неконтролируемый контекст. We действительно все равно, если повторное умножение/добавление приводит к переполнению, потому что мы не ищем «величину» как таковую ... просто число, которое мы можем неоднократно использовать для равных объектов.

два альтернативных способа обработки недействительности, кстати:

public override int GetHashCode() 
{ 
    // Unchecked to allow overflow, which is fine 
    unchecked 
    { 
     int hash = 17; 
     hash = hash * 31 + (FirstName ?? "").GetHashCode(); 
     hash = hash * 31 + (LastName ?? "").GetHashCode(); 
     hash = hash * 31 + (PhoneNumber ?? "").GetHashCode(); 
     return hash; 
    } 
} 

или

public override int GetHashCode() 
{ 
    // Unchecked to allow overflow, which is fine 
    unchecked 
    { 
     int hash = 17; 
     hash = hash * 31 + (FirstName == null ? 0 : FirstName.GetHashCode()); 
     hash = hash * 31 + (LastName == null ? 0 : LastName.GetHashCode()); 
     hash = hash * 31 + (PhoneNumber == null ? 0 : PhoneNumber.GetHashCode()); 
     return hash; 
    } 
} 
+0

Точно то, что я написал (если бы не было слишком поздно :-) –

+1

Эта реализация GetHashCode() должна содержать пояснительные комментарии. Мой мозг не работает на вашем уровне. –

+0

Можете ли вы, пожалуйста, метод GetHashCode, я, похоже, не следую этому примеру. – Sandy

1
class Contact { 
    public int Id { get; set; } 
    public string Name { get; set; } 

    public override string ToString() 
    { 
     return string.Format("{0}:{1}", Id, Name); 
    } 

    static private IEqualityComparer<Contact> comparer; 
    static public IEqualityComparer<Contact> Comparer { 
     get { return comparer ?? (comparer = new EqualityComparer()); } 
    } 

    class EqualityComparer : IEqualityComparer<Contact> { 
     bool IEqualityComparer<Contact>.Equals(Contact x, Contact y) 
     { 
      if (x == y) 
       return true; 

      if (x == null || y == null) 
       return false; 

      return x.Name == y.Name; // let's compare by Name 
     } 

     int IEqualityComparer<Contact>.GetHashCode(Contact c) 
     { 
      return c.Name.GetHashCode(); // let's compare by Name 
     } 
    } 
} 

class Program { 
    public static void Main() 
    { 
     var list = new List<Contact> { 
      new Contact { Id = 1, Name = "John" }, 
      new Contact { Id = 2, Name = "Sylvia" }, 
      new Contact { Id = 3, Name = "John" } 
     }; 

     var distinctNames = list.Distinct(Contact.Comparer).ToList(); 
     foreach (var contact in distinctNames) 
      Console.WriteLine(contact); 
    } 
} 

дает

1:John 
2:Sylvia 
1

Для решения этой задачи у меня нет necessar ily считает, что внедрение IComparable является очевидным решением. Вы можете сортировать и тестировать уникальность по-разному.

я бы пользу реализации IEqualityComparer<Contact>:

sealed class ContactFirstNameLastNameComparer : IEqualityComparer<Contact> 
{ 
    public bool Equals (Contact x, Contact y) 
    { 
    return x.firstname == y.firstname && x.lastname == y.lastname; 
    } 

    public int GetHashCode (Contact obj) 
    { 
    return obj.firstname.GetHashCode()^obj.lastname.GetHashCode(); 
    } 
} 

И затем использовать System.Linq.Enumerable.Distinct (предполагая, что вы используете, по крайней мере .NET 3.5)

var unique = contacts.Distinct (new ContactFirstNameLastNameComparer()).ToArray(); 

PS. Говоря о HashSet<> Обратите внимание, что HashSet<> принимает параметр IEqualityComparer<> в качестве параметра конструктора.

Смежные вопросы