starikcetin
starikcetin

Reputation: 1488

Comparing Tuples, Ignoring Order of Elements

Consider I have objects Foo and Bar; and 3 tuples named A, B and C.

A = (Foo, Bar)
B = (Bar, Foo)
C = (Foo, Bar)

I want to know if their elements are the same, not taking order of elements into account. So my desired results are;

A.HasSameElementsWith(B)  -> True
A.HasSameElementsWith(C)  -> True
B.HasSameElementsWith(C)  -> True

I know I can run a nested loop to compare them for each of their elements. Something along the lines of:

foreach (itemA in A)
{
    bool flag = false;

    foreach (itemB in B)
    {
        if(itemA == itemB)
        {
            flag = true;
            break;
        }
    }

    if (!flag) return false;
}

return true;

But this seems inefficient. Is there a more convinient way to do this?


Note:

I'm using generics, so Foo and Bar can be of any type. But they will be the same type with each other. (i.e. The type of Foo will be the same with the type of Bar)

Upvotes: 3

Views: 2056

Answers (2)

svick
svick

Reputation: 244757

If you have two 2-tuples, then there are only two options for them to be equal according to your rules and you can write that as an almost one-liner method:

public static bool HasSameElementsWith<T>(this (T, T) tuple1, (T, T) tuple2) =>
    (Equals(tuple1.Item1, tuple2.Item1) && Equals(tuple1.Item2, tuple2.Item2)) ||
    (Equals(tuple1.Item1, tuple2.Item2) && Equals(tuple1.Item2, tuple2.Item1));

If you can have more than two items per tuple, then I would start to consider them to be a collection and the question then becomes if two collections have the same items. And to do that, you can count up each item in the first collection in a Dictionary<T, int>, and then count down the items from the second collection. If both collections contain the same items, all counts should be zero at the end. (If you're sure items in each collection are unique, you could use HashSet<T> instead.) In code:

public static bool HasSameElementsWith<T>(
    this IEnumerable<T> collection1, IEnumerable<T> collection2)
{
    var counts = new Dictionary<T, int>();

    foreach (var item in collection1)
    {
        counts.TryGetValue(item, out int count);
        count++;
        counts[item] = count;
    }

    foreach (var item in collection2)
    {
        counts.TryGetValue(item, out int count);
        if (count == 0)
            return false;
        count--;
        counts[item] = count;
    }

    return counts.Values.All(c => c == 0);
}

Now you can implement the tuple version of HasSameElementsWith on top of the collection version:

public static bool HasSameElementsWith<T>(this (T, T) tuple1, (T, T) tuple2) =>
    HasSameElementsWith(tuple1.ToArray(), tuple2.ToArray());

public static T[] ToArray<T>(this (T, T) tuple) => new[] { tuple.Item1, tuple.Item2 };

Upvotes: 4

Michał Turczyn
Michał Turczyn

Reputation: 37337

I will exapnd @doctorlove comment a little. He mentioned sorting. Once you have sorted it, you can compare them element by element. Pseudo code:

public bool Compare(A, B){
    return (A[1] == B[1] & A[2] == B[2]);
}

Upvotes: 0

Related Questions