【问题标题】:Performant intersection and distinct element extraction?高性能交集和不同的元素提取?
【发布时间】:2009-11-05 18:21:33
【问题描述】:

我的代码中有如下一行:

potentialCollisionsX.Intersect(potentialCollisionsY).Distinct().ToList();

通过分析,我确定它占用了我大约 56% 的时间。我需要弄清楚如何提供有效的实现。我试过了

        List<Extent> probableCollisions = new List<Extent>();
        for (int j = 0; j < potentialCollisionsX.Count; j++)
        {
            if (potentialCollisionsY.Contains(potentialCollisionsX[j]) && !probableCollisions.Contains(potentialCollisionsX[j]))
            {
                probableCollisions.Add(potentialCollisionsX[j]);
            }
        }

但这只会降低到 42%。非常感谢优化或替代想法。

编辑:有人要求提供有关 Extent 类的信息,我想不出比提供类定义更好的方式来向他们提供信息。

    private enum ExtentType { Start, End }
    private sealed class Extent
    {
        private ExtentType _type;
        public ExtentType Type
        {
            get
            {
                return _type;
            }
            set
            {
                _type = value;
                _hashcode = 23;
                _hashcode *= 17 + Nucleus.GetHashCode();
            }
        }
        private Nucleus _nucleus; //Nucleus is the main body class in my engine
        public Nucleus Nucleus
        {
            get
            {
                return _nucleus;
            }
            set
            {
                _nucleus = value;
                _hashcode = 23;
                _hashcode *= 17 + Nucleus.GetHashCode();
            }
        }

        private int _hashcode;

        public Extent(Nucleus nucleus, ExtentType type)
        {
            Nucleus = nucleus;
            Type = type;
            _hashcode = 23;
            _hashcode *= 17 + Nucleus.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as Extent);
        }
        public bool Equals(Extent extent)
        {
            if (this.Nucleus == extent.Nucleus) //nucleus.Equals does an int comparison
            {
                return true;
            }
            return false;
        }
        public override int GetHashCode()
        {
            return _hashcode;
        }
    }

Edit2:似乎使用哈希集可以使我的这部分代码达到我需要的性能,所以感谢你的帮助!

【问题讨论】:

  • 您能否详细说明 Extent 类包含哪些数据以及如何比较它们?我考虑了几种不同的算法,具体取决于它们是否只能进行相等比较;比较较小/较大;或转换为某种二进制字符串(如整数值或其他东西)。
  • 您知道对于同一个 Nucleus,两个不同的范围(一个是起始范围,另一个是结束范围)将返回 Equals()==true,但哈希码不同?
  • 嗯,我应该可以解决这个问题。
  • 我记得为什么我现在不这样做,我不需要区分范围类型。回滚...
  • 那么你也应该让你的哈希码不区分它们。否则所有依赖它的算法都会失败

标签: c# .net optimization list intersection


【解决方案1】:

Intersect 无论如何都会返回不同的元素,因此无需调用Distinct()。这至少会占用你的一些时间。

另外,你真的需要打电话给ToList吗?然后你对结果做了什么?

顺序重要吗?如果没有,您应该考虑使用HashSet&lt;T&gt; 而不是List&lt;T&gt; 作为您的“手动”代码。 (并且可能还为potentialCollisionsY 创建一个HashSet&lt;T&gt;。)这将使Contains 调用更快,至少如果集合足够大的话......

顺便说一句,不要相信 documentation for Intersect - 它是 wrong about the order of operations(至少在 .NET 3.5 中)

【讨论】:

  • 在测试了一个没有 Distinct 的版本后,这段代码的耗时下降到了 14%。更多提示将不胜感激,但此代码的优化并不是最重要的。至于使用哈希集,顺序很重要,所以我认为我不能使用它。
  • 至于为什么我需要 ToList,我直接调用 RemoveAll 之后 IEnumerable 似乎没有。
  • @RCIX:只要用相反的条件做一个 Where 来过滤掉你不想要的东西。
  • 例如如果你在做.RemoveAll(x =&gt; x.IsFoo),你会使用.Where(x =&gt; !x.IsFoo)
【解决方案2】:

好的,我看到了 Extent 类的定义。首先,它违反了如果obj1.Equals(obj2)==true 那么obj1.GetHashCode()==obj2.GetHashCode() 的规则。但这不是重点,可以修复(如果你不这样做,依赖散列的算法,比如 HashSet 将会失败)。

现在,如果可以对 Extent 对象执行的唯一操作是比较是否相等,那么将不可能获得高于 O(N*M) 的最坏情况性能(其中 N 是第一个集合的大小,M 是第二个集合的大小)。那是因为您最终必须将每个元素与每个元素进行比较。

这可以通过使用GetHashCode() 以及具有不同哈希码的对象本身也会不同这一事实来改善。其他人建议使用HashSet 类,这就是这样的解决方案。在这种情况下,最好的情况是 O(N+M),最差的情况是 O(N+N*M)。平均而言,尽管您应该获胜,除非 GetHashCode() 方法实现得很差,并且为许多对象返回相同的哈希码。

我自己更喜欢更稳定的解决方案。如果可以可靠地对范围类进行排序(也就是说,如果您可以比较两个范围对象以查看哪个更大哪个更小),那么您可以对两个列表进行排序并且性能可以降低到 O (排序+M+N)。这个想法是,当列表被排序时,您可以同时浏览它们并在那里寻找相同的元素。

现在排序性能是棘手的事情。如果只实现比较操作(如IComparable 接口),您将能够在时间O(N*logN+M*logM) 中对两个列表进行排序。标准的List.Sort() 方法应该为您做到这一点。总而言之,总性能将是 O(N*logN+M*logM+N+M)。但是您应该注意,这使用了 QuickSort 算法,该算法在几乎排序的列表上表现不佳。最坏的情况是一个完全排序的列表,在这种情况下它是 O(N*M)。如果您的列表已经接近排序,您应该考虑另一种排序算法(并自己实现)。

如果您可以将每个 Extent 转换为一个整数(或更一般地,某个字符串),那么最终的可靠速度将是,如果字符串相等,则 Extents 也相等,如果字符串不相等相等,则范围也不相等。字符串的问题是它们可以用radix sortradix tree等算法在线性时间内排序。然后排序只需要O(N+M)的时间。事实上,如果你构建了一个基数树,你只需要对第一个列表进行排序,就可以直接在其中搜索字符串(每次搜索都需要 O(1) 时间)。总而言之,总性能将是 O(N+M),这是最好的。

不过,您应该始终牢记一件事 - 大算法有大常数。基数方法在纸面上可能看起来最好,但实施起来非常棘手,而且通常比处理少量数据的简单方法慢。只有当您的列表包含数千和数万范围内的元素时,您才应该开始考虑这一点。此外,这些算法需要创建大量新对象,并且每个new() 操作的成本也变得很大。您应该仔细考虑以尽量减少所需的分配数量。

【讨论】:

  • +1 嗯,我应该在发布自己的帖子之前仔细阅读这篇文章:)
【解决方案3】:

试试这个:

HashSet<Extent> result = new HashSet<Extent>();
HashSet<Extent> potentialSetY = new HashSet<Extent>(potentialCollisionsY);
foreach (Extent ex in potentialCollisionsX)
    if (potentialSetY.Contains(ex))
        result.Add(ex);

Hash sets擅长快速做Contains,但不要保持秩序


如果您需要保持顺序,这里有一些更复杂的东西:有序哈希集。它使用普通的哈希集语义(好吧,一个字典,但它是同一件事),但在枚举之前它根据插入顺序重新排序项目。

// Unchecked code

public class OrderedHashSet<T> : IEnumerable<T> {
    int currentIndex = 0;
    Dictionary<T, index> items = new Dictionary<T, index>();

    public bool Add(T item) {
        if (Contains(item))
            return false;
        items[item] = currentIndex++;
        return true;
    }

    public bool Contains(T item) {
        return items.ContainsKey(item);
    }

    public IEnumerator<T> GetEnumerator() {
        return items.Keys.OrderBy(key => items[key]).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}

现在只需将上述示例中的HashSet 更改为OrderedHashSet,它应该工作。

【讨论】:

  • 假设 Extent 类实现了一个好的 GetHashCode() 并且没有破坏如果 Extent1.Equals(Extent2)==true,那么 Extent1.GetHashCode()==Extent2.GetHashCode() 的规则.
  • 不幸的是,我需要在这些列表中保留订单。
  • 您可以在上面的代码中为result 使用List&lt;T&gt; - 但为了确保您不会重复,您可能需要有一个@ 987654329@ 个您已经看到的结果(您可以快速查找)。
  • 等一下,我可能只是不需要在这种情况下保留的订单....我想我只需要一个无序列表。我会试试这个,看看是否有帮助。
  • 或者您可以使用一项新发明 - OrderedHashSet&lt;T&gt;
【解决方案4】:

如果您无法提出更好的解决方案,请考虑使用非托管代码作为最后的手段。

【讨论】:

    【解决方案5】:

    两种方法:

    如果项目不存在,则将它们放入哈希图中,否则在哈希图中将它们标记为重复。这是 O(n)。然后你遍历哈希图中的所有项目,看看它们是否被标记为重复 - O(n) 再次。

    另一种方法:

    对两个列表进行排序。这是一个 O(n lg n) 操作,但至关重要的是,您可以愉快地维护两个列表始终排序,因此在专门寻找交集等时不会花费成本。

    然后依次遍历这两个列表,找到不同的和重复的 etc 条目。这是 O(n)。

    【讨论】:

    • 不,您只对一个列表进行排序(创建一个 HashSet),然后在其中搜索。
    • Vilx,如果我错了,请纠正我,但是构建 HashSet 是 O(N) 并且对 N 个项目的搜索是 O(1),因此总体而言是 O(N)。
    猜你喜欢
    • 2021-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多