Hadron
Hadron

Reputation: 501

Loop over multiple generators of multiple values

I am trying to go through all elements of two given dictionaries to make sure they are equal, and if not I want the name of the property where they differ.

Here is my basic approach:

# Dictionnaries
v1 = { "a" : { "b" : 1, "c" : 2 }, "d" : { "e" : { "f" : 3 }}}
v2 = { "a" : { "b" : 1, "c" : 2 }, "d" : { "e" : { "f" : 4 }}}

def gen(k, v):
    if type(v) is dict:
        for k in v:
            yield from gen(k, v[k])
    else:
        yield k, v

# Used alone the generator works as expected:
for k, v in gen("root", v1):
    print("{}: {}".format(k, v))

My problem is that I want to compare the two dictionaries, from what I could gather it should look like this:

for k1, v1, k2, v2 in zip(gen("root1", v1), gen("root2", v2)):
    print("{}: {}".format(k1, v1))
    print("{}: {}".format(k2, v2))
    print("===========")

The interpreter says only two values are returned, so I assumed this would work (and it does):

for t1, t2 in zip(gen("root1", v1), gen("root2", v2)):
    print("{}: {}".format(t1[0], t1[1]))
    print("{}: {}".format(t2[0], t2[1]))
    print("===========")

My question:

It might be purely preferential, but I really wonder

  1. How I can get the first loop to work, so that I don't have to use the brackets all the time?
  2. I tried to unpack with *, but no success. Why can't I do zip(*gen(...), *gen(...))?

(By the way I know I could just add k1, v1, k2, v2 = *t1, *t2 at the beginning of the loop, I'm just looking for a nicer solution)

Upvotes: 2

Views: 265

Answers (2)

Alex Sorokoumov
Alex Sorokoumov

Reputation: 139

Your question was more specific to syntax, but there are some things you might want to consider:

  1. dictionaries are unordered collections, so that comparison might fail even if those dicts are identical just because they were iterated over in a different order.
  2. Python does not have Tail Call Optimization, so for sufficiently nested dicts, that code will fail with stack overflow.

If you want to visually compare dicts, here is a solution that takes care of these issues.

Assumption: structure of the dicts is the same (keys are the same, if a value for 1 is a dict, it will be a dict for the other one, those keys can't be iterables of other types, e.g. lists).

from collections import deque

def gen(dct1, dct2):
    q = deque()
    q.append(("root", dct1, dct2))
    while q:
        k, d1, d2 = q.pop()
        for key in d1:  # assuming the same keys for both dicts
            val1 = d1[key]
            val2 = d2[key]

            # assuming that if the structure is the same -> if val1 is a dict, val2 is a dict as well 
            if isinstance(val1, dict): 
                q.append((k, val1, val2)) 
            else:
                yield key, val1, val2

for k, val1, val2 in gen(v1, v2):
    print("{}: {}".format(k, val1))
    print("{}: {}".format(k, val2))
    print("===========")

Upvotes: 0

avayert
avayert

Reputation: 684

You can unpack each returned tuple as follows:

for (k1, val1), (k2, val2) in zip(gen('root1', v1), gen('root2', v2)):
    ...

Upvotes: 4

Related Questions