我有2个清单。一个包含搜索元素,一个包含数据。 我需要为list2中的每个元素循环,其中包含list1中的任何字符串(“cat”或“dog”)。例如:
List<string> list1 = new List<string>();
list1.Add("Cat");
list1.Add("Dog");
list1.Add... ~1000 items;
List<string> list2 = new List<string>();
list2.Add("Gray Cat");
list2.Add("Black Cat");
list2.Add("Green Duck");
list2.Add("White Horse");
list2.Add("Yellow Dog Tasmania");
list2.Add("White Horse");
list2.Add... ~million items;
我的期望是listResult: {"Gray Cat", "Black Cat", "Yellow Dog Tasmania"}
(因为它在list1中包含“cat”和“dog”)。而不是嵌套循环,你有任何想法让序列运行得更快吗?
我目前的解决方案如下。但是......看起来太慢了:
foreach (string str1 in list1)
{
foreach (string str2 in list2)
{
if str2.Contains(str1)
{
listResult.Add(str2);
}
}
}
答案 0 :(得分:6)
并行化的一个很好的用例!
没有并行化的Linq方法(内部等于你的方法除了内部循环在找到一个匹配时中断的事实 - 你的方法也搜索其他匹配)
List<string> listResult = list2.Where(x => list1.Any(x.Contains)).ToList();
将循环与AsParallel()
并行化 - 如果你有一个多核系统,将会有巨大的性能提升。
List<string> listResult = list2.AsParallel().Where(x => list1.Any(x.Contains)).ToList();
运行时比较: (4核心系统,list1 1000项,list2 1.000.000项目)
Without AsParallel(): 91 seconds
With AsParallel(): 23 seconds
使用Parallel.ForEach
和线程安全结果列表
System.Collections.Concurrent.ConcurrentBag<string> listResult = new System.Collections.Concurrent.ConcurrentBag<string>();
System.Threading.Tasks.Parallel.ForEach<string>(list2, str2 =>
{
foreach (string str1 in list1)
{
if (str2.Contains(str1))
{
listResult.Add(str2);
//break the loop if one match was found to avoid duplicates and improve performance
break;
}
}
});
旁注:您必须先匹配list2,然后再匹配break;
,否则您需要两次添加项目:https://dotnetfiddle.net/VxoRUW
答案 1 :(得分:1)
List字符串不是用于有效解决此问题的合适数据结构。
您要找的是Trie或Dawg,用于对原始词典列表中的每个单词进行排序1。
目标是列表2中的每个字母,你只需要0-26检查。
使用这个数据结构而不是读一个大的单词列表,直到你找到一个,你会在纸质词典中寻找单词。这应该更快。从文本中的语言中查找所有单词的应用程序使用此原则。
答案 2 :(得分:1)
Contains将使用'天真的方法'来进行字符串搜索。您可以通过查看string search algorithms来改善这一点。
执行此操作的一种方法是为所有搜索字词创建广义Suffix tree。然后遍历list2中的所有项目以查看它们是否匹配。
尽管如此,这可能有点矫枉过正。您可以先尝试fubo提出的一些简单优化,看看它是否足够快。
答案 3 :(得分:1)
由于您似乎希望匹配整个字词,因此您可以使用HashSet
进行更有效的搜索,并防止多次迭代list1
和list2
。
HashSet<string> species =
new HashSet<string>(list1);
List<string> result = new List<string>();
foreach (string animal in list2)
{
if (animal.Split(' ').Any(species.Contains))
result.Add(animal);
}
如果我在4核笔记本电脑上运行此操作(list1
包含1000个项目,list2
包含100,000个项目):
The algorithm in the question: 37 seconds
The algorithm using AsParallel: 7 seconds
This algorithm: 0.17 seconds
list2
中有100万个项目,此算法大约需要一秒钟。
现在虽然这种方法有效,但可能会产生错误的结果。如果list1
包含狮子,则list2
中的海狮将添加到结果中,即使list1
中没有。 (如果您在StringComparer
中使用不区分大小写的HashSet
,如下所示。)
要解决该问题,您需要一些方法将list2
中的字符串解析为更复杂的对象Animal
。如果你可以控制你的输入,这可能是一项微不足道的任务,但总的来说很难。如果您有某种方法,可以使用如下解决方案:
public class Animal
{
public string Color { get; set; }
public string Species { get; set; }
public string Breed { get; set; }
}
然后在HashSet
中搜索物种。
HashSet<string> species = new HashSet<string>
{
"Cat",
"Dog",
// etc.
};
List<Animal> animals = new List<Animal>
{
new Animal {Color = "Gray", Species = "Cat"},
new Animal {Color = "Green", Species = "Duck"},
new Animal {Color = "White", Species = "Horse"},
new Animal {Color = "Yellow", Species = "Dog", Breed = "Tasmania"}
// etc.
};
var result = animals.Where(a => species.Contains(a.Species));
请注意HashSet
中的字符串搜索区分大小写,如果您不希望可以提供StringComparer
作为构造函数参数:
new HashSet<string>(StringComparer.CurrentCultureIgnoreCase)