C# - 将2个列表与自定义元素进行比较

时间:2018-05-25 08:16:55

标签: c# list for-loop iterator

我有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);
      }
   }
}

4 个答案:

答案 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字符串不是用于有效解决此问题的合适数据结构。

您要找的是TrieDawg,用于对原始词典列表中的每个单词进行排序1。

目标是列表2中的每个字母,你只需要0-26检查。

使用这个数据结构而不是读一个大的单词列表,直到你找到一个,你会在纸质词典中寻找单词。这应该更快。从文本中的语言中查找所有单词的应用程序使用此原则。

答案 2 :(得分:1)

Contains将使用'天真的方法'来进行字符串搜索。您可以通过查看string search algorithms来改善这一点。

执行此操作的一种方法是为所有搜索字词创建广义Suffix tree。然后遍历list2中的所有项目以查看它们是否匹配。

尽管如此,这可能有点矫枉过正。您可以先尝试fubo提出的一些简单优化,看看它是否足够快。

答案 3 :(得分:1)

由于您似乎希望匹配整个字词,因此您可以使用HashSet进行更有效的搜索,并防止多次迭代list1list2

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)