1

Code

public static void Main()
{
    List<int> list1 = new List<int> {1, 2, 3, 4, 5, 6 };
    List<int> list2 = new List<int> {1, 2, 3 };
    List<int> list3 = new List<int> {1, 2 };
    var lists = new IEnumerable<int>[] { list1, list2, list3 };
    var commons = GetCommonItems(lists);
    Console.WriteLine("Common integers:");
    foreach (var c in commons)
        Console.WriteLine(c);
}

static IEnumerable<T> GetCommonItems<T>(IEnumerable<T>[] lists)
{
    HashSet<T> hs = new HashSet<T>(lists.First());
    for (int i = 1; i < lists.Length; i++)
        hs.IntersectWith(lists[i]);
    return hs;
}

As for the sample, I showed "list1" "list2" "list3", but I may have more than 50 lists that are generating each list using for each loop. How can I add programmatically each "list" to IEnumerable lists for comparing data of each list?

I tried many ways like conversion to list, Add, Append, Concat but nothing worked.

Is there any other best way to compare the N number of lists?

The output of Code: 1 2

PavanKumar GVVS
  • 441
  • 6
  • 20
  • 4
    First off, this code won't compile as you cannot instantiate an interface. Secondly, you cannot add to an `IEnumerable`, you can only add to an object that supports it, so `List` or anything that implements `ICollection`. – DavidG Nov 07 '19 at 09:29
  • or create a new list that is a result of any other list-operation, e.g. `newList = list1.Intersect(list2)`. – HimBromBeere Nov 07 '19 at 09:31
  • @HimBromBeere Minor point, but `Intersect` won't create a new list, it will give you a new `IEnumerable`. – DavidG Nov 07 '19 at 09:39
  • 1
    @DavidG the code is not attempting to instantiate an interface, but _an array of the interface_. – yaakov Nov 07 '19 at 09:51
  • @yaakov Ah yes, so it is, but the rest of my point stands. – DavidG Nov 07 '19 at 09:52
  • You could instead change the `GetCommonItems` method signature to `GetCommonItems(params IEnumerable[] lists)` which gives you the ability to sent as many lists as you want separately. – Zohar Peled Nov 07 '19 at 10:11
  • Thank you for taking the time to share your problem. What you asking for is unclear. What is your goal and your difficulty? What have you done so far? Please try to better explain your issue, your development environment and the data structures, as well as to share more code (no screenshot), images or sketches of the screen, and user stories or scenario diagrams. To help you improve your requests, please read the *[How do I ask a good question](https://stackoverflow.com/help/how-to-ask)* and **Questions I avoid asking** at the top right. – Olivier Rogier Nov 07 '19 at 10:56
  • Perhaps you can use `var lists = new List>()` and `lists.Add(somelist)` somewhere. – Olivier Rogier Nov 07 '19 at 10:57

3 Answers3

1

You can create a list of lists and add lists to that list dynamically. Something like this:

var lists = new List<List<int>>();
lists.Add(new List<int> {1, 2, 3, 4, 5, 6 });
lists.Add(new List<int> {1, 2, 3 });
lists.Add(new List<int> {1, 2 });

foreach (var list in listSources)
    lists.Add(list);

var commons = GetCommonItems(lists);

To find intersections you can use this solution for example: Intersection of multiple lists with IEnumerable.Intersect() (actually looks like that's what you are using already).

Also make sure to change the signature of the GetCommonItems method:

static IEnumerable<T> GetCommonItems<T>(List<List<T>> lists)
Andrii Litvinov
  • 9,864
  • 2
  • 45
  • 50
  • why not `IEnumerable>` in the signature? – Mark Cilia Vincenti Nov 07 '19 at 12:37
  • There is no strong reason for it, just a good practice. It will allocate less when iterating in foreach, for example, as the iterator struct won't be boxed to the heap. Also this is private method, so there is no need in the interface. – Andrii Litvinov Nov 07 '19 at 12:43
0

What you could do is allow the GetCommonItems method to accept a variable amount of parameters using the params keyword. This way, you avoid needing to create a new collection of lists.

It goes without saying, however, that if the amount of lists in your source is variable as well, this could be trickier to use.

I've also amended the GetCommonItems method to work like the code from https://stackoverflow.com/a/1676684/9945524

public static void Main()
{
    List<int> list1 = new List<int> { 1, 2, 3, 4, 5, 6 };
    List<int> list2 = new List<int> { 1, 2, 3 };
    List<int> list3 = new List<int> { 1, 2 };

    var commons = GetCommonItems(list1, list2, list3); // pass the lists here

    Console.WriteLine("Common integers:");
    foreach (var c in commons)
        Console.WriteLine(c);
}

static IEnumerable<T> GetCommonItems<T>(params List<T>[] lists)
{
    return lists.Skip(1).Aggregate(
        new HashSet<T>(lists.First()),
        (hs, lst) =>
        {
            hs.IntersectWith(lst);
            return hs;
        }
    );
}

Alternate solution using your existing Main method.

EDIT: changed the type of lists to List<List<int>> as per comment in this answer.

public static void Main()
{
    List<int> list1 = new List<int> { 1, 2, 3, 4, 5, 6 };
    List<int> list2 = new List<int> { 1, 2, 3 };
    List<int> list3 = new List<int> { 1, 2 };
    var lists = new List<List<int>> { list1, list2, list3 };
    var commons = GetCommonItems(lists);
    Console.WriteLine("Common integers:");
    foreach (var c in commons)
        Console.WriteLine(c);
}

static IEnumerable<T> GetCommonItems<T>(List<List<T>> lists)
{
    return lists.Skip(1).Aggregate(
        new HashSet<T>(lists.First()),
        (hs, lst) =>
        {
            hs.IntersectWith(lst);
            return hs;
        }
    );
}
  • Your alternative solution is dangerous. You should avoid enumerating multiple times an `IEnumerable` of unknown origin. It could cause multiple hits to a database, or to the filesystem, or to a web resource. I am refering to `lists.First()` and `lists.Skip(1).Aggregate` pair. – Theodor Zoulias Nov 07 '19 at 15:44
  • @TheodorZoulias: done. amended my first solution as well. – Mark Cilia Vincenti Nov 07 '19 at 15:59
  • OK, but now your method became very restrictive regarding the types it supports. So it is not as useful as before. The challenge is to keep accepting an `IEnumerable` of `IEnumerable`s, but enumerate each of them only once. – Theodor Zoulias Nov 07 '19 at 16:04
  • @TheodorZoulias: then it's a `.ToList()` away. Something's got to give, otherwise the solution would not be very optimised. – Mark Cilia Vincenti Nov 07 '19 at 16:07
  • `ToList` has other problems. If I feed your method with `Enumerable.Range(1, 1_000_000_000).Select(n => new List { 1, 2, 3, 4, 5 })` and you call to `ToList` internally, I'll probably get an `OutOfMemoryException`. – Theodor Zoulias Nov 07 '19 at 16:12
  • @TheodorZoulias it makes little sense to create a `Range` and then compare the lists. It would be a very weird way of solving the problem. So while your comment is true, it's highly unrealistic that it will ever be used in that way, and if it is it would help the coder rework what would probably be spaghetti code. – Mark Cilia Vincenti Nov 07 '19 at 16:18
  • Actually working with deferred enumerables is the only practical way of solving problems with huge data space. Dealing with these problems was the reason that iterators and LINQ were invented in the first place. – Theodor Zoulias Nov 07 '19 at 16:22
  • @TheodorZoulias my point still remains. You should not be using this code if you're manually building lists with `Enumerable.Range`. There would be more trivial solutions for that. – Mark Cilia Vincenti Nov 07 '19 at 16:52
  • My point is that your code can be improved to be `IEnumerable`-friendly with very little effort. It's up to you if you want to make this effort or not. – Theodor Zoulias Nov 07 '19 at 16:58
-2
  1. IEnumerable is immutable so you always should return an implementation of IEnumerable depending on your needs.
  2. If I understand correctly you want to get common items of N lists. I would use LINQ for this.

My proposition: 1. make one list that contains all of the items. =>

var allElements = new List<int>();
  var lists = new List<List<int>>();
foreach (list in lists)
    allElements.AddRange(list);
  1. Take items that are repetitive

allElements.GroupBy(x => x).Where(x => x.Count() > 1).Select(x => x).ToList();

Avid Programmer
  • 1,411
  • 13
  • 23
Ultern
  • 3
  • 3
  • Instead of comparing for greater that 1 `.Where(x => x.Count() > 1)` I think you should compare to the lists count, something like `.Where(x => x.Count() == allElements.Count)` to select only numbers that appear in every list. – Andrii Litvinov Nov 07 '19 at 10:05
  • allElements is list that contains all of the items from each list so this comparison is wrong. When count is > 1 it means that number occured more than one times. – Ultern Nov 07 '19 at 10:07
  • 1
    Who told you an `IEnumerable` is immutable? Most collections are not immutable at all and most if not all of them implements the `IEnumerable`interface. The fact that the `IEnumerable` interface doesn't expose ways to change it's content doesn't make it immutable. In fact, an interface can't be neither mutable nor immutable, since it's only a contract, not an implementation. – Zohar Peled Nov 07 '19 at 10:07
  • Fact that you cannot change elements in IEnumerable interface mean that for practical point of view its immutable. – Ultern Nov 07 '19 at 10:11
  • It wasn't very clear from initial formatting. I mean `lists.Count`, of course. We are interested in number to appear in every list to return it. – Andrii Litvinov Nov 07 '19 at 10:12
  • @Ultern What if you have an `IEnumerable someCollection` where the `T` itself is mutable? Would you still say that `someCollection` is immutable? – Zohar Peled Nov 07 '19 at 10:15
  • You are asking if I can change state of an object in IEnumerable. Yes I can do it. But I cannot add next item to my IEnumerable, so that mean IEnumerable itself is immutable. Not content of it. – Ultern Nov 07 '19 at 10:16