如何使这些foreach循环更高效?

4

我有一个 (PatchFacilityManager) 列表和一个 (Int) facilityManagerId 的列表。我想让下面的代码更加高效。是否有任何方法可以删除这两个 foreach 循环。

 foreach (PatchFacilityManager PM in patchFacilityManager)
 {
     foreach (int FM in facilityManagerId)
     {
         if (PM.FacilityManagerId == FM)
         {
             PM.IsSelected = true;
         }
     }
 }

1
小优化:在设置PM.IsSelected后添加一个“break”,这样您就可以跳过facilityManagerId列表的其余部分。 - Hans Kesting
如果在facilityManagerId中找不到与PM.facilityManagerId匹配的项,应该将PM.IsSelected设置为false吗? - this. __curious_geek
1
PM.IsSelected已经设置为false,因为它是默认值(从SP返回0),所以不需要再设置为false。 - Rohit Raghuvansi
好的,我进行了一次快速的分析会话来确认答案。@this.__curious_geek的解决方案在每个列表中有1000个项目时更快,因为HashSet的构造函数的开销很大 - 对于任何关心的人来说,它是O(n)。所以我将每个列表中的项目数增加到了10000。此时,LukeH的解决方案开始轻松获胜。如果我再增加它,就有点儿荒谬了。结论是,如下所述,@LukeH的解决方案是您想要的。您应该更改所选答案。 - Mel Harbour
当我说“变得愚蠢”时,我是真的这么认为。每个列表中有1000000个元素,LukeH的解决方案大约需要50毫秒才能运行。而this.__curious_geek的解决方案则需要太长时间(> 2分钟),以至于我感到无聊并停止了它。 - Mel Harbour
7个回答

7

以下是一种方法:

    foreach (PatchFacilityManager PM in patchFacilityManager)
    {
        PM.IsSelected = facilityManagerId.Contains(PM.FacilityManagerId);
    }

编辑

我认为,与问题中给出的代码相比,这个解决方案在三个方面上更有效率。

首先,它不会测试条件,表达式的结果直接赋值给PM.IsSelected。根据LukeH的评论,强制不将PM.IsSelected设置为false,因此条件是不可避免的。但是,如果要将其设置为false,则适用此改进。 从问题提问者的评论中,他的情况似乎符合这种优化。 因此不需要条件赋值。

第二,它不会迭代整个列表,因为List.Contains(int)会在参数传递的int第一次出现时返回true并退出循环。

第三,当框架为您提供List.Contains(int)功能时,为什么要重新发明轮子呢。因此,从维护的角度来看,这也更有效率。


2
我认为你应该在.Contains调用中使用new HashSet<int>(facilityManagerId)。这样做有问题吗? - Jens
它更有效率,因为它消除了条件。其次,.Contains()会在找到第一个匹配项时返回true,在问题的代码中,它将迭代所有元素,这是完全不必要的。 - this. __curious_geek
1
@this.__curious_geek:移除条件语句意味着您的代码与问题中给出的代码具有不同的逻辑。原始代码将永远不会将“IsSelected”设置为false,而您的代码可能会这样做。 - LukeH
2
唯一的优化是它在找到元素时停止。循环中缺少终止条件是上述代码中的一个错误。List<T>.Contains 的内部实现基本上不比上面的方法快。如果你真的想让它运行得更快,你需要使用哈希表等数据结构。例如,HashTable.Contains 运行时间为 O(1)。是的,你需要创建哈希表,但这个开销可能会显著低于上述算法的总成本。 - Mel Harbour
@Mel Harbour:我明白你的观点。感谢你抽出时间来解释。 :) - Chris
显示剩余9条评论

1
patchFacilityManager
.Where(c => facilityManagerId.Contains(c.FacilityManagerId))
.ForEach(c => c.IsSelected = true);

2
这样做会不会在幕后执行与问题中的代码类似的操作? - Bart van Heukelom
嗯,这有点像多对多的比较。将A中的每个项与B中的每个项进行比较,并为每个匹配项设置A的属性。这就是“伪代码”中的说法。无论如何,我认为Contains方法比嵌套循环更有效率。 - Matteo Mosca
IEnumerable<T> 没有内置的 ForEach 方法,而你的 Where 调用就是返回了该类型。ForEachList<T> 上的实例方法,所以你需要先调用 ToList,但这并不会提高效率! - LukeH
你说得对,我忘记了因为我使用了一些自定义扩展方法,其中之一是 IEnumerable<T> 的 ForEach。我的错没有提到那个。 - Matteo Mosca

1
var ids = new HashSet<int>(facilityManagerId);
foreach (PatchFacilityManager pfm in patchFacilityManager)
{
    if (ids.Contains(pfm.FacilityManagerId))
        pfm.IsSelected = true;
}

是的,比我下面的版本整洁多了。绝对加一! - Mel Harbour
这是应该被标记为答案的解决方案。与被接受的答案进行比较,对于集合中合理数量的项目,它的性能快得离谱。请投票支持这个答案! - Mel Harbour

0
patchFacilityManager
   .Where(m => facilityManagerId.Contains(m.FacilityManagerId))
   .ToList()
   .ForEach(m => m.IsSelected = true);

或者

patchFacilityManager
   .Join(facilityManagerId, m => m.FacilityManagerId, f => f, (m,f) => m)
   .ToList()
   .ForEach(m => m.IsSelected = true);

0

使用LINQ语法的另一种变体:

var match = for PM in patchFacilityManager
            join FM in facilityManager on PM.FacilityManagerId equals FM
            select PM;
foreach(var PM in match)
{
   PM.IsSelected = true;
}

0

只是为了好玩和超越常规——当列表已排序时的一种方法:

static void Main(string[] args)
        {
            List<int> nums = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8 });
            List<int> ids = new List<int>(new int[] { 2, 4, 5 });

            for (int i = 0, j = 0; i < nums.Count && j < ids.Count; i++)
            {
                int num = nums[i];
                int id = ids[j];

                if (num == id)
                {
                    Console.WriteLine("Match = " + id);
                    j++;
                }
            }

            Console.ReadLine();
        }

我假设您对这个想法的性能优劣没有任何了解。当然,如果您不喜欢使用for循环,您可以修改它以使用主foreach来处理数字,并手动使用IDs的枚举器在主foreach内部。


0
你可以将设施经理的ID按照排序顺序存储在一个数组中,然后使用BinarySearch进行查找,而不是使用foreach

更好的解决方案。BinarySearch 是 O(log n),但 HashSet 版本仍然胜出,因为它是 O(1)。 - Mel Harbour
从技术上讲,二分查找的最坏时间复杂度是(log n);HashSet的平摊复杂度为O(1),最坏时间复杂度为(n)。话虽如此,我认同HashSet更好。 - Stephen Cleary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接