Search code examples
c#for-loopforeachgeneric-list

How shall I Quickly compare two generic lists and it's contents for checking if they are identical?


//Here I have a List of Lists

List<List<T>> SelectionList = new List<List<T>>();

//My current code to compare lists

    for(int i = 0; i < SelectionList.Count; i++)
    {
        for(int j = 0; j < SelectionList.Count; j++)
        {
            if (SelectionList[i].Equals(SelectionList[j]))
            {
                SelectionList.Remove(SelectionList[j]);
            }
        }
    }

//Note: The above code supposedly works, in cases where the contents of both the lists are ordered ie., they are in same index, but in my lists they might be same but list contents are shuffled. in this case it fails to identify.

//Basically I need to remove any repetitions of same list in my list of lists;


Solution

  • If (and only if) the following is true:

    • Your individual lists do not contain any duplicates
    • The type T of your list elements implements IComparable and GetHashCode() correctly

    Then you can remove each list that matches an earlier list like so (note that you must traverse the list backwards when removing items from the end of it otherwise the loop indices could go out of range):

    for (int i = lists.Count - 1; i > 0; i--)
    {
        for (int j = i - 1; j >= 0; j--)
        {
            if (!lists[i].Except(lists[j]).Any())
            {
                lists.RemoveAt(i);
                break;
            }
        }
    }
    

    The important line here is: !lists[i].Except(lists[j]).Any().

    Let's break it down:

    lists[i].Except(lists[j]): - This produces a sequence of all the elements of lists[i] that are NOT in lists[j], regardless of order.

    Thus if all of the items in lists[j] are also in lists[j], this will produce an empty sequence; otherwise, it will produce a non-empty sequence.

    The .Any() will return true for a non-empty sequence, and false for an empty sequence.

    So lists[i].Except(lists[j]).Any() will return false if the items are the same in each list and true if they differ.

    This is the opposite of what we want for the lists.RemoveAt() so we just negate the result, giving the final code !lists[i].Except(lists[j]).Any().

    Compilable console app:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    static class Program
    {
        static void Main()
        {
            var lists = new List<List<int>>
            {
                new() {1, 2, 3, 4, 5}, // [0]
                new() {2, 3, 4, 5, 6}, // [1]
                new() {3, 4, 5, 6, 7}, // [2]
                new() {5, 4, 3, 2, 1}, // [3] Dupe of [0]
                new() {4, 5, 6, 7, 8}, // [4]
                new() {6, 5, 4, 3, 2}, // [5] Dupe of [1]
                new() {5, 6, 7, 8, 9}, // [6] 
                new() {3, 4, 5, 2, 1}, // [7] Dupe of [0]
                new() {6, 7, 8, 9, 0}  // [8]
            };
    
            for (int i = lists.Count - 1; i > 0; i--)
            {
                for (int j = i - 1; j >= 0; j--)
                {
                    if (!lists[i].Except(lists[j]).Any())
                    {
                        lists.RemoveAt(i);
                        break;
                    }
                }
            }
    
            for (int i = 0; i < lists.Count; ++i)
            {
                Console.WriteLine(string.Join(", ", lists[i]));
            }
        }
    

    Try it on DotNetFiddle: https://dotnetfiddle.net/nWnOcP