【问题标题】:Iterating over unordered pairs of elements stored in a collection迭代存储在集合中的无序元素对
【发布时间】:2011-09-11 17:44:30
【问题描述】:

在 C# 中,我有一组独特的元素,我想为每个无序对高效地执行一些代码。例如,如果我的容器包含 {a,b,c},则无序对是 (a,b)、(a,c) 和 (b,c)。问题出现在执行 2-opt 优化的范围内,因此效率是一个问题。

  • 我目前的解决方案如下:

    foreach(var a in container) 
    {
        foreach(var b in container)
        {
            if (a < b)
            {
                 // execute code    
            }
        }
     }
    

    显然,如果运算符 [] 可用于获取第 i 个元素(即如果底层数据结构是列表),则可以轻松修改。但是对于所有其他容器,解决方案取决于存在一些比较功能,效率不高。

  • 我还尝试了一种基于 LINQ 语句的公式,该语句只生成每个所需的对一次。然而,正如预期的那样,这比第一种方法慢得多。这也适用于使用ElementAt 的解决方案。

编辑:这是使用的(改进的)LINQ 代码:

var x = from a in container
        from b in container
        where a < b 
        select new KeyValuePair<int,int>(a,b);

不过,与其他解决方案相比,执行速度要慢 3-5 倍。

  • 这是我在 C++ 中的做法(获得良好的效率):

    for(auto it1 = container.begin(); it1!=container.end(); ++it1) 
    {
        auto it2 = it1;
        for(++it2; it2!=container.end(); ++it2) 
        {
            // execute code    
        }
     }
    

    不幸的是,要将其转换为 C#,需要克隆(内部使用的)枚举器,该语言本身不支持。

有人有更好的想法/解决方案吗?

【问题讨论】:

  • 您的 Linq 解决方案是什么样的?也许你的工作效率低下。
  • 你的 a
  • 为什么在 LINQ 语句中调用 ToList()?如果您所做的只是针对每个元素执行每个元素代码,则可以省去分配该列表。
  • ToList() 只是在我编写的代码中用于比较不同的解决方案...
  • 是反复迭代的问题还是比较方法本身的问题?最好的解决方案是支持索引运算符。

标签: c# mathematical-optimization


【解决方案1】:

您是否尝试先将元素复制到列表中,然后使用索引器 ([i]) 运算符执行算法?由于该算法无论如何都具有二次运行时间,因此在其前面进行线性复制操作可能可以忽略不计。您必须自己找出小型、中型和大型容器的实际运行时...

我认为这可能值得一试,这可能比每次都使用比较运算符要快得多。

您还可以检查容器是否为IList&lt;T&gt; 类型并跳过复制操作。

【讨论】:

  • 感谢您的回答,我只是快速检查了一下。考虑到迭代(包括复制)所花费的时间,复制到数组将性能提高了大约 20%!
  • 在上面提到的测试代码中,我生成了一个 List> 包含所需的对。用 KeyValuePair 替换 Tuple 表明浪费了大量时间。总的来说,我现在将运行时间减半。
【解决方案2】:

如果你不关心订单,你可以这样做:

int i = 0;
foreach (var a in list)
{
    int j = 0;
    foreach (var b in list)
    {
        if (i <= j)
            break;

        // execute code    

        j++;
    }

    i++;
}

如果您确实关心顺序,则可以将自己限制为实现 IList&lt;T&gt; 的集合,其中包含 [] 运算符。或者,您可以先将集合复制到 List&lt;T&gt;,然后再使用它。

【讨论】:

  • 感谢您的回答。快速检查您的代码的性能导致大约 10% 的改进。但是,复制速度更快……
【解决方案3】:

C# 中的枚举器与您问题中的 C++ 枚举器不同。在 C# 中,您既没有 begin 也没有 end 容器元素。你只有Current 元素和Next() 方法。它允许您产生更多的序列,例如 - 您可以枚举无穷大的随机数序列,这显然没有开始或结束。

所以 - 你不能像在 C++ 代码中那样只使用 IEnumerable 类在 C# 中做到这一点。最好的方法是使用System.Collection.Generics.IList&lt;T&gt; 接口。很多类型(比如数组)都继承了这个接口。

如果您使用IEnumerable,那么在您的类型中(在大多数情况下)您会遍历某个集合。如果你这样做 - 你可以实现IList&lt;T&gt; 接口。

还有另一种解决方案 - 在 C# 中,引用类型的列表和数组仅包含对对象的引用。所以 - 您可以将数据复制到本地列表并使用它进行操作。但这取决于您的内存和性能要求。

【讨论】:

  • 你必须在某处开始迭代序列,所以它必须有一个开始。
  • @svick 这只是第一个元素的错觉。如果您将在其他地方(在其他时间开启)迭代相同的对象 - 您将获得不同的第一个元素。所以,我认为将其命名为由类表示的序列的开始并不完全正确。这只是您从某个地方的班级获得的具体序列的开始。
  • 感谢您的回答。我知道 C++ 的迭代器和 C# 枚举器的区别。但我仍然缺少我曾经应用的功能。
【解决方案4】:

将您的项目放入 List 或 IList,然后您可以使用与您的 C++ 代码非常相似的模式使用索引来访问它们。

for (int i = 0; i < container.Count(); i++)
for (int j = i+1; j < container.Count(); j++)
{
    var item1 = container.Item[i];
    var item2 = container.Item[j];
}

我希望使用索引而不是 n^2 比较来迭代有序集合会更有效。比较运算符的效率很重要,但您根本不需要比较。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-17
    • 1970-01-01
    • 2018-06-09
    • 2012-05-13
    相关资源
    最近更新 更多