Skip to content

JoinMapping: A class to join mappings with equal keys into one mapping #839

@NeilGirdhar

Description

@NeilGirdhar

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions