1

Assume I've got the dictionaries:

dict1 = {'A': 1, 'B': 2, 'C' : 3}
dict2 = {'a': 4, 'b': 5, 'c' : 6}

This link suggest several ways to merge the two but all of the merges are simply concatenations. I want to merge them like a dealer shuffles cards, or like a zipper zips. What I mean by this is that once merging dict1 and dict2, the resulting dict3 should become

dict3 = {'A': 1, 'a': 4, 'B': 2, 'b': 5, 'C' : 3, 'c' : 6}

So the merge grabs elements from dict1 and dict2 in an alternating fashion. My dictionaries are in fact very large so doing it manually is not an option.

martineau
  • 119,623
  • 25
  • 170
  • 301
Parseval
  • 503
  • 1
  • 4
  • 14
  • 1
    Just to check: Why do you need that specific ordering? While 3.7 did make insertion-ordered `dict`s a language-guarantee, it's fairly rarely you *rely* on it (among other things, the order isn't part of an equality check). Just making sure you don't have [an XY problem](https://meta.stackexchange.com/q/66377/322040) here. – ShadowRanger May 10 '22 at 12:28

3 Answers3

1

There is round-robin itertools recipe to select data this way.

You can use:

dict3 = dict(roundrobin(dict1.items(), dict2.items()))

output:

{'A': 1, 'a': 4, 'B': 2, 'b': 5, 'C': 3, 'c': 6}

recipe:

from itertools import cycle, islice
def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    num_active = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while num_active:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            # Remove the iterator we just exhausted from the cycle.
            num_active -= 1
            nexts = cycle(islice(nexts, num_active))

You can also use more-itertools.interleave

from more_itertools import interleave

dict(interleave(dict1.items(), dict2.items()))
mozway
  • 194,879
  • 13
  • 39
  • 75
1

Indented for readability:

from itertools import chain
dict(
    chain.from_iterable(
        zip(dict1.items(), dict2.items())
    )
)

If your dictionaries are not guaranteed to be of equal size, adapt this solution to use itertools.zip_longest() instead of basic zip()

Nikolaj Š.
  • 1,457
  • 1
  • 10
  • 17
  • 1
    It's an ugly adaptation to use `zip_longest` though, since you need to use a sentinel that can't possibly occur in the inputs (I typically use `sentinel = object()`) and explicitly exclude it from the values after the fact. The round robin recipe has the advantage of handling the uneven input case without adding that per item filtering check. – ShadowRanger May 10 '22 at 12:35
  • 1
    I suppose in this case it's not quite so bad; since you're using all two-`tuple`s (always truthy), the fill item can be `False` and you can just wrap the `chain.from_iterable` in `filter(None, ...)` without needing a special sentinel. – ShadowRanger May 10 '22 at 12:44
  • Exactly. Or with a generator expression `(t for t in chain.from_iterable(...) if t is not None)` – Nikolaj Š. May 10 '22 at 12:50
1

There are many things that could go wrong with this approach. What if your 2 dictionnaries don't have the same length? What if they have duplicate keys? What if later you need 3 dicts instead of 2?

Also, do you have a real use case behind this requirement? Do you really need to build a new dict with all data like this, or do you simply need to iterate over the (key, value) pairs in a specific, alternate fashion like this?

Assuming you don't have to worry about the above, if you only need to iterate, you could simply do something like that:

def iter_zip(dict1, dict2):
    for i1, i2 in zip(dict1.items(), dict2.items()):
        yield i1
        yield i2
   
dict1 = {'A': 1, 'B': 2, 'C' : 3}
dict2 = {'a': 4, 'b': 5, 'c' : 6}

# you can then do
for key, val in iter_zip(dict1, dict2):
    do_something(key, val)

And if you really need to build a new dict, it is now as simple as:

dict(iter_zip(dict1, dict2))
# {'A': 1, 'a': 4, 'B': 2, 'b': 5, 'C': 3, 'c': 6}
Guillaume
  • 5,497
  • 3
  • 24
  • 42
  • Correct, there is no need to worry of any about the above since the dictionaries will always be of equal size and there is a particular use case that forces me to alternate the elements like this. Thanks a lot for answer! – Parseval May 10 '22 at 12:52