2

I saw a similar question here with a very good solutions: Simplest way to form a union of two lists

But the problem here is, it works when there is only one parameter in each list (int value). I had this rquirement to combine 5 different lists containing objects of the same class without data redundancy and the final list should be sorted out in ascending order of int value.

Example:

Class Company   //data Class
{
int companyNo;
string Name;
}

Class CompanyList : List<Company>
{
  .................
  public CompanyList GetList(int userID)
  {
    .....
  }
}

Class company has a pulic method returning list of companies corresponding to a search criteria, let us userID.

CompanyList list1 = CompanyList .GetList(userID1);
CompanyList list2 = CompanyList .GetList(userID2);
CompanyList list3 = CompanyList .GetList(userID3);
CompanyList list4 = CompanyList .GetList(userID4);
CompanyList list5 = CompanyList .GetList(userID5);

The solution I implemented is (worked well):

CompanyList _finalList = list1;
*foreach (CompanyList _list in {_list2 ,_list3 ,_list4 ,_list5}) //loop thorugh all other list
{
   foreach (Company item in _list)
   {
      for (int i = 0; i <= _finalList.Count - 1; i++) 
      {
         if (_finalList.Item(i).CompanyNo== item.CompanyNo)
         //***EXIT TAKE NEXT LIST - GO TO *
      }
      if (i == _finalList.Count - 1)  //else check end of first list
      {
        //company no. not yet encountered(new)
        int pos = 0;
        foreach (Company companyInfo in _finalList) //search for position for new company no.
        {
          if (companyInfo.CompanyNo> item.CompanyNo) 
          {
             break;
          } 
          else 
          {
             pos = pos + 1; //increment position
           }
       }
      _finalList.Insert(pos, item); 'Add new item
    }
}

}

**the code is converted from VB.Net to C#. Here I could not find the quivalent code piece for this line so replaced it with the concept.

I am not an expert C# programmer and just wondering if there is any better or simpler way to do this?

Data example:

Input:
list1[0] = {0,"TCS"};
list1[1] = {1,"Infosys"};
list2[0] = {8,"IBM"};
list3[1] = {1,"Infosys"};
list4[0] = {0,"TCS"};
list5[0] = {9,"Accenture"};
list5[1] = {6,"HCL"};

Output:
finalList[0] = {0,"TCS"};
finalList[1] = {1,"Infosys"};
finalList[2] = {6,"HCL"};
finalList[3] = {8,"IBM"};
finalList[4] = {9,"Accenture"};

Regards Sj

Community
  • 1
  • 1
IFlyHigh
  • 546
  • 2
  • 9
  • 20

4 Answers4

2

You can use either GroupBy or Union to remove duplicates... Union makes for a little cleaner linq (I think) but either can work... the downside is that you also need a custom IEqualityComparer in this case since equals on your company objects will return false (since they are different instances)... An alternative is to have your Company class implement IEqualityComparer and just copy the code I have implementing that interface into your Company class.

// Union gives you a unique list if it knows how to compare the objects properly
var companyEqualityComparer = new CompanyEqualityComparer();
foreach (var companyList in new List<List<Company>>(){list2, list3, list4, list5})
{
    combinedList = combinedList.Union(companyList, companyEqualityComparer);
}
// Order your output list
var finalList = combinedList.OrderBy(c => c.companyNo).ToList();

Define your CompanyEqualityComparer...

// CompanyEqualityComparer which is needed since your companies are different instances
public class CompanyEqualityComparer : IEqualityComparer<Company>
{
    public bool Equals(Company x, Company y)
    {
        return x.companyNo.Equals(y.companyNo);
    }
    public int GetHashCode(Company obj)
    {
        return obj.companyNo.GetHashCode();
    }
}
Kevin
  • 4,586
  • 23
  • 35
  • I've included an generic `EqualityComparerImproved` class in my answer that has a `Create` method that takes appropriate delegates to return an `IEqualityComparer`. – Jodrell Mar 27 '14 at 16:02
2

Similar to Habib's solution, but a bit more concise and complete.

int[] userIDs = new[] { userID1, userID2, userID3, userID4, userID5 };

IEnumerable<Company> distinctCompanies =
    from companyList in userIDs.Select(CompanyList.GetList)
    from company in companyList
    group company by company.companyNo into companiesWithSameNo
    select companiesWithSameNo.First();

CompanyList finalList = new CompanyList();
finalList.AddRange(distinctCompanies);

You might have a constructor in CompanyList that directly accepts an IEnumerable<Company>, too, so you could directly pass distinctCompanies there instead.

Medo42
  • 3,821
  • 1
  • 21
  • 37
2

Okay, so you have a number of sequences of something, in your case "something" would be Company, which doesn't overide object.Equals or object.HashCode.

So, a new extension like this, might prove useful

public static IEnumerable<T> Union(
    this IEnumerable<T> source,
    IEqualityComparer<T> comparer,
    params IEnumerable<T>[] others)
{
    if (comparer == null)
    {
        comparer = EqualityComparer<T>.Default;
    }

    var result = source.Distinct(comparer); 
    foreach(var o in source)
    {
        if (o == null)
        {
            continue;
        }

        result = result.Union(o, comparer);
    }

    return result;
}

To make this, and other functions that take an IEqualityComparer simple to use, you could add this class to your code,

public class EqualityComparerImproved<T> : EqaulityComparer<T>
{
    private readonly Func<T, T> equalityComparison;

    private readonly Func<T, int> hashGenerator;

    private EqualityComparerImproved(
             Func<T, T> equalityComparison,
             Func<T, int> hashGenerator)
    {
        this.equalityComparison = equalityComparison;
        this.hashGenerator = hashGenerator;
    }

    public static EqualityComparerImproved<T> Create
             Func<T, T> equalityComparison,
             Func<T, int> hashGenerator)
    {
         if (equalityComparison == null)
         {
             throw new ArgumentNullException("equalityComparison");
         }

         if (hashGenerator == null)
         {
             throw new ArgumentNullException("hashGenerator");
         }

         return new EqualityComparerImproved<T>(
             equalityComparison,
             hashGenerator);
    }

    public override bool Equals(T x, T y)
    {
        return this.equalityComparison(x, y);
    }

    public override int GetHashCode(T obj)
    {
        return this.hashGenerator(obj);
    }
}

Once these two, admittedly lengthy, bits of code were in place you could do

var output = list1.Union(
    EqualityComparerImproved<Company>.Create(
        (x, y) => x.companyNo == y.companyNo && x.Name == y.Name,
        (obj) =>
            {
                unchecked // Overflow is fine, just wrap
                {
                    int hash = 17;
                    hash = hash * 23 + obj.companyNo;
                    hash = hash * 23 + obj.Name.GetHashCode();
                    return hash;
                }
            },
    list2,
    list3,
    list4,
    list5);

or if companyNo is a unique key,

var output = list1.Union(
    EqualityComparerImproved<Company>.Create(
        (x, y) => x.companyNo == y.companyNo,
        (obj) => obj.companyNo),
    list2,
    list3,
    list4,
    list5);

would suffice.

Jodrell
  • 34,946
  • 5
  • 87
  • 124
  • +1 Nice! I really like the generic IEqualityComparer! It does make for a bit of a long answer but very complete. :) – Kevin Mar 27 '14 at 16:21
  • Thanks Jodrell.. after a bit of extra efforts it worked (as in reality data class is much more complex). +1!! – IFlyHigh Mar 28 '14 at 07:51
1

I think you need something like:

List<Company> inputList = //Get your input List
List<Company> outputList = inputList.GroupBy(r => r.companyNo)
                                    .Select(grp => new Company
                                        {
                                            companyNo = grp.Key,
                                            Name = grp.First().Name,
                                        })
                                    .OrderBy(r=> r.companyNo)
                                    .ToList();
Habib
  • 219,104
  • 29
  • 407
  • 436