如何从列表<列表<T>>中删除值类型T的重复项?

  • 本文关键字:列表 类型 删除 c# linq
  • 更新时间 :
  • 英文 :


这是一个如此简单的问题,肯定已经有人问过并回答过了…但是我找不到它。

我想使用LINQ从值类型列表的列表中删除重复项。我尝试了以下操作:

List<List<int>> a = new List<List<int>>() { new List<int>() { 1, 2, 3 }, new List<int>() { 1, 2, 3 }, new List<int>() { 2, 3, 4 } };
// remove duplicates from a
List<List<int>> b = a.Distinct().ToList(); // this doesn't do it
List<List<int>> c = a.Distinct(new ListKeyComparer<int>()).ToList(); // nor does this
internal class ListKeyComparer<TKey> : IEqualityComparer<List<TKey>>
{
  public bool Equals(List<TKey> key1, List<TKey> key2)
  {
    return String.Join("_", key1).Equals(String.Join("_", key2));
  }
  public int GetHashCode(List<TKey> key)
  {
    return key.GetHashCode();
  }
}

欢迎所有解决方案!

您想要的是序列的IEqualityComparer。这并不是特别困难。(请注意,您可以随意地将示例概括为通用的,而不是特定于int的,并使用IEnumerable而不是List,因为您不需要特定于列表的功能。

public class SequenceComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    private IEqualityComparer<T> comparer;
    public SequenceComparer(IEqualityComparer<T> comparer = null)
    {
        comparer = comparer ?? EqualityComparer<T>.Default;
    }
    public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
    {
        return x.SequenceEqual(y, comparer);
    }
    public int GetHashCode(IEnumerable<T> sequence)
    {
        unchecked
        {
            int hash = 19;
            foreach (var item in sequence)
                hash = hash * 79 + comparer.GetHashCode(item);
            return hash;
        }
    }
}

EqualsSequenceEqual的形式免费提供给您。剩下唯一有趣的事情是基于序列中的值创建一个有意义的哈希,而不是使用序列本身提供的GetHashCode方法,因为它通常不会这样做(大多数IEnumerable,包括List,将基于对类的引用,而不是其中的值)。

在本例中,不需要为SequenceComparer提供项类型的内部比较器(在本例中为int),因为默认的相等性应该正是您所需要的。例如,如果您有一个List<List<string>>,并且您想比较列表是否相等,并对字符串进行不区分大小写的比较,那么您可以使用new SequenceComparer<string>(StringComparer.InvariantCultureIgnoreCase)

注意,连接项的字符串值并不是比较两个序列的特别安全的方法。对象可能没有有意义的ToString方法。(任何不覆盖ToString的类型将只打印类型名称,这意味着所有内容都等于其他内容。)您还需要处理碰撞的情况。例如,如果您有一个生成字符串值"1_2"的项,它将被认为等于两个不同的项,每个项生成"1""2"

您实现的问题是它使用key列表中的straight GetHashCode。您可以通过将其替换为"键字符串"的哈希码来修复它,该"键字符串"是通过将数字与下划线连接起来构建的,或者通过动态计算哈希码:

// Here is a fix to your method. It would work if TKey values
// cannot have underscores. In any event, it will be very slow.
internal class ListKeyComparer<TKey> : IEqualityComparer<List<TKey>>
{
  // Make a method that produces the key to avoid repeating yourself:
  private string MakeKey(List<TKey> key) {
    return String.Join("_", key);
  }
  public bool Equals(List<TKey> key1, List<TKey> key2)
  {
    return MakeKey(key1).Equals(MakeKey(key2));
  }
  public int GetHashCode(List<TKey> key)
  {
    return MakeKey(key).GetHashCode();
  }
}

下面是一个更好的实现:

internal class ListKeyComparer<TKey> : IEqualityComparer<List<TKey>>
{
  public bool Equals(List<TKey> key1, List<TKey> key2)
  {
    return key1.SequenceEqual(key2);
  }
  public int GetHashCode(List<TKey> key)
  {
    return key.Aggregate((p, v) => 31*p + v.GetHashCode());
  }
}

这个实现更好,有三个原因:

  • 更具可读性 -每个方法都是单行,这或多或少是不言自明的(假设您熟悉计算多部分密钥的哈希码)
  • 这是更有效的 -这段代码避免构造字符串,将在哈希你的键的过程中被反复丢弃
  • 它提高了正确性 -即使TKey字符串包含下划线,这个实现也能正确工作。

实现使用LINQ方法SequenceEqualAggregate来缩短EqualsGetHashCode的代码。

相关内容

  • 没有找到相关文章

最新更新