【问题标题】:Removing duplicate pairs of a List<object>删除 List<object> 的重复对
【发布时间】:2014-07-20 08:18:51
【问题描述】:

问题

我正在使用 3D WPF 库中的简单线条显示几何图形。在下一张图片中可以看到它的一个例子:

您可以在其中看到一组三角形和四边形。我绘制这个的方式是我提供了一个List&lt;Point3D&gt;,我在其中放置了代表每个段的点对。

问题是有很多重复的边,我想避免这种情况,因为这种类型的表示似乎非常需要资源。

点列表是在每个包含 N 个顶点的Element 上迭代生成的。它不知道是否共享特定的边缘。

&lt;p0, p1, p1, p2, p2, p333, p333, p89, p89, p2, p2, p1 ...&gt;

这个想法是删除重复的对(注意顺序可能不一样)。在上面的示例中,删除的对应该是最后一个 (p2, p1),因为它表示与第二对点 (p1, p2) 相同的边。可能有一对、两对或更多对重复的点。

我需要尽快执行此操作,性能是这里的首要任务。

想法

在列表中添加点时,我可以暂时存储其中两个并检查列表是否已包含它们,但这意味着每次添加点时都要查看列表,这对我来说似乎不是一个好主意(列表将包含数千个点 5000-50000)。

我生成点列表的元素具有多个具有唯一 ID 的节点,因此我认为可以通过创建有序 Tuple&lt;Point3D, Point3D&gt;Dictionary 然后删除重复项来以某种方式使用它。

我还没有尝试过最后一个想法,因为我还不确定如何实现它,我想知道是否还有其他可以做的事情。

【问题讨论】:

  • 你的例子只是给出了一个没有连接的点列表。如果表示为对/元组会很容易。只需要写一个比较器,告诉你是否使用了相同的坐标,也就是说它们是相同的,可以去掉。
  • 此列表解释为将每对转换为一个段。我认为比较节点 ID 比查看坐标要快得多。

标签: c# wpf list duplicates


【解决方案1】:

您可以使用HashSet 来存储所有边。检查速度很快,边缘是否已经设置好。但是你应该覆盖GetHashCodeEquals。我做了一个简单的例子。

class MyLine
{
    public MyPoint P1 { get; private set; }
    public MyPoint P2 { get; private set; }
    public MyLine(MyPoint p1, MyPoint p2)
    {
        P1 = p1;
        P2 = p2;
    }
    protected bool Equals(MyLine other)
    {
        return (Equals(P1, other.P1) && Equals(P2, other.P2)) || Equals(P1, other.P2) && Equals(P2, other.P1);
    }
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((MyLine)obj);
    }
    public override int GetHashCode()
    {
        unchecked
        {
            return P1.GetHashCode() + P2.GetHashCode();
        }
    }
}
class MyPoint
{
    public string Id { get; private set; }
    public MyPoint(string id)
    {
        Id = id;
    }
    protected bool Equals(MyPoint other)
    {
        return string.Equals(Id, other.Id);
    }
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((MyPoint)obj);
    }
    public override int GetHashCode()
    {
        return (Id != null ? Id.GetHashCode() : 0);
    }
}

那么你应该可以像这样添加每一行:

public static void Main(string[] args)
{
    HashSet<MyLine> lines = new HashSet<MyLine>();
    var line = new MyLine(new MyPoint("a"), new MyPoint("b"));
    lines.Add(line);
    line = new MyLine(new MyPoint("b"), new MyPoint("a"));
    lines.Add(line);
}

还可以使用GetHashCodeEquals 将所有行存储在List 中,然后使用Distinct 方法。

public static void Main(string[] args)
{
    List<MyLine> lines = new List<MyLine>();
    var line = new MyLine(new MyPoint("a"), new MyPoint("b"));
    lines.Add(line);
    line = new MyLine(new MyPoint("b"), new MyPoint("a"));
    lines.Add(line);
    lines = lines.Distinct().ToList();
}

【讨论】:

  • 再看看你的GetHashCode方法。就目前而言,它只是在字段的哈希码总和中添加一个常数。
  • @MatthewStrawbridge 是的,我刚刚将这个stackoverflow.com/a/263416/1237491 升级为点对称。不确定它是否是一个好的哈希码。有什么建议可以改进吗?
  • 好的,我已经更新了。关键是整个哈希值在每一步都需要乘以 23,而在第一个字段之后你就错过了。
  • @MatthewStrawbridge 不,我是故意的。看equals方法。 (p1,p2) 和 (p2, p1) 的哈希值必须相同。
  • 好吧,我明白你的意思了。那么您不妨只使用不带任何素数的总和:它们不添加任何内容(即,就好像您只有一个字段,而不是两个字段)。
【解决方案2】:

使用 HashSet&lt;Tuple&lt;Point3D, Point3D&gt;&gt; 。每当你得到一条新边 - p1,p2,检查 (p1,p2) 在集合中的存在。还要检查 (p2,p1) 的存在。如果都不存在,则将 (p1,p2) 和 (p2,p1) 添加到集合中并使用边。

您可以通过制作自己的哈希和相等函数来进一步加快这一速度,这些函数将看到 (p1,p2) 等于 (p2,p1)。除非需要,否则不要这样做,Set 操作非常快,我怀疑改进会很大。

【讨论】:

  • 1. C# 没有Set,只有HashSet。 2. 您应该同时检查(p1,p2) (p2,p1) 或不检查任何内容并添加(p1,p2) (p2,p1)。如果元组已经存在,HashSet 将自动跳过添加。
猜你喜欢
  • 2015-05-23
  • 1970-01-01
  • 1970-01-01
  • 2019-03-24
  • 2019-04-06
  • 2016-07-02
  • 2015-12-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多