【问题标题】:Loop optimization within a nested loop c#嵌套循环中的循环优化c#
【发布时间】:2013-05-30 00:22:04
【问题描述】:

我正在比较从二进制文件生成的两个数据列表。我很清楚为什么它运行缓慢,当有大量记录时,它会做不必要的冗余工作。

例如,如果 a1 = a1,则条件为真。既然 2a != 1a 那么为什么还要麻烦检查呢?我需要从再次检查中消除 1a。如果我不这样做,它将在检查第 400,000 条记录时检查第一条记录。我想过将第二个 for 循环设为 foreach,但在遍历嵌套循环时无法删除 1a

“for 循环”中的项目数量可能会有所不同。我认为使用 'i' 的单个 for 循环不会起作用,因为匹配可以在任何地方。我正在读取二进制文件

这是我当前的代码。程序已经运行了一个多小时,它还在继续。出于可读性原因,我删除了很多迭代代码。

   for (int i = 0; i < origItemList.Count; i++)
    {
        int modFoundIndex = 0;
        Boolean foundIt = false;
        for (int g = 0; g < modItemList.Count; g++)
        {
            if ((origItemList[i].X == modItemList[g].X)
                && (origItemList[i].Y == modItemList[g].Y)
                && (origItemList[i].Z == modItemList[g].Z)
                && (origItemList[i].M == modItemList[g].M))
            {

            foundIt = true;
            modFoundIndex = g;
            break;

            }
            else
            {
                foundIt = false;
            }

        }
        if (foundIt)                         
        {
            /*
             * This is run assumming it finds an x,y,z,m 
             coordinate. It thenchecks the database file.
             * 
             */
            //grab the rows where the coordinates match 
            DataRow origRow = origDbfFile.dataset.Tables[0].Rows[i];
            DataRow modRow = modDbfFile.dataset.Tables[0].Rows[modFoundIndex];

            //number matched indicates how many columns were matched
            int numberMatched = 0;

            //get the number of columns to match in order to detect all changes
            int numOfColumnsToMatch = origDbfFile.datatable.Columns.Count;

            List<String> mismatchedColumns = new List<String>();

            //check each column name for a change
            foreach (String columnName in columnNames)
            {
                //this grabs whatever value is in that field                            
                String origRowValue = "" + origRow.Field<Object>(columnName);
                String modRowValue = "" + modRow.Field<Object>(columnName);

                //check if they are the same
                if (origRowValue.Equals(modRowValue))
                {
                    //if they aren the same, increase the number matched by one
                    numberMatched++;
                    //add the column to the list of columns that don't match

                }
                else
                {
                    mismatchedColumns.Add(columnName);
                }

            }
            /* In the event it matches 15/16 columns, show the change */
            if (numberMatched != numOfColumnsToMatch)
            {
                //Grab the shapeFile in question
                Item differentAttrShpFile = origItemList[i];
                //start blue highlighting
                result += "<div class='turnBlue'>";
                //show where the change was made at
                result += "Change Detected at<br/> point X: " +
                differentAttrShpFile.X + ",<br/> point Y: " +
                    differentAttrShpFile.Y + ",<br/>";
                result += "</div>"; //end turnblue div
                foreach (String mismatchedColumn in mismatchedColumns)
                {
                    //iterate changes here

                }

            }

        }

    }

【问题讨论】:

  • 您要处理多少条记录?
  • moditemlist 是否明显小于外部列表?然后,您只能遍历较小的列表,从而减少大量工作。您也许还可以将 x、y、z、m 的组合变成一种键来进行集合样式查找,而不是在整个列表中进行线性搜索?
  • 另外,您可以尝试使用 foreach 循环而不是每次通过索引器访问吗?
  • 大家好,他们通常拥有大约相同数量的记录。它们的大小从 20 到 250,000 不等。理想情况下,大文件不应超过几分钟。
  • 我会对每个列表进行排序 O(NLogN),然后是合并顺序循环 O(N)。我对哈希很敏感,但原则上它们可以让你整体降低到 O(N)。

标签: c# asp.net optimization for-loop


【解决方案1】:

你以完全错误的方式来解决这个问题。您拥有的循环是 O(n^2),当您找到匹配项时中断平均会将命中时间缩短一半,这还不够。如果列表中有 25 万个项目,那么这个循环将执行 620 亿次,即使编译器优化了额外的数组查找,您仍然需要查看至少一万亿条指令。如果可以的话,不要对大 n 做 O(n^2)!

您需要做的是摆脱 O(n^2) 方面的问题。我的建议:

1) 定义一个散列函数,查看 x、y、z 和 m 并得出一个整数值,我倾向于使用目标平台的字大小。

2) 遍历两个列表,计算所有内容的哈希值。

3) 为表、哈希和对象之一建立索引。我怀疑字典是这里最好的数据结构,但一个简单的排序数组也可以。

4) 遍历您没有建立索引的列表,将哈希值与索引中的条目进行比较。如果它是一个 O(n) 任务的哈希,如果它是一个排序数组,它是 O(n log n)。

5) 当哈希匹配时,请进行完整比较以确认命中是真实的,因为您会偶尔与良好的 64 位哈希发生冲突,如果您的哈希是 32 位,您将获得相当数量的冲突.

【讨论】:

  • 当我找到匹配项时(如果有 250,000 条记录,大部分将是匹配项)进行完整比较的最有效方法是什么?如果我再次执行完整的 foreach 循环,如果在所有 250,000 条记录中只有 2 条记录发生变化,我几乎无法获得任何速度。
  • 我在上面的例子中实现了 if (set2.Contains(t1)) { },我只是想避免做另一个 foreach 循环。
  • @EvanParsons 我不明白为什么你必须重新开始,尽管即使你这样做会执行得更快。基于散列或字典的方法将在循环中将其从数百亿次减少到数十万次。
  • 我很难理解第 3 步。我的自定义对象上有一个索引键,该对象中的键可用于进行行查找。但是我无法索引哈希集。我尝试制作数据行的“列表”,但遇到了与以前相同的性能损失。我的印象是字典是用于文本的?现在我正在尝试用 Datarows 填充字典。
  • @EvanParsons 这里的关键点是避免使用循环来搜索匹配项。可接受的最低性能是一个 SORTED 列表,您可以对其进行二进制搜索。理想的解决方案是 DictionaryHashSet,两者内部都是哈希表。
【解决方案2】:

这与 Loren 所说的类似,但下面是 .NET 语言:)
1。覆盖 GetHashCode 方法以返回 x、y、z 和 m 的总和。覆盖 Equals 方法来检查这个总和。
2。在循环之前从 modItemList (List) 迭代并创建 HashSet。
3。在内部循环中,首先使用 YourModHashSet.Contains(MyObject) 方法检查 origItemList[i] 是否存在于 HashSet 中。
4。如果 .Contains 返回 false,则携带一个,不匹配。
5。如果 .Contains 返回 true,则遍历整个 modItemList 并应用您当前的逻辑来检查整个列表的 x、y、z 和 m。请注意,在这里您应该使用 List 作为哈希表,可能会吃掉许多哈希码相同的对象。

另外,我会使用 Foreach 而不是 For,因为在这种情况下,我已经看到 Foreach 提供了更好的结果(快 5% 到 30%)。

更新:

我创建了如下 MyObject 类:

 public class MyObject
 {
    public int X, Y, Z, M;
    public override int GetHashCode()
    {
          return X*10000 + Y*100 + Z*10 + M;
    }

    public override bool Equals(object obj)
    {
        return (obj.GetHashCode() == this.GetHashCode());
    }
}

GetHashCode 方法在这里很重要。我们不想要很多误报。当哈希与 X、Y、Z 和 M 的其他组合匹配时会出现误报。防止误报的最佳方法是将每个成员相乘,这样每个成员都会影响 HashCode 中的一位小数。请注意,您应该考虑不超过 Int.Max 值。如果 X、Y、Z 和 M 的期望值很小,你应该是好的。

set2.Clear();
s1 = DateTime.Now;
MyObject matchingElement;
totalmatch = 0;

foreach (MyObject elem in list2)
set2.Add(elem);
foreach (MyObject t1 in list1)
{
if (set2.Contains(t1))
{
    matchingElement = null;
    foreach (MyObject t2 in list2)
    {
        if (t1.X == t2.X && t1.Y == t2.Y && t1.Z == t2.Z && t1.M == t2.M)
        {
            totalmatch++;
            matchingElement = t2;
            break;
        }
    }
    //Do Something on matchingElement if not null
}
}
Console.WriteLine("set foreach with contains: " + (DateTime.Now - s1).TotalSeconds + "\t Total Match: " + totalmatch);

以上是我试图在回答中描述的示例代码。如果预计匹配次数较少,则此代码应该会运行得非常快。

【讨论】:

  • 您不需要Equals 来检查哈希码。 HashSet 首先为您检查。
  • HashSet.Contain 方法不检查 Equals 吗?而不是 GetHashCode。
  • 不,它会检查两者,首先 GetHashCode 查看要检查的内部存储桶,然后在存储桶中的每个元素上调用 Equals 以查看哪个是正确的值。
  • @ScottChamberlain 所以我们应该像我说的那样覆盖 Equals。我们不想在我的逻辑中检查引用相等性。它永远不会匹配,因为两个列表的数据都是从不同的二进制文件中读取的(有问题)。
  • x、y、z 和 m 是双精度数,而不是整数。我不能超越它来返回双倍。将其解析为整数仍然可以使其准确吗?此外,它告诉我没有 Equals 方法可以覆盖。
猜你喜欢
  • 2021-01-21
  • 2015-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-28
  • 1970-01-01
相关资源
最近更新 更多