Będziemy odtwarzać znaną z języka JavaScript i bardzo przydatną funkcję groupBy, która pozwala listę słowników pogrupować według danego klucza.
Na przykładzie poniższym:
people = [
{
"name": "John",
"age" : 30,
"grade" : 5
},
{
"name": "Jim",
"age": 24,
"grade" : 4
},
{
"name": "Jane",
"age": 30,
"grade" : 4
},
{
"name": "Bob",
"age": 24,
"grade" : 5
},
]
Nasza funkcja, jeżeli przyjmie wartość „grade” to pogrupuje nasze słowniki „po ocenach” i będą klucze „4” oraz „5” z wartościami jakimi są obiekty posiadające taką ocenę.
Rozwiązanie
Zacznijmy od utworzenia słownika, który będzie zawierał odpowiednie klucze.
def group_by(arr, key):
output = dict()
keys = list(set([x[key] for x in arr]))
for key in keys:
output[key] = dict()
return output
print(group_by(people, 'grade'))
# {4: {}, 5: {}}
Tworzymy nowy słownik. Następnie do keys wrzucamy unikalne wartości klucza, którego szukamy.
Przechodzimy po keys w pętli i do output przypisujemy odpowiedni klucz z pustym słownikiem, zwracamy output.
Szukając „po ocenach” daje nam klucze 4 oraz 5 (takie są oceny). To już połowa sukcesu – teraz pod tymi kluczami muszą znaleźć się obiekty z takimi ocenami.
Aha, drobny błąd – potrzebujemy list, nie słowników:
def group_by(arr, key):
output = dict()
keys = list(set([x[key] for x in arr]))
for k in keys:
output[k] = list()
return output
print(group_by(people, 'grade'))
# {4: [], 5: []}
Tak to będzie wyglądać
def group_by(arr, key):
output = dict()
keys = list(set([x[key] for x in arr]))
for k in keys:
output[k] = list()
for record in arr:
rec_key_val = record[key]
output[rec_key_val].append(record)
return output
print(group_by(people, 'grade'))
# {
# 4: [{'name': 'Jim', 'age': 24, 'grade': 4},
# {'name': 'Jane', 'age': 30, 'grade': 4}],
# 5: [{'name': 'John', 'age': 30, 'grade': 5},
# {'name': 'Bob', 'age': 24, 'grade': 5}
# ]
# }
Czyli przechodzimy po rekordach, sprawdzamy wartość szukanego klucza (która jest kluczem w naszym słowniku output, w tym wypadku – ocenę).
Pod wartością szukanego klucza w output mamy listę, do której dodajemy rekord i zwracamy output.
Chcieliśmy pogrupować po ocenie – działa.
Zobaczmy, czy po wieku zadziała!
def group_by(arr, key):
output = dict()
keys = list(set([x[key] for x in arr]))
for k in keys:
output[k] = list()
for record in arr:
rec_key_val = record[key]
output[rec_key_val].append(record)
return output
print(group_by(people, 'age'))
# {
# 24: [{'name': 'Jim', 'age': 24, 'grade': 4}, {'name': 'Bob', 'age': 24, 'grade': 5}],
# 30: [{'name': 'John', 'age': 30, 'grade': 5}, {'name': 'Jane', 'age': 30, 'grade': 4}]
# }
Jest ok.
Sposób 2 – dict comprehension
Sposób prosty i trudny zarazem:
def group_by(arr, key):
return {record[key]: [r for r in arr if r[key] == record[key]] for record in arr}
print(group_by(people, 'age'))
# {
# 24: [{'name': 'Jim', 'age': 24, 'grade': 4}, {'name': 'Bob', 'age': 24, 'grade': 5}],
# 30: [{'name': 'John', 'age': 30, 'grade': 5}, {'name': 'Jane', 'age': 30, 'grade': 4}]
# }
Albo to rozumiemy, albo nie. Tworzymy klucze, czyli dla wartości 'age’ będą to 24 i 30 (tylko taki wiek występuje w naszej liście).
Następnie małe list comprehension – dodajemy tam rekordy pod warunkiem, że ich klucz, tutaj wiek, to wartość, jakiej oczekujemy. Pod ’24’ nie mogą być przecież obiekty, których wiek jest inny.
I tym sprytnym sposobem też możemy osiągnąć group_by, za pomocą dict i list comprehension. Pytanie – czy warto. Taki kod nie jest nazbyt zrozumiały, ale w ramach ćwiczenia nadaje się.