Chetak Kadam
Chetak Kadam

Reputation: 33

Identify common Lists in List and return Distinct Lists in List

I have a following List, I need to iterate through the list and see if the list has identical elements in it and return only a unique list. Could anyone please let me know what's wrong with the following code and a proper way to do it?

Also, Linq way to do it, if any?

Expected Solution would be = {{"a", "b", "c"},{"e", "b", "c" }}

class Program1
{
    static void Main(string[] args)
    {
        List<string>[] stringLists = new List<string>[3]
            {
                new List<string>(){ "a", "b", "c" },
                new List<string>(){ "e", "b", "c" },
                new List<string>(){ "a", "b", "c" }
            };

        List<List<string>> prt = new List<List<string>>();

        /* I DONT UNDERSTAND WHY THIS IS NOT WORKING !!!!!!!!!!!!!!! */
        foreach (var item in stringLists)
        {
            for (int i = 0; i < item.Count; i++)
            {
                if (item == stringLists[i] && (!prt.Contains(item)))
                {
                    prt.Add(item);
                }
            }
        }
    }
}

Upvotes: 3

Views: 79

Answers (5)

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186698

You can try good old Distinct with a custom IEqualityComparer<T>:

using System.Linq;

...

public class SequenceComparer<T> : IEqualityComparer<IEnumerable<T>> {
  public bool Equals(IEnumerable<T> x, IEnumerable<T> y) {
    return Enumerable.SequenceEqual(x, y);
  }

  //TODO: Suboptimal HashCode implementation
  public int GetHashCode(IEnumerable<T> obj) {
    return obj == null
      ? 0 
      : obj.Count(); 
  }
}

...

var stringLists = new List<string>() {
  new List<string> {"a", "b", "c"},
  new List<string> {"e", "b", "c"},
  new List<string> {"a", "b", "c"} 
};

// All you have to do is to put Distinct
var result = stringLists
  .Distinct(new SequenceComparer<string>())
  .ToList(); // If you want materialization

Test:

Console.Write(string.Join(Environment.NewLine, result
  .Select(list => string.Join(", ", list))));

Outcome:

a, b, c
e, b, c

Upvotes: 2

Phillip Ngan
Phillip Ngan

Reputation: 16106

Your misunderstanding is that you expect the statement

prt.Contains(item)

to return true when the sequence of strings in item already exists in prt. However the test internal to Contains used to determine this is a reference equality, not a item by item equality. Here is a minimal example to illustrate this:

void Main()
{
    Console.Write( (new []{new []{ "a", "b", "c" }}).Contains(new[] { "a", "b", "c" }));
    // Prints false
}

You either need to use a deep equals comparer like @Dmitry's answer which creates a digest (hash) of each list and compares the digests, or to do it explicitly, like this code:

class Program1
{
    static void Main(string[] args)
    {
        List<string>[] stringLists = new List<string>[3]
            {
                new List<string>(){ "a", "b", "c" },
                new List<string>(){ "e", "b", "c" },
                new List<string>(){ "a", "b", "c" }
            };

        List<List<string>> prt = new List<List<string>>();
        for(int i = 0; i < 3; i++)
        {
            bool isDifferentFromAllOthers = true;
            for(int j = 0; j < i; j++)
            {
                bool isSameAsThisItem = true;
                for(int item = 0; item < 3; item++)
                {
                    // !!! Here is the explicit item by item string comparison
                    if (stringLists[i][item] != stringLists[j][item])
                    {
                        isSameAsThisItem = false;
                        break;
                    }                   
                }
                if (isSameAsThisItem)
                {
                    isDifferentFromAllOthers = false;
                    break;
                }
            }
            if (isDifferentFromAllOthers)
            {
                prt.Add(stringLists[i]);
            }
        }


//        /* I DONT UNDERSTAND WHY THIS IS NOT WORKING !!!!!!!!!!!!!!! */
//        foreach (var item in stringLists)
//        {
//            for (int i = 0; i < item.Count; i++)
//            {
//                if (item == stringLists[i] && (!prt.Contains(item)))
//                {
//                    prt.Add(item);
//                }
//            }
//        }
    }
}

Upvotes: 0

Patrick Artner
Patrick Artner

Reputation: 51653

Dmitry Bychenko's answer is the way to go.

For your special case and with data that does not contain , you could get away with:

using System;
using System.Linq;
using System.Collections.Generic;

public class Program1
{
    public static void Main()
    {
        var stringLists = new List<List<string>>{
            new List<string> {"a", "b", "c"}, 
            new List<string> {"e", "b", "c"}, 
            new List<string> {"a", "b", "c"}
        };

        var prt = stringLists
            .Select(l => string.Join(",", l))      // make it a string separated by ,
            .Distinct()                            // distinct it using string.Distinct()
            .Select(l => l.Split(',').ToList());   // split it again at , and make it List

        foreach (var p in prt)
        {
            foreach (var c in p)
                Console.WriteLine(c);

            Console.WriteLine();            
        }   
    }
}

There is lots of un-needed object creation in this approach - but i would work (until your data contains a , - then is messes your lists up).

Output:

a
b
c

e
b
c

Upvotes: 0

tchelidze
tchelidze

Reputation: 8318

prt.Contains(item) expression compares List's with their reference not by their elements, so it's a bad choice for determining if two lists are duplicates.

Try following

 var stringLists = new List<string>[3]
 {
    new List<string> {"a", "b", "c"},
    new List<string> {"e", "b", "c"},
    new List<string> {"a", "b", "c"}
 };

 var prt = new List<List<string>>();

 foreach (var item in stringLists)
 {
     if(prt.All(it => it.Count != item.Count || it.Except(item).Any()))
          prt.Add(item);
 }

Upvotes: 0

Arun Kumar
Arun Kumar

Reputation: 957

You need to do union on all the list and than do a distinct.

Something like this, just iterate through the list and union it with last result:

List<string> result = new List<string>();
        foreach (var list in stringLists)
        {
            result = result.Union(list).ToList();

        }
        result = result.Distinct().ToList();

Upvotes: 0

Related Questions