5

The goal: working analogue of expression

{k1: v1, k2: v2 for k1, k2, v1, v2 in data}

or more particular case {k1: v, k2: v for k1, k2, v, _ in data}

that iterates through data only 1 time (in given examples data is iterable object of 4-tuples).

(And similar question about list comprehension, e.g. [myfunc1(v1), myfunc2(v2) for v1, v2 in data]).

I can suppose only solution using own iterator:

def getby(iterable_data, iterable_indexes):
    for indexed_data in iterable_data:
        for tupl in iterable_indexes:
            yield tuple(indexed_data[ind] for ind in tupl)
    raise StopIteration

Example of working: list(getby([('a', 'b', 'c'), ('d', 'e', 'f', 'g')], [(0, 1), (2, 1)])) returns [('a', 'b'), ('c', 'b'), ('d', 'e'), ('f', 'e')]. But possible it is slow.

I also can suppose equivalent expression for example above in terms of itertools.chain and itertools.groupby:

list(chain.from_iterable(lst for lst, _ in groupby([('a', 'b', 'c'), ('d', 'e', 'f', 'g')],
                                                   lambda lst: [(lst[0], lst[1]), (lst[2], lst[1])])))

But possible it is ugly.

ipython-based comparison of two solution: the first 1000 loops, best of 3: 20.2 ms per loop, the second 1000 loops, best of 3: 4.12 ms per loop, so the second solution really faster. But maybe is more elegant solution exist?

kupgov
  • 383
  • 6
  • 16
  • 2
    Maybe `{k: v for k1, k2, v1, v2 in data for k, v in ((k1, v1), (k2, v2))}`? Particular case: `{k: v for k1, k2, v, _ in data for k in (k1, k2)}`. – Delgan Dec 20 '15 at 14:45
  • @Delgan, of course! This is really elegant solution that works slightly faster! Thanks! – kupgov Dec 20 '15 at 14:53
  • No problem, do not forget to accept the @Martijn solution. ;) – Delgan Dec 20 '15 at 14:54

1 Answers1

5

The comprehension format is strictly limited to one result per iteration. You can add extra loops though, to loop over the multiple elements you want to insert:

{k: v for k1, k2, v1, v2 in data for k, v in ((k1, v1), (k2, v2))}

or your second example:

{k: v for k1, k2, v, _ in data for k in (k1, k2)}

which is the equivalent of:

result = {}
for k1, k2, v1, v2 in data:
    for k in (k1, k2):
        result[k] = v

If you can write something out as nested loops, possibly with if statements in between, with just one statement at the innermost level that assigns a key-value pair, then you can express it in a dictionary comprehension.

You can do this too with chain.from_iterable(), but then produce lists or tuples with a sequence of key-value pairs, and pass the result to the dict() function:

dict(chain.from_iterable(((k1, v1), (k2, v2)) for k1, v1, k2, v2 in data))
dict(chain.from_iterable(((k1, v), (k2, v)) for k1, k2, v, _ in data))
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343