3

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!!!

9
  • It would only work if they were in order like they are now and you got everything after a certain index. Otherwise anything efficient would check value vs value and would recognize that Three is equal to Three. Can you give a use case for not needing the first "Three" but needing the next two? That would probably help your question get answered with a suggestion of a better approach. Commented Nov 19, 2015 at 19:14
  • Supposing array two was {"One", "Two", "Three", "Three"};? Commented Nov 19, 2015 at 19:19
  • @spender : after your supposing result should be Three. That is only 1 item Commented Nov 19, 2015 at 19:20
  • Is the order of the final output important? Commented Nov 19, 2015 at 19:20
  • 2
    That depends on whether having string[] arrayTwo = {"Two", "Three", "Three", "One"}; would still filter "One" out of arrayOne... Habib's doesn't do that. Commented Nov 19, 2015 at 19:36

8 Answers 8

6

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();
Sign up to request clarification or add additional context in comments.

2 Comments

This will not work for arrayTwo with duplicate elements
@AndreyNasonov My modded version will take this into account.
4

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

2 Comments

Yeah,.. Quite Nice and Tricky. Thanks for elaborating it clearly.
+1 - For once imperative, mutative code is actually more concise than most of our Linq efforts. Plus it also retains a left to right 'annihilation' of the elements.
2

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

Comments

2

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

Comments

1

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

2 Comments

Thanks @StuartLC :). I have tested ur answer and it was working as expected but I was looking for some shorter code and so went with splender's answer:)
Agreed - my answer is clumsy compared to Spender's clever use of ToLookup() and Andrey's second answer is elegantly simple.
1

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.

2 Comments

True that custom comparer's are not over complicated, even I created one, but was excited to learn some new instead of again going for if else.
Would u like to give an example of Except Overload that u were thinking of?
1

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

Comments

0

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 

1 Comment

Yeah ur updated code works.. Thanks for givnig ur time to give a try :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.