tags:

views:

162

answers:

5

To make sure that two lists are the same, in nunit, we can use CollectionAssert.AreEquivalent to check that these two lists contain the same elements ( orders not important).

But how to check whether two List<List<T>> are equivalent? The idea is that if one List<T> has the same elements as the other List<T> ( again, order not important) then they are equal.

A: 

I don't think you get around checking for each element individually. I'll typically first check for equal length, then loop for say i over the lists' length and check that list1[i]==list2[i]

update

Sorry, missed the (central) part about elements not having to be in order. In that case, create a HashSet with the elements of the second list and check for each element in list1 if it's in the hashset. This reduces the running time for the lookup from linear to logarithmic.

update 2 As noted by Donald Ray, you have to check both ways if you have possible multiple occurrences of single objects in any of the lists.

Nicolas78
I think you are missing this part: **The idea is that if one `List<T>` has the same elements as the other `List<T>` ( again, order not important) then they are equal.** Note that two lists are the same if all the elements are the same, regardless of order.
Ngu Soon Hui
@Ngu Soon Hui, true order isn't important to you, but unless you need to keep them in their starting order, and if there's a chance you'll need to compare them more than once, then keeping them sorted will enable a faster comparison. It **may** not be appropriate in your case, but if it is appropriate, the gain can be considerable.
Jon Hanna
@Jon, the thing is that I would have a difficulty in defining the elements order. In other words I don't know how to implement `IComparer<T>` for that particular `T`.
Ngu Soon Hui
yea sorry missed the most important part somehow. pls chk update
Nicolas78
Concerning your update, HashSet has constant lookup complexity, not logarithmic.
Matajon
@Matajon, 1st thanks for mentioning HashSet, HashTable was clearly superfluous. 2nd: Um... care to elaborate about constant lookup complexity? 3rd: Only makes my argument stronger in any case, right?
Nicolas78
What if there is an element in the second list not in the first? You also have to create a hashset of the first list and check if each element of list 2 is in it.
DonaldRay
@DonaldRay, no: if the lists are of equal length and each element from list 1 is in list 2 there no "space" in list 2 for superfluous elements. Ie one of the elements in list 1 must be missing, so you'd find out about that, too.
Nicolas78
@Nicolas78: <1,1,2> <2,1,3>
DonaldRay
@DonaldRay oh. you're right for multiple occurences of the same object. which wasn't excluded in the original question. so yea, you're right.
Nicolas78
+4  A: 

You do have to loop through them to be sure that they are equivalent, but with some important shortcuts:

  1. If they are actually the same instance (and in real code this often comes up), then ReferenceEquals(x, y) will return true. Otherwise it won't. If ReferenceEquals returns true, then they are equivalent.

  2. If one is null and the other isn't, then obviously they aren't equal (if they are both null you'll have caught that above with ReferenceEquals). You'll need to test for null anyway for safety, so you've another short-cut in many cases.

  3. If they are of different sizes then (for most definitions of equivalence, there are exceptions) they are not equal. Return false immediately.

  4. The moment you've found a mismatch, you can return false without continuing to check.

  5. It will be faster to compare them if they are already sorted. If you can keep them sorted, or failing that keep track of whether they are sorted or not and then sort only when needed, you can massively speed things up. (Note though that many sorting algorithms have their worse-case behaviour when needlessly sorting a list that is already sorted).

Jon Hanna
That's tantamount to doing a brute force compare for all the elements, isn't it?
Ngu Soon Hui
@Ngu: Yes, and there is no other way.
Adam Robinson
Yes, but the short-cuts are important (ReferenceEquals especially)
Jon Hanna
A: 

I think you will need to write your own code to do this.

Look at how CollectionAssert.AreEquivalent works with Reflector then write your own helper class "MyAsserts".

You will have to be clever if you wish to run faster than O(n^2*m^2) where n is the number of lists in the outmost list and m is the number of items in the inner lists.

The simple solution is to loop over all “inner lists” in the list1 and use the code from CollectionAssert.AreEquivalent to check if list2 contains such a list. Then swap list1 and list2 and repeat. However you can do a lot better.

(You then need to work out how to produce a helpful message when the lists are not equivalent.)

Ian Ringrose
+2  A: 

Here's an attempt, not tested. If each inner list contains m elements, and the outer list-list contains n lists, I believe the complexity is O (n^2 x m), but I might be wrong. Assumptions:

  1. T does not implement IComparable or any such interface that allows sorting.
  2. Ordering is irrelevant to equality for both the List<List<T>>s and the composing List<T> objects.

--

public static bool ListListsAreEqual<T>(List<List<T>> listlist1, List<List<T>> listlist2)
{
    if (listlist1.Count != listlist2.Count)
        return false;

    var listList2Clone = listlist2.ToList();

    foreach (var list1 in listlist1)
    {
        var indexOfMatchInList2 = listList2Clone
                   .FindIndex(list2 => ListsArePermutations(list1, list2));

        if (indexOfMatchInList2 == -1)
            return false;

        listList2Clone.RemoveAt(indexOfMatchInList2);
    }

    return true;
}

private static bool ListsArePermutations<T>(List<T> list1, List<T> list2)
{
    return list1.Count == list2.Count && new HashSet<T>(list1).SetEquals(list2);
}
Ani
Why not sort the lists and do a straight compare?
Daniel Earwicker
@ Daniel: How do you know they implement IComparable? IEquatable is possibly all we can assume.
Ani
The type `T` doesn't have to support `IComparable`. `List<T>.Sort` can take a delegate that defines the comparison.
Daniel Earwicker
@Daniel: And how do we write a Comparison<T> delegate that works for all T?
Ani
Ah, good point! :)
Daniel Earwicker
A: 

I'd use SelectMany() and flatten the list. Now you can either compare element against element, using Assert.Equals() or use a normal collection assert for lists. A query expression with two from clauses will do the same thing:

void AssertEquals<T>(List<List<T>> expected, List<List<T>> actual)
{
  var flatExpected = expected.SelectMany(x=>x);
  var flatActual = expected.SelectMany(x=>x);

  CollectionAssert.Equals(flatExpected, flatActual);
}

Note that this is a very naive implementation only, the flattened sequences may be equal while the source sequences contain the same elements but in a different partitioning.

Johannes Rudolph
-1 the first list is [{1,2},{3,4}], and the 2nd is [{1,3},{2,4}]. If you use selectMany you will find that they are the same, but in fact they are not.
Danny Chen
aehm, no?! [{1,2,3},{4}], [{1,2},{3,4}] will be the same but not the example you have given.
Johannes Rudolph