Background
I find that I sometimes need to iterate over multiple mappings having identical keys. Thus, I do something like:
def f(a: Mapping[Key, Value], b: Mapping[Key, Value], c: Mapping[Key, Value]) -> Any:
for key, a_value in a.items():
b_balue = b[key]
c_balue = c[key]
...
This has one downside of not verifying that a, b, and c have identical keys. The strict flag was added to zip to do a similar verification because it’s extremely useful. This also has the downside of being slightly convoluted (iteration happens on a instead of on all of them, and the key lookups happen on only b and c.
Proposal
Add an itertool class that reflects the parallel structure and verifies equivalence of keys:
from collections.abc import Iterator, Mapping
from typing import TypeVar, override
K = TypeVar('K')
V = TypeVar('V')
class JoinMapping(Mapping[K, tuple[V, ...]]):
def __init__(self, x: Mapping[K, V], /, *args: Mapping[K, V]):
super().__init__()
self.mappings = x, *args
n = len(x)
s = set(x)
for y in args:
if len(y) != n:
raise ValueError
if set(y) != s:
raise ValueError
@override
def __getitem__(self, key: K) -> tuple[V, ...]:
return tuple(mapping[key] for mapping in self.mappings)
@override
def __iter__(self) -> Iterator[K]:
return iter(self.mappings[0])
@override
def __len__(self) -> int:
return len(self.mappings[0])
This checks for equal lengths, iterates over one mapping, while indexing the other mappings. I initially thought of proposing this for Python, but I think if this were to be added, it would belong in more-itertools first.
This is a bit like ChainMap in the sense that it combines mappings, but instead of delegation of values, it creates tuples of keys.
Examples
JoinMappings(a, b, c)[x] # equivalent to (a[x], b[x], c[x])
JoinMappings(a, b, c).items() # equivalent to ((k, (a[k], b[k], c[k])) for k in a)
JoinMappings(a, b, c).values() # equivalent to ((a[k], b[k], c[k]) for k in a)
# etc.