blah
blah

Reputation: 664

How can I transform nested for-loops with if-statements into a list comprehension?

Im completely new to list comprehensions, and I know how to make a basic one. However, how can I transform these nested for-loops with an if-statement, into a list comprehension?

fyi: listDicts1 and listDicts2 are lists of dictionaries (did not want to provide them as they are very large).

  for d in listDicts1:
        for d2 in listDicts2:
            if d['name'] in d2['text']:
                  d['person'].append(d2) 
        print(d)

My attempt:

new = [d2 for d in listDicts1 for d in listDicts2 if d['name'] in d2['text']]

Upvotes: 1

Views: 90

Answers (2)

Pierre D
Pierre D

Reputation: 26221

I tend to agree with @Aplet123's comment that the OP's initial code is concise and readable. But there is one important caveat: it does modify in place the dicts of listDicts1, which in my book is a big no-no: imagine that an unsuspecting user (yourself, in three months) has a bunch of dicts, puts them in a list and passes that to your function, only to (later, during debugging and much head scratching) discover that the original dicts themselves have been sneakily modified in place as a side-effect of your function... (I guarantee you, that's happened in the best families).

So, if instead you'd like to keep the initial dicts unchanged, which in much safer and IMHO always preferable, then you would have to generate new dicts whenever there is a modification needed (when your condition is True).

Version 1: pure comprehensions (but also always makes a new dict):

listDicts3 = [{
    **d,
    **{'person': d.get('person', []) + [
        d2 for d2 in listDicts2 if d['name'] in d2['text']
    ]}} for d in listDicts1]

Version 2, preferred: only makes new dicts when needed (others are just copy-through by reference):

persons = [[d2 for d2 in listDicts2 if d['name'] in d2['text']] for d in listDicts1]
listDicts3 = [
    {**d, **dict(person=d.get('person', []) + p)} if p else d
    for d, p in zip(listDicts1, persons)
]

Note that both of the above start with an empty list if person was not yet a member of d.

Test example:

listDicts1 = [dict(name='fred', person=[]), dict(name='paul', person=[])]
listDicts2 = [dict(text='paul ate an apple'), dict(text='anne reads a book')]

# ... (one of the code snippets above to make listDicts3)

listDicts3
# output:
[{'name': 'fred', 'person': []},
 {'name': 'paul', 'person': [{'text': 'paul ate an apple'}]}]

listDicts1
# out (shows it is unchanged)
[{'name': 'fred', 'person': []}, {'name': 'paul', 'person': []}]

# your code, with side-effect
for d in listDicts1:
    for d2 in listDicts2:
        if d['name'] in d2['text']:
              d['person'].append(d2) 
    print(d)
# out:
{'name': 'fred', 'person': []}
{'name': 'paul', 'person': [{'text': 'paul ate an apple'}]}

# HOWEVER:
listDicts1
# out:
[{'name': 'fred', 'person': []},
 {'name': 'paul', 'person': [{'text': 'paul ate an apple'}]}]

Note: you can avoid the side effect and keep your code almost as is:

for d in listDicts1:
    d = d.copy()
    for d2 in listDicts2:
        if d['name'] in d2['text']:
              d['person'].append(d2) 
    print(d)

Upvotes: 1

George Crowther
George Crowther

Reputation: 558

I'm inferring quite a lot from the way you've structured your loop here, it would be easier if you could provide a minimal example.

listDicts1 = [{'name': 'A', 'person': []}, {'name': 'B', 'person': []}]
listDicts2 = [{'text': ['A', 'B']}, {'text': ['B', 'C']}, {'text': ['C', 'A']}]
[d1['person'].append(d2) for d1 in listDicts1 for d2 in listDicts2 if d1['name'] in 
d2['text']]

print(listDicts1)

"[{'name': 'A', 'person': [{'text': ['A', 'B']}, {'text': ['C', 'A']}]}, {'name': 'B', 
 'person': [{'text': ['A', 'B']}, {'text': ['B', 'C']}]}]"

Whether this is a good idea to do or not, is up for debate, my only objection to it would be readability.

Upvotes: 1

Related Questions