Iterate different loops for in 1?

1

I have this code:

for k, v in redirections.items():
    new_id = list(filter(lambda a: v == a.dialog.peer.channel_id, dialogs))[0].id
    redirections[k] = new_id

for a, b in redirections2.items():
    new_id2 = list(filter(lambda a: b == a.dialog.peer.channel_id, dialogs))[0].id
    redirections2[a] = new_id2

for c, d in redirections3.items():
    new_id3 = list(filter(lambda a: d == a.dialog.peer.channel_id, dialogs))[0].id
    redirections3[c] = new_id3

I would like to be able to iterate the 3 loops into one, since I find it too redundant, I tried to do it with ifs. But it does not finish working well the loop. Redirections (2 and 3) are 3 dictionaries that I have in a configuration file, and the lambda simply checks that the data that arrives in "dialog.peer.channel_id, matches what it has in the dictionaries and if so, adds it .

With which I think it is quite redundant and I would like to simplify it.

Someone could give me some indication, the help is appreciated.

Greetings and thanks in advance.

    
asked by Peisou 20.12.2018 в 17:46
source

3 answers

3

(See Update at the end, for the response to the statement in a comment by the user)

In the itertools module you have the chain function, designed to link several iterables in lazy form (that is, without having to generate and store in memory the lists of each iterable to be able to add them).

Example:

d1 = { "a": 1, "b": 2}
d2 = { "b": 3, "c": 4}
d3 = { "a": 2, "b": 5, "f": 6}

from itertools import chain

for k,v in chain(d1.items(), d2.items(), d3.items()):
  print(k,v)

Print

a 1
b 2
b 3
c 4
a 2
b 5
f 6

Version with functional programming

I know what you're thinking. That of d1.items(), d2.items(), d3.items() is a repetition that "smells a bit bad". Maybe it could be avoided.

For example, what if all the dictionaries were in a list, for example datos = [d1, d2, d3] ? How to invoke .items() in each element to apply chain() to the result?

A simple way is to use a generating expression to apply .item() , and the unpacked operator * to convert the resulting iterable into a series of parameters for chain() . So:

datos = [d1, d2, d3]
for k,v in chain(*(d.items() for d in datos)):
  print(k,v)

This is for me the most readable and pythonica form. But you know that Python also supports the functional programming paradigm, although for my taste the syntax becomes more obfuscated. Although mathematicians tend to like this more:

from itertools import chain
from operator import methodcaller
from functools import partial

getitems = partial(methodcaller("items"))

for k, v in chain(*map(getitems, datos)):
  print(k,v)

If someone is interested in explaining how this works, you can leave a comment.

Update

Sorry. So focused was the question of how to "concatenate" the dictionaries to iterate on them, that I did not notice that during the loop those dictionaries were being modified.

Input a note about modifying iterables while iterating over them. It is forbidden (not strictly, but the result is unpredictable) adding or removing elements to the iterable while iterating through it. However, this is not the case in the user's question, what he does is modify the value stored in the keys.

That's for sure, but the mechanisms based on chain() are not applicable, because with these mechanisms we have only the keys and the values, but not the references to the original dictionaries to be able to change elements.

Fortunately, the solution is extremely simple. In fact, this same solution would be usable even if the elements are not changed and therefore it is an alternative to chain() , and to that proposed in other answers. Just make a nested loop! One iterates through the dictionaries and the other through the elements of each dictionary.

For example, the following code changes the value stored in each key of each dictionary by twice the value it had:

for d in (d1, d2, d3):
  for k,v in d.items():
    d[k] = 2*v

In your case:

for d in (redirections, redirections2, redirections3):
    for k, v in d.items():
        new_id = list(filter(lambda a: v == a.dialog.peer.channel_id, dialogs))[0].id
        d[k] = new_id

Side note The list(filter(...)) can be changed to a list comprehension that in the opinion of many (including Guido) is more readable:

new_id = [a for a in dialogs if a.dialog.peer.channel_id==v][0].id
    
answered by 20.12.2018 / 19:35
source
2

Of course, you can put together the lists and then iterate just one list:

for a,b in redirections.items() + redirections2.items() + redirections3.items():
    ...
    
answered by 20.12.2018 в 19:05
2

If the keys are different between the dictionaries, one way is to "unite" the three in a single one and go through it once.

Starting with Python 3.5 you can do this:

redirections = {"a": 1, "b": 2}
redirections2 = {"c": 3, "d": 4}
redirections3 = {"e": 5, "f": 6}

for a, b in {**redirections, **redirections2, **redirections3}.items():
  print(a,b)

If there are repeated keys between the dictionaries, you will only get one of them.

    
answered by 20.12.2018 в 19:30