Inside Man
Inside Man

Reputation: 4319

Find common items in multiple lists

I searched, but I found only answers which related to two lists. But what about when they are more than two?

List 1 = 1,2,3,4,5
List 2 = 6,7,8,9,1
List 3 = 3,6,9,2,0,1
List 4 = 1,2,9,0,5
List 5 = 1,7,8,6,5,4
List 6 = 1
List 7 =

How to get the common items? as you can see one of them is empty, so the common will be empty, but I need to skip empty lists.

Upvotes: 3

Views: 5027

Answers (4)

Han
Han

Reputation: 3072

var data = new [] {
    new List<int> {1, 2, 3, 4, 5},
    new List<int> {6, 7, 8, 9, 1},
    new List<int> {3, 6, 9, 2, 0, 1},
    new List<int> {1, 2, 9, 0, 5},
    new List<int> {1, 7, 8, 6, 5, 4},
    new List<int> {1},
    new List<int> {},
    null
};

var temp = data[0];
foreach (var arr in data.Skip(1) )
   if (arr != null && arr.Count != 0)
      temp =  arr.Intersect(temp);

Upvotes: 3

Anton&#237;n Lejsek
Anton&#237;n Lejsek

Reputation: 6103

var data = new List<List<int>> {
    new List<int> {1, 2, 3, 4, 5},
    new List<int> {6, 7, 2, 8, 9, 1},
    new List<int> {3, 6, 9, 2, 0, 1},
    new List<int> {1, 2, 9, 0, 5},
    new List<int> {1, 7, 8, 6, 2, 5, 4},
    new List<int> {1, 7, 2}
};


List<int> res = data
    .Aggregate<IEnumerable<int>>((a, b) => a.Intersect(b))
    .ToList();

The type of Aggregate is explicitly given, otherwise aggregation of two Lists would have to be List too. It can be easily adapted to run in parallel:

List<int> res = data
    .AsParallel<IEnumerable<int>>()
    .Aggregate((a, b) => a.Intersect(b))
    .ToList();

EDIT

Except... it does not run in parallel. The problem is operations on IEnumerable are deferred, so even if they are logically merged in parallel context, the actual merging occurs in the ToList(), which is single threaded. For parallel execution it would be better to leave IEnumerable and return to the Lists:

List<int> res = data
    .AsParallel()
    .Aggregate((a, b) => a.Intersect(b).ToList());

Upvotes: 9

juharr
juharr

Reputation: 32296

One way is to use a HashSet. You can put the items of the first collection in the hash, then iterate each collection after the first and create an new hash that you add items from the current collection to if it's in the hash. At the end you assign that common hash set to the overall one and break if it's every empty. At the end you just return the overall hash set.

public IEnumerable<T> CommonItems<T>(IEnumerable<IEnumerable<T>> collections)
{
    if(collections == null)
        throw new ArgumentNullException(nameof(collections));

    using(var enumerator = collections.GetEnumerator())
    {
        if(!enumerator.MoveNext())
            return Enumerable<T>.Empty();

        var overall = new HashSet<T>(enumerator.Current);
        while(enumerator.MoveNext())
        {
            var common = new HashSet<T>();
            foreach(var item in enumerator.Current)
            {
                if(hash.Contains(item))
                    common.Add(item);
            }

            overall = common;
            if(overall.Count == 0)
                break;
        }

        return overall;
    }
}

Upvotes: 2

Ofir Winegarten
Ofir Winegarten

Reputation: 9365

You can chain Intersect:

List<int> List1 = new List<int> {1, 2, 3, 4, 5};
List<int> List2 = new List<int> { 6, 7, 8, 9, 1 };
List<int> List3 = new List<int> { 3, 6, 9, 2, 0, 1 };
List<int> List4 = new List<int> { 1, 2, 9, 0, 5 };
List<int> List5 = new List<int> { 1, 7, 8, 6, 5, 4 };
List<int> List6 = new List<int> { 1 };

List<int> common = List1
  .Intersect(List2)
  .Intersect(List3)
  .Intersect(List4)
  .Intersect(List5)
  .Intersect(List6)
  .ToList();

Upvotes: 6

Related Questions