Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)

like image 626
S. Tarık Çetin Avatar asked Nov 25 '25 07:11

S. Tarık Çetin


1 Answers

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 };
like image 183
svick Avatar answered Nov 26 '25 22:11

svick



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!