(高度简化的例子) 我有一个通用的字符串列表:
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;
有没有办法在原始列表中只循环一次?
答案 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);