【问题标题】:Compare two arrays using LINQ使用 LINQ 比较两个数组
【发布时间】:2015-11-19 19:04:48
【问题描述】:

例如,我有两个数组:

string[] arrayOne = {"One", "Two", "Three", "Three", "Three"};
string[] arrayTwo = {"One", "Two", "Three"};

var result = arrayOne.Except(arrayTwo);

foreach (string s in result) Console.WriteLine(s);

我想要来自arrayOne 的项目,这些项目不在arrayTwo 中。所以在这里我需要结果为:Three Three 但我没有得到任何结果,因为它将“三”视为常见而不检查其他两项(“三”,“三”)。

我不想最终编写一个巨大的方法来解决这个问题。在 SO 上尝试了其他几个答案,但没有按预期工作:(。

谢谢!!!

【问题讨论】:

  • 只有当它们像现在这样有序并且你在某个索引之后得到了所有东西时才会起作用。否则任何有效的东西都会检查价值与价值,并会认识到三等于三。你能给出一个不需要第一个“三”但需要后面两个的用例吗?这可能会帮助您提出更好的方法建议来回答您的问题。
  • 假设数组二是{"One", "Two", "Three", "Three"};?
  • @spender :假设结果应该是Three。那只是 1 项
  • 最终输出的顺序重要吗?
  • 这取决于拥有string[] arrayTwo = {"Two", "Three", "Three", "One"}; 是否仍会从arrayOne 中过滤掉“一”... Habib 不会这样做。

标签: c# .net arrays linq compare


【解决方案1】:

构建第二个的HashSet,如果不能从HashSet中移除项目,则过滤第一个只允许的项目。

var hs = new HashSet<string>(arrayTwo);
var filtered = arrayOne.Where(item => !hs.Remove(item)).ToArray();

考虑到您在 cmets 中的额外要求,ILookup 的一些巧妙用法在这里很有效。

var lookup1 = arrayOne.ToLookup(item => item);
var lookup2 = arrayTwo.ToLookup(item => item);
var output = lookup1.SelectMany(i => i.Take(i.Count() - lookup2[i.Key].Count())).ToArray();

【讨论】:

  • 这不适用于具有重复元素的arrayTwo
  • @AndreyNasonov 我的改装版本会考虑到这一点。
【解决方案2】:

答案取决于数组大小、重复元素计数、代码速度的重要性。

对于小数组,下面的代码是最简单最好的:

List<string> result = new List<string>(arrayOne);
foreach (string element in arrayTwo)
    result.Remove(element);

如果您想提高大型数组的效率,可以使用 spender 的答案。

如果您想要最高效的代码,则必须手动编写以下算法: 1.对arrayOne和arrayTwo进行排序。 2. 同时迭代两种算法(如在归并排序中)并省略具有相同元素的对。

Proc:没有繁重的 Lookup 对象 缺点:需要编码

【讨论】:

  • 是的,.. 相当不错和棘手。感谢您详细说明。
  • +1 - 曾经命令式的可变代码实际上比我们的大多数 Linq 工作更简洁。此外,它还保留了从左到右的元素“湮灭”。
【解决方案3】:

您可以通过为数组的每个元素添加索引来获得所需的输出,使它们看起来像

{{ "One", 0 }, { "Two", 0 }, { "Three", 0 }, { "Three", 1 }, { "Three", 2 }}
{{ "One", 0 }, { "Two", 0 }, { "Three", 0 }}

然后你可以使用Except 删除重复项

var arrayOneWithIndex = arrayOne
    .GroupBy(x => x)
    .SelectMany(g => g.Select((e, i) => new { Value = e, Index = i }));

var arrayTwoWithIndex = arrayTwo
    .GroupBy(x => x)
    .SelectMany(g => g.Select((e, i) => new { Value = e, Index = i }));

var result = arrayOneWithIndex.Except(arrayTwoWithIndex).Select(x => x.Value);

【讨论】:

    【解决方案4】:

    一种方法是包含索引以及:

    var result = arrayOne.Select((r, i) => new {Value = r, Index = i})
        .Except(arrayTwo.Select((r, i) => new {Value = r, Index = i}))
        .Select(t => t.Value);
    

    这将为您的输入提供所需的输出,但上述方法的问题是,不同索引上的相同字符串将被区别对待。

    忽略索引的另一种方法可以这样做:

    string[] arrayOne = { "One", "Two", "Three", "Three", "Three", "X" };
    string[] arrayTwo = { "One", "Two", "Three" };
    
    var query1 = arrayOne.GroupBy(r => r)
        .Select(grp => new
        {
            Value = grp.Key,
            Count = grp.Count(),
        });
    
    var query2 = arrayTwo.GroupBy(r => r)
        .Select(grp => new
        {
            Value = grp.Key,
            Count = grp.Count(),
    
        });
    
    var result = query1.Select(r => r.Value).Except(query2.Select(r => r.Value)).ToList();
    var matchedButdiffferentCount = from r1 in query1
        join r2 in query2 on r1.Value equals r2.Value
        where r1.Count > r2.Count
        select Enumerable.Repeat(r1.Value, r1.Count - r2.Count);
    
    result.AddRange(matchedButdiffferentCount.SelectMany(r=> r));
    

    result 将包含{"X", "Three", "Three"}

    【讨论】:

      【解决方案5】:

      由于不需要最终输出的顺序,您可以将arrayOne 中的重复字符串分组,然后按组减去arrayTwo 中计数(和当前)的重复次数。然后,您可以再次展平集合,同时使用 Enumerable.Repeat 复制迭代次数。

      string[] arrayOne = {"One", "Two", "Three", "Three", "Three"};
      string[] arrayTwo = {"One", "Two", "Three"};
      
      var groupedTwo = arrayTwo
          .GroupBy(g => g)
          .ToDictionary(g => g.Key, g => g.Count());
      
      var groupedResult = arrayOne
          .GroupBy(a => a)
          .Select(g => new {g.Key, Count = g.Count()})
          .Select(g => new {g.Key, Residual = g.Count - 
             (groupedTwo.ContainsKey(g.Key) ? groupedTwo[g.Key] : 0)})
          .SelectMany(g => Enumerable.Repeat(g.Key, g.Residual));
      
      foreach (string s in groupedResult) 
      {
         Console.WriteLine(s);
      }
      

      请注意,这显然不会保留任何可能以原始顺序发生的交错。

      例如对于

      string[] arrayOne = {"Three", "Four", "One", "Two", "Three", "Three"};
      

      答案是不直观的

      Three
      Three
      Four
      

      【讨论】:

      • 谢谢@StuartLC :)。我已经测试了你的答案,它按预期工作,但我正在寻找一些更短的代码,所以选择了 splender 的答案:)
      • 同意 - 与 Spender 巧妙地使用 ToLookup() 相比,我的回答显得笨拙,而 Andrey 的第二个回答则非常简单。
      【解决方案6】:

      迟到了这个讨论,在这里记录下来以供参考。 LINQ 的 except 方法使用默认的相等比较器来确定两个数组中哪些项目匹配。在这种情况下,默认的相等比较器调用对象的 Equals 方法。对于字符串,此方法已被重载以比较字符串的内容,而不是其标识(引用)。

      这解释了为什么在这种特定情况下会发生这种情况。当然,它没有提供解决方案,但我相信其他人已经提供了很好的答案。 (实际上,这超出了我的评论范围。)

      我可能提出的一个建议是编写一个自定义比较器,并将其传递给接受一个的例外重载。自定义比较器并不过分复杂,但鉴于您的情况,我了解您可能不希望这样做的地方。

      【讨论】:

      • 诚然,自定义比较器并不过分复杂,即使我创建了一个,但很高兴能学到一些新东西,而不是再去if else
      • 你想举一个你想到的Except Overload的例子吗?
      【解决方案7】:

      试试这个:

      var result = from s in first
                  where !string.IsNullOrWhiteSpace(s) &&
                  !second.Contains(s)
                   select s;
      

      好吧,如果这不起作用——我会更仔细地阅读 cmets。

      以下代码:

      private static void Main(string[] args)
          {
      
              string[] first = {"One", "Two", "Three", "Three", "Three"};
              string[] second = {"One", "Two", "Four", "Three"};
      
              var result = FirstExceptSecond(first, second);
      
              foreach (string s in result)
              {
                  Console.WriteLine(s);
              }
          }
      
          private static IEnumerable<string> FirstExceptSecond(IList<string> first, IList<string> second)
          {
              List<string> firstList = new List<string>(first);
              List<string> secondList = second as List<string> ?? second.ToList();
      
              foreach (string s in secondList)
              {
                  if (firstList.Contains(s))
                  {
                      firstList.Remove(s);
                  }
              }
      
              return firstList;
          } 
      

      产生以下结果:

      Three
      Three 
      

      【讨论】:

      • 是的,您更新的代码有效。感谢您有时间试一试:)
      【解决方案8】:

      您可以使用 LINQ 比较数组是否相等的另一种方法如下。

      LINQ 中使用的逻辑: 在这段代码中,我过滤了第一个数组元素,使得第一个数组中的每个元素都等于第二个数组中的对应元素,并且第一个数组的当前索引存在于第二个数组中;如果要比较的两个数组相等,则此过滤应产生与第一个数组中相同数量的元素。

      string[] arrayOne = {"One", "Two", "Three", "Three", "Three"};
      string[] arrayTwo = {"One", "Two", "Three"};
      
      bool result =(arrayOne.Where((string n, int i) => i <= (arrayTwo.Length-1) &&
                                                 n == arrayTwo[i]).Count() == arrayOne.Length);
      
       //if result == true then arrays are equal else they are not
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-05-23
        • 2014-12-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多