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ę.