Randel Ramirez
Randel Ramirez

Reputation: 3761

How to compare two list<object> in C# and retain only the items that don't have duplicates?

Here are two lists:

var list1 = new List<UserGroupMap> 
        { 
            new UserGroupMap { UserId = "1", GroupId = "1", FormGroupFlag = "1", GroupDescription = "desc1", GroupName = "g1"}, 
            new UserGroupMap { UserId = "1", GroupId = "2", FormGroupFlag = "1", GroupDescription = "desc1", GroupName = "g1"},
            new UserGroupMap { UserId = "1", GroupId = "3", FormGroupFlag = "1", GroupDescription = "desc1", GroupName = "g1"},
            new UserGroupMap { UserId = "2", GroupId = "3", FormGroupFlag = "1", GroupDescription = "desc1", GroupName = "g1"} 
        };

        var list2 = new List<UserGroupMap> 
        { 
            new UserGroupMap { UserId = "1", GroupId = "1", FormGroupFlag = "1", GroupDescription = "desc1", GroupName = "g1"}, 
            new UserGroupMap { UserId = "1", GroupId = "2", FormGroupFlag = "1", GroupDescription = "desc1", GroupName = "g1"},
            new UserGroupMap { UserId = "1", GroupId = "3", FormGroupFlag = "1", GroupDescription = "desc1", GroupName = "g1"},
            new UserGroupMap { UserId = "2", GroupId = "3", FormGroupFlag = "1", GroupDescription = "desc1", GroupName = "g1"},
            new UserGroupMap { UserId = "4", GroupId = "3", FormGroupFlag = "1", GroupDescription = "desc1", GroupName = "g1"}, 
            new UserGroupMap { UserId = "3", GroupId = "3", FormGroupFlag = "1", GroupDescription = "desc1", GroupName = "g1"}, 
        };

now what I want to happen is to get a list that doesn't have duplicates, basically compare list1 and list2 return only the items that are duplicate.

based from the sample what it should return are the last two items from list 2 since they are not in list1.

Upvotes: 5

Views: 2446

Answers (5)

Farhad Jabiyev
Farhad Jabiyev

Reputation: 26645

Computer doesn't know how to compare your custom class instances. You have some choices and one of them is to create your own comparer which must implement IEqualityComparer<T> interface:

sealed class MyComparer : IEqualityComparer<UserGroupMap>
{
    public bool Equals(UserGroupMap x, UserGroupMap y)
    {
        if (x == null)
            return y == null;
        else if (y == null)
            return false;
        else
            return x.UserId.Equals(y.UserId) 
                 && x.GroupId.Equals(y.GroupId)
                 && x.FormGroupFlag.Equals(y.FormGroupFlag)
                 && x.GroupDescription.Equals(y.GroupDescription)
                 && x.GroupName.Equals(y.GroupName);
    }

    public int GetHashCode(UserGroupMap obj)
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 23 + (obj.UserId ?? "").GetHashCode();
            hash = hash * 23 + (obj.GroupId ?? "").GetHashCode();
            hash = hash * 23 + (obj.FormGroupFlag ?? "").GetHashCode();
            hash = hash * 23 + (obj.GroupDescription ?? "").GetHashCode();
            hash = hash * 23 + (obj.GroupName ?? "").GetHashCode();

            return hash;
        }
    }
}

And then use Except() from the System.Linq namespace to find the difference of two sequences by using the default equality comparer:

var result = list2.Except(list1, new MyComparer()).ToList();

Upvotes: 1

fdomn-m
fdomn-m

Reputation: 28611

It's not clear from the question if you want:

1: results from list2 that aren't in list1, as in the small sample set example:

what it should return are the last two items from list 2 since they are not in list1

or 2: if you want to find the non-duplicates from both lists

to get a list that doesn't have duplicates

or 3: if you want to find the duplicates from both lists

basically compare list1 and list2 return only the items that are duplicate.

3: is trivial, so probably not that:

list1.Concat(list2).Duplicate(new UserGroupMap());

Assuming you've added IEqualityComparer to your UserGroupMap (or added as a separate comparer).

1: has been answered

2: has not been answered, so here you go:

var result = (from item in list1.Concat(list2)
              group item by new { item.UserId, item.GroupId, item.FormGroupFlag, item.GroupDescription, item.GroupName }
              into groups
              where groups.Count() == 1
              select groups)
            .SelectMany(x => x);

this will then work even if you swap the content of list1 and list2.

Upvotes: 0

Tim Schmelter
Tim Schmelter

Reputation: 460208

what it should return are the last two items from list 2 since they are not in list1.

Update If you can't use LINQ you could use a HashSet<T> to search and remove duplicates. You have to override GetHashCode and Equals(or implement IEquatable<T> what i did):

public class UserGroupMap :  IEquatable<UserGroupMap>
{ 
    public string UserId {get;set;}
    public string GroupId { get; set; }
    public string FormGroupFlag { get; set; }
    public string GroupDescription { get; set; }
    public string GroupName { get; set; }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 23 + (UserId ?? "").GetHashCode();
            hash = hash * 23 + (GroupId ?? "").GetHashCode();
            hash = hash * 23 + (FormGroupFlag ?? "").GetHashCode();
            hash = hash * 23 + (GroupDescription ?? "").GetHashCode();
            hash = hash * 23 + (GroupName ?? "").GetHashCode();

            return hash;
        }
    }

    public bool Equals(UserGroupMap other)
    {
        if(other == null) return false;
        if(Object.ReferenceEquals(this, other)) return true;

        return this.UserId == other.UserId
            && this.GroupId == other.GroupId
            && this.FormGroupFlag == other.FormGroupFlag
            && this.GroupDescription == other.GroupDescription
            && this.GroupName == other.GroupName;
    }    
}

Now it's easy:

var uniqueInList2 = new HashSet<UserGroupMap>(list2);
uniqueInList2.ExceptWith(list1);

Result: your two desired objects from list2.

Note that this approach also removes duplicates from list2, i'm not sure if that's desired.


Old answer:

var onlyInTwo = list2
  .Where(x => !list1.Any(x2 => x.UserId == x2.UserId && x.FormGroupFlag == x2.FormGroupFlag && x.GroupDescription == x2.GroupDescription && x.GroupName == x2.GroupName));

You could also implement a custom IEqalityComparer<UserGroupMap> which can be used for example in Enumerable.Except. Then it's simple and efficient:

var onlyInTwo = list2.Except(list1, new UserGroupMapComparer());

Another way is let UserGroupMap override Equals and GetHashCode or to implement the IEquatable<UserGroupMap> interface.

Upvotes: 1

Codor
Codor

Reputation: 17605

Basically, the task can be solved with Linq by using

var Result = list1.Concat(list2).Except(list1.Intersect(list2));

however this probably requires UserGroupMap to implement the interface IEquatable<UserGroupMap> in a suitable way, unless UserGroupMap is a struct. If implementation of IEquatable<UserGroupMap> for some reason is impossible, the overload of Except which takes a custom comparison as an argument can be used, as well as the overload of Intersect which takes a custom comparison as an argument.

Upvotes: 4

yohannist
yohannist

Reputation: 4204

Try this

list2.Except(list1).Concat(list1.Except(list2));

Upvotes: 5

Related Questions