【问题标题】:C# Performance setting value for each list item每个列表项的 C# 性能设置值
【发布时间】:2010-11-28 17:33:54
【问题描述】:

我正在尝试找到一种快速的方法来设置通用列表中每个项目的特定属性。

基本上,要求是遍历项目列表并将 IsHit 属性重置为 FALSE。之后只有第二个“命中”列表中的项目应设置为 TRUE。

我的第一次尝试是这样的:

listItems.ForEach(delegate(Item i) { i.IsHit = false; });

foreach (int hitIndex in hits)
{
    listItems[hitIndex - 1].IsHit = true;
}

注意:命中是从 1 开始的,项目列表是从 0 开始的。

然后我尝试提高速度并想出了这个:

for (int i = 0; i < listItems.Count; i++)
{
    bool hit = false;
    for (int j = 0; j < hits.Count; j++)
    {
        if (i == hits[j] - 1)
        {
            hit = true;
            hits.RemoveAt(j);
            break;
        }
    }

    if (hit)
    {
        this.listItems[i].IsHit = true;
    }
    else
    {
        this.listItems[i].IsHit = false;
    }
}

我知道这是一个微优化,但它确实是对时间敏感的代码,因此改进此代码以超出可读性是有意义的......当然只是为了好玩;-)

不幸的是,我真的看不到任何进一步改进代码的方法。但我可能错过了什么。



谢谢

PS:最好使用 C# / .NET 2.0 中的代码。


我最终改用 Eamon Nerbonne 解决方案。但后来我注意到我的基准测试中有一些奇怪的地方。

委托人:

listItems.ForEach(delegate(Item i) { i.IsHit = false; });

比:

foreach (Item i in listItems)
{
    i.IsHit = false;
}

这怎么可能?

我试图查看 IL,但那只是我想不到的……我只看到代表的行数减少了,不管这意味着什么。

【问题讨论】:

  • 有机会将“命中”列表更改为哈希表吗?通过删除第二个 for 循环将大大提高性能。

标签: c# performance search list


【解决方案1】:

嵌套的 for 循环是多余的,特别是“remove”调用本身代表了另一个 for 循环。总而言之,您的第二个优化版本的时间复杂度比第一个解决方案更差,尤其是在有很多匹配项时。

最快的解决方案可能如下:

foreach(var item in listItems)
    item.IsHit = false;
foreach (int hitIndex in hits)
    listItems[hitIndex - 1].IsHit = true;

这避免了低效的嵌套 for 循环,并避免了基于委托的 .ForEach 方法的开销(这是一种很好的方法,但在性能关键代码中不是)。它涉及稍微更频繁地设置 IsHit,但大多数属性设置器都是微不足道的,因此这可能不是瓶颈。在任何情况下,快速的微基准测试都可以作为良好的健全性检查。

只有IsHit真的很慢,下面的会更快:

bool[] isHit = new bool[listItems.Count]; //default:false.
//BitArray isHit = new BitArray(listItems.Count);  
//BitArray is potentially faster for very large lists.
foreach (int hitIndex in hits)
    isHit [hitIndex - 1] = true;
for(int i=0; i < listItems.Count; i++)
    listItems[i].IsHit = isHit[i];

最后,考虑使用数组而不是List&lt;&gt;。如果您可以避免需要 List&lt;&gt; 类型的插入/删除方法,数组通常会更快。

var 关键字是 C# 3.5,但可以在 .NET 2.0 中使用(新语言功能通常不需要更新的库版本 - 只是它们对那些更新的库最有用)。当然,您知道List&lt;&gt; 所专门化的类型,并且可以显式指定它。

【讨论】:

  • 关于 OP 的第二个版本的复杂性的小注释:Remove 调用将精确地迭代那些命中 not 被显式内部 for 循环迭代的元素,所以总体该解决方案的复杂性约为listItems.Count × hits.Count
【解决方案2】:

你能把第二个列表中的项目放入字典吗? 如果是这样,您可以这样做:

for( int i = 0; i < firstList.Count; i++ )
{
   firstList[i].IsHit = false;

   if( secondList.Contains (firstList[i].Id) )
   {
       secondList.Remove (firstList[i].Id);
       firstList[i].IsHit = true;
   }
}

其中 secondList 是 Dictionary offcourse。

通过将 histlist 中的项目放入 Dictionary 中,您可以使用 O(1) 操作检查项目是否包含在该列表中。 在上面的代码中,我使用了某种 Item 的唯一标识符作为字典中的 Key。

【讨论】:

  • 你可以使用 HashSet 来代替字典。此外,如果您确定所有命中都将被“使用”,则无需从字典/集合中删除命中 - 只需在循环后清空集合。
  • 是的,您对 HashSet 集合的看法是正确的。我有时会忘记它的存在:o。关于从集合或字典中删除项目:我刚刚这样做了,因为我看到 topicstarter 在他的示例代码中也这样做了。或许他还有别的原因,但在使用集合或字典的性能方面确实没有必要这样做。
  • HashSet&lt;&gt;Dictionary&lt;&gt; 比普通的旧数组慢很多 - 而且由于您正在处理密集的整数索引,因此哈希集和字典在这里没有有意义的附加功能。
  • 好吧,似乎没有礼貌的声誉点......至少对评论进行投票会很好。
【解决方案3】:

您可以对 hits 集合进行排序并执行二进制搜索,然后您将是 O(n log2 n) 而不是 O(n2)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-16
    • 2014-03-21
    • 2017-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多