基于条件“分割”通用列表的最(性能)效率和可读方式是什么?

时间:2011-05-23 13:18:37

标签: c# linq performance

(高度简化的例子) 我有一个通用的字符串列表:

var strings = new List<string> { "abc", "owla", "paula", "lala", "hop" };

我正在寻找一种最有效的方法将此列表拆分为一个列表,其中包含符合条件的元素和不符合相同条件的元素列表。

Func<string, bool> condition = s => s.IndexOf("o") > -1;
Predicate<string> kickOut = s => s.IndexOf("o") > -1;
var stringsThatMeetCondition = strings.Where(condition);
strings.RemoveAll(kickOut);
var stringsThatDontMeetCondition = strings;

有没有办法在原始列表中只循环一次?

4 个答案:

答案 0 :(得分:2)

使用一些linq:

var matches = list.Select(s => s.IndexOf("o") > -1).ToList();
var notMatches = list.Except(matches).ToList();
list.Clear();
list.AddRange(matches);

更新:正如评论中提到的那样,小心地改变列表,因为linq方法尝试按需,他们不会迭代列表,直到你开始查看{{1 }}。但是在我的情况下,我调用了IEnumerable,这有效地使它在整个项目列表中运行。

答案 1 :(得分:1)

这样就可以了:

IEnumerable<T> FilterAndRemove(this List<T> list, Func<T, bool> pred)
{
  List<T> filtered = new List<T>();
  int i = 0;
  while(i < list.Count)
  {
     if (pred(list[i]))
     {
        filtered.Add(list[i]);
        list.RemoveAt(i);
     }
     else
     { 
        ++i;
     }
  }
  return list;
}

但我相信你已经想到了类似的东西。您能否以您所寻求的效率更新您的答案?

请注意,在原始列表中使用pred!pred的两次过滤运行仍然是O(n),并且完全没有效率。特别是考虑到你可以获得两个结果集的延迟评估的全部好处。另见Rob的回答。

此算法位于O(n ^ 2)。

相反,删除每个元素,您也可以在新列表中收集它们,并在返回之前将它们复制到输入列表中。这也可以给你O(n)。

O(n)的另一个选项是切换到链接列表。

答案 2 :(得分:1)

为什么不使用

var stringsThatMeetCondition = strings.Where(condition);
var stringsThatDontMeetCondition = strings.Where(x => !condition(x));

当然,您最终会将条件应用于列表中的每个元素两次。为了避免这种情况,你可能想要编写一个通用的分裂函数,它不会那么整洁。

答案 3 :(得分:0)

Func<string, bool> condition = ...;
var groupedStrings = strings.GroupBy(condition)
var stringsMeetingCondition = groupedStrings.FirstOrDefault(g => g.Key);
var stringsNotMeetingCondition = groupedStrings.FirstOrDefault(g => !g.Key);