问题
For example, I have two arrays:
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);
I want Items from arrayOne
which are not there in arrayTwo
. So here I need result as: Three Three
but I am getting no results as its treating "Three" as common and not checking the other two items("Three", "Three").
I dont want to end up writing a huge method to solve this. Tried couple other answer on SO but didnt worked as expected :(.
Thanks!!!
回答1:
Build a HashSet of the second, and then filter the first only allowing items if you can't remove the item from the HashSet.
var hs = new HashSet<string>(arrayTwo);
var filtered = arrayOne.Where(item => !hs.Remove(item)).ToArray();
Taking account of your extra requirements in the comments, some nifty use of ILookup
works nicely here.
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();
回答2:
The answer depends on array sizes, duplicate elements count, importance of code speed.
For small arrays, the following code would be the simplest and the best:
List<string> result = new List<string>(arrayOne);
foreach (string element in arrayTwo)
result.Remove(element);
If you want more efficiency for large arrays, you can use spender's answer.
If you want the most efficient code, you will have to code manually the following algorithm: 1. Sort both arrayOne and arrayTwo. 2. Iterate over both algorithms simultaneously (like in mergesort) and omit pairs with the same elements.
Proc: no heavy Lookup object Cons: need coding
回答3:
You can get the desired output by adding an index to each element of the arrays to make them look like
{{ "One", 0 }, { "Two", 0 }, { "Three", 0 }, { "Three", 1 }, { "Three", 2 }}
{{ "One", 0 }, { "Two", 0 }, { "Three", 0 }}
Then you can use Except
to remove duplicates
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:
One way to do it would be to include indices as well like:
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);
This will give you the required output for your input, but the issue with the above approach is, that, same string on different indices will be treated differently.
The other approach to ignore indices could be done like:
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
will contain {"X", "Three", "Three"}
回答5:
Since the order of the final output isn't required, you could group up the repeated strings in arrayOne
, and subtract, groupwise, the counted (and present) number of repeats in arrayTwo
. You can then flatten out the collections again, at the same time using Enumerable.Repeat
to replicate out the number of iterations.
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);
}
Note that this obviously won't preserve any interleaving which could occur in the original order.
e.g. For
string[] arrayOne = {"Three", "Four", "One", "Two", "Three", "Three"};
The answer would unintuitively be
Three
Three
Four
回答6:
Coming to this discussion late, and recording this here for reference. LINQ's Except method is using the default equality comparer to determine which items match in your two arrays. The default equality comparer, in this case, invokes the Equals method on the object. For strings, this method has been overloaded to compare the content of the string, not its identity (reference).
This explains why this is occurring in this particular scenario. Granted, it doesn't provide a solution, but I believe that others have already provided excellent answers. (And realistically, this is more than I could fit into a comment.)
One suggestion I might have made was to write a custom comparer, and passed it to the Except overload that accepts one. Custom comparers are not overly complicated, but given your scenario, I understand where you might not have desired to do so.
回答7:
Try this:
var result = from s in first
where !string.IsNullOrWhiteSpace(s) &&
!second.Contains(s)
select s;
Ok if that didn't work -- I read the comments a bit more carefully.
The following code:
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;
}
Produces the following results:
Three
Three
回答8:
Another way you could compare equality of arrays using LINQ is as below.
Logic used in LINQ: In this code, I am filtering the first array elements such that each element in first array is equal to corresponding element in second array and the current index of first array exists in second array; if the two arrays being compared are equal then this filtering should result in the same number of elements as there are in the first array.
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
来源:https://stackoverflow.com/questions/33812323/compare-two-arrays-using-linq