Generate list from two different lists by key

Viewed 68

Considering the following structure:

myObj1 = [{"id":1, "name":"john"},
          {"id":2, "name":"roger"},
          {"id":3, "name":"carlos"}]
myObj2 = [{"group": "myGroup1","persons":[1, 2, 3]},
          {"group": "myGroup2", "persons":[2]},
          {"group": "myGroup3", "persons":[1,3]}]

I would like the produce the following result:

result = [{"group": "myGroup1","persons":[{"id":1, "name":"john"},
                                          {"id":2, "name":"roger"},
                                          {"id":3, "name":"carlos"}]},
          {"group": "myGroup2", "persons":[{"id":2, "name":"roger"}]},
          {"group": "myGroup3", "persons":[{"id":1, "name":"john"},
                                           {"id":3, "name":"carlos"}]}]

The challenge is for each value in the "persons" array substitute it for the entire myObj1 item value where the id matches.

I could achieve that using like 3 for's but I want to know if there's a pythonic way of doing this using interpolation, map, filter, sets and etc.. I'm knew to the python word but got this question from an interviewer and he told me that I was supposed to do that with 1-2 lines of code.

UPDATE: Here's what was my newbie approach:

for item in myObj1:
    id = item["id"]
    for item2 in myObj2:
        for i in range(len(item2["persons"])):        
            if item2["persons"][i] == id:
                item2["persons"][i] = item
5 Answers

How about the following:

result = [dict(x) for x in myObj2]

for grp in result:
    grp["persons"] = [p for p in myObj1 if p["id"] in grp["persons"]]

We create a new list (using dict(x) to ensure we don't retain references to the elements ofmyObj2`), and then update accordingly.

result = myObj2.copy()
for d in result:
    d['persons'] = [[j for j in myObj1 if j['id']==i][0] for i in d['persons']]

result

Output:

[{'group': 'myGroup1',
  'persons': [{'id': 1, 'name': 'john'},
   {'id': 2, 'name': 'roger'},
   {'id': 3, 'name': 'carlos'}]},
 {'group': 'myGroup2', 'persons': [{'id': 2, 'name': 'roger'}]},
 {'group': 'myGroup3',
  'persons': [{'id': 1, 'name': 'john'}, {'id': 3, 'name': 'carlos'}]}]

You can try this:

myObj1 = [{"id":1, "name":"john"},
      {"id":2, "name":"roger"},
      {"id":3, "name":"carlos"}]
myObj2 = [{"group": "myGroup1","persons":[1, 2, 3]},
      {"group": "myGroup2", "persons":[2]},
      {"group": "myGroup3", "persons":[1,3]}]
final_dict = [{a:b if a != "persons" else c for a,b in d.items()} for c, d in zip(myObj1, myObj2)]

Output:

[{'persons': {'id': 1, 'name': 'john'}, 'group': 'myGroup1'}, {'persons': {'id': 2, 'name': 'roger'}, 'group': 'myGroup2'}, {'persons': {'id': 3, 'name': 'carlos'}, 'group': 'myGroup3'}]

one approach is to substitute the values for the "persons" key, like this:

[group.update({'persons':[myObj1[next(index for (index, d) in enumerate(myObj1) if d["id"] == idstud)] for idstud in group['persons'] if idstud in [i['id'] for i in myObj1]]}) for group in myObj2]

What you're trying to do is essentially a group by operation followed by mapping over dictionary values. This is an example of where the itertools module really shines.

from itertools import chain, groupby


def concat(lists):
    """
    Helper function to make concatenating lists/iterables easier
    """
    return list(chain.from_iterable(lists))

by_group = {
    id: list(people) 
    for id, people in groupby(myObj1, key=lambda person: person['id'])
}

result = [
    {'group': group['group'], 
     'persons': concat(by_group[id] for id in group['persons'])}
    for group in myObj2
]

In this example, you still need the for-loops, but it is now clear what those loops are trying to do. The first is making an intermediate data structure to keep track of who has what id. The second is then going through another data structure and calculating who's in what group based on the groupby operation.

Related