使用LINQ比较两个数组

3
例如,我有两个数组:
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,但现在并没有得到任何结果,因为它将"Three"视为共同点而没有检查其他两个项目("Three", "Three")。
我不想写一个庞大的方法来解决这个问题。尝试了一些其他在SO上的答案,但都没有按预期工作 :(。
谢谢!

只有在它们像现在这样按顺序排列并且您获取了某个索引之后的所有内容时,它才能正常工作。否则,任何有效的方法都会检查值与值,并且会认识到 Three 等于 Three。您能否提供一个用例,不需要第一个“Three”,但需要接下来的两个?这可能会帮助回答您的问题,并提出更好的方法建议。 - Stephen Brickner
假设数组two是{"One", "Two", "Three", "Three"} - spender
不需要... 没必要。 - Sandeep Kushwah
经过充分测试,我发现@spender和Habib的回答是解决我的问题的方法。该选择哪个作为答案?他们的答案有什么优缺点吗? - Sandeep Kushwah
2
那要看有没有 string[] arrayTwo = {"Two", "Three", "Three", "One"};,如果有,是否会过滤掉 arrayOne 中的 "One"。但 Habib 的方法并未这样做。 - spender
显示剩余4条评论
8个回答

6

构建第二个HashSet,然后过滤第一个,只允许项目如果你不能从HashSet中删除该项。


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

考虑到您在评论中提出的额外要求,巧妙地使用 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将无法工作。 - Andrey Nasonov
@AndreyNasonov 我的修改版将考虑到这一点。 - spender

4
答案取决于数组大小、重复元素数量和代码速度的重要性。
对于小型数组,以下代码将是最简单和最好的:
List<string> result = new List<string>(arrayOne);
foreach (string element in arrayTwo)
    result.Remove(element);

如果您想要更高效地处理大型数组,可以使用spender的方法。
如果您想要最高效的代码,您将需要手动编写以下算法: 1. 对arrayOne和arrayTwo进行排序。 2. 同时迭代两个数组(类似于归并排序),并省略具有相同元素的对。
优点:无需重量级的查找对象 缺点:需要编码

是的,非常好而且棘手。感谢您清晰地阐述它。 - Sandeep Kushwah
+1 - 对于一次性的、可变的代码来说,它实际上比我们大多数的 Linq 代码更简洁。此外,它还保留了从左到右的元素“湮灭”。 - StuartLC

2

一种实现的方法是同时包含索引,例如:

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"}


2

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

{{ "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);

1

您可以使用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

1

由于最终输出的顺序不是必需的,因此您可以将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的答案:) - Sandeep Kushwah
同意 - 我的回答与Spender巧妙地使用ToLookup()相比显得笨拙,而Andrey的第二个答案则非常简洁优雅。 - StuartLC

1

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

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

我可能会建议编写自定义比较器,并将其传递给接受一个比较器的Except重载。自定义比较器并不是过于复杂,但考虑到您的情况,我理解您可能不想这样做。


确实,自定义比较器并不是太复杂,我甚至创建了一个,但很兴奋能学到一些新东西,而不是再去写“if else”语句。 - Sandeep Kushwah
你能否举个你想到的“异常重载”例子? - Sandeep Kushwah

0

试试这个:

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

好的,如果那个不起作用——我仔细阅读了一下评论。

以下代码:

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 

是的,你更新的代码可以工作了。感谢你抽出时间来尝试并提供帮助 :) - Sandeep Kushwah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接