Trier les dictionnaires d'une liste en fonction d'une de leur clé

L’auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonsoir,

Je viens vers vous car je bloque sur ce code Python. J’ai un dictionnaire de codes qui associe à une lettre une chaîne de caractères. J’ai ensuite une liste de dictionnaires décrivant un objet. Voici ce que ça donne :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
codes = {
    "Y": "Titre Y",
    "B": "Titre B",
    "A": "Titre A",
    "D": "Titre D"
}

parts = [
    {"id": "A-004", "name": "Foo"},
    {"id": "D-142", "name": "Foo"},
    {"id": "B-044", "name": "Foo"},
    {"id": "Y-002", "name": "Foo"},
    {"id": "D-024", "name": "Foo"},
]

J’ai besoin d’en tirer le dictionnaire suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
what_I_want = {
    "A": [
        {"id": "A-004", "name": "Foo"},
    ],
    "B": [
        {"id": "B-044", "name": "Foo"},
    ],
    "D": [
        {"id": "D-024", "name": "Foo"},
        {"id": "D-142", "name": "Foo"},
    ],
    "Y": [
        {"id": "Y-002", "name": "Foo"},
    ]
}

La première condition est que chaque objet soit ajouté à la liste qui lui correspond. Si son id commence par A alors il va dans la liste de la clé A. Celle-ci est assez simple. La deuxième, celle sur laquelle je bloque, est qu’il faut que chaque liste soit trié en fonction de l’id de ses objets. Par exemple l’objet portant l’id D-024 doit être avant l’objet portant l’id D-142. Voici ce que j’ai fait pour la première condition :

1
2
3
4
5
6
7
8
9
what_I_get = {}

for code in codes:
    what_I_get[code] = []
    for part in parts:
        if part["id"][0] == code:
            what_I_get[code].append(part)

print(what_I_get)

Ce qui fonctionne sans problème. Comment résoudre la deuxième solution ?

Merci pour votre aide et bonne soirée à vous ! :)

+0 -0

Cette réponse a aidé l’auteur du sujet

Salut !

Je te suggère de regarder du côté de la fonction sorted(), qui prend un itérable et qui le trie (par défaut par ordre alphabétique ou numérique croissant). Pour des cas plus poussés comme le tiens, elle prend aussi un paramètre key qui prend un callable (qui devra recevoir un élément de l’itérable et retourner quelque chose qui peut être ordonné, comme une lettre ou un chiffre). Tu peux alors utiliser une lambda, comme ceci : sorted(dicts, key=lambda d: d["value"]), qui ordonnera une liste de dictionnaire selon leurs clés value.

Cependant, j’ai du mal à comprendre de quelle liste tu parle. Ta variable what_I_get est un dictionnaire, qui n’est pas un ordonné (sauf dans les dernières versions de Python, mais même dans ce cas, il n’est ordonné que selon l’ordre d’insertion des valeurs). Il faut soit que tu fasse une liste, soit que tu utilise un collections.OrderedDict. ;)

"Les accidents dans un système doivent se produire, mais il n’est pas obligatoire qu’ils produisent pour vous et moi." Laurence Gonzales - Deep Survival

+0 -0

Ce problème peut se résoudre simplement en employant sorted et itertools.groupby directement sur la liste parts :

1
2
sorted_parts = sorted(parts, key=lambda d: d['id'])
what_I_get = {k: list(g) for k, g in groupby(sorted_parts, lambda d: d['id'][0])}

Édité par yoch

+3 -0
Auteur du sujet

Merci pour vos réponses !

Effectivement, ça fonctionne à la perfection :

1
2
3
4
5
6
for code in codes:
    what_I_get[code] = []
    for part in parts:
        if part["id"][0] == code:
            what_I_get[code].append(part)
    what_I_get[code] = sorted(what_I_get[code], key=lambda part: part["id"])

@yoch ta solution est moins lisible je trouve (manque d’habitude surement) mais merci pour la fonction groupby, je ne la connaissais pas !

+0 -0
Auteur du sujet

Il y a une raison à ce pourquoi tu ne veux pas plutôt le format ci-dessous ?

1
2
3
4
5
what_I_want = {
    "A": { 4: "Foo" },
    "B": { 44: "Foo"},
    "D": { 24: "Foo", 142: "Foo"}
}
blo yhg

Oui il y en a une toute simple, il n’y a pas que deux valeurs dans mon dictionnaire, j’ai fait ça pour simplifier. Et autre désavantage, c’est qu’un dictionnaire n’est pas ordonné donc en parcourant D dans ton exemple je pourrais très bien récupérer l’item 142 avant le 24. L’ordre est important car à partir de ces éléments je dois maintenant générer un tableau LaTeX.

+0 -0

Cette réponse a aidé l’auteur du sujet

Effectivement, ça fonctionne à la perfection :

1
2
3
4
5
6
for code in codes:
    what_I_get[code] = []
    for part in parts:
        if part["id"][0] == code:
            what_I_get[code].append(part)
    what_I_get[code] = sorted(what_I_get[code], key=lambda part: part["id"])

Le souci de cette solution, c’est que tu parcours l’ensemble des parts pour chaque code. Si ton volume de codes est conséquent, cela aura un impact négatif sur le temps d’exécution.

Par ailleurs, même avec ton approche, il est sans doute bénéfique de trier la liste parts au début, ensuite l’ordre d’insertion dans les sous-listes est garanti.

@yoch ta solution est moins lisible je trouve (manque d’habitude surement) mais merci pour la fonction groupby, je ne la connaissais pas !

Wizix

Le principe de ma solution, c’est de trier une seule fois parts directement sur le champs id, le reste du traitement consistant simplement à découper cette liste triée par code.

C’est ce que fait groupby, mais ça peut être facilement fait à la main :

1
2
3
4
5
6
7
what_I_get = {}
code = None
for part in sorted(parts, key=lambda d: d['id']):
    if part['id'][0] != code:
        code = part['id'][0]
        what_I_get[code] = []
    what_I_get[code].append(part)

voire même de façon implicite, par exemple avec un collections.defaultdict:

1
2
3
what_I_get = defaultdict(list)
for part in sorted(parts, key=lambda d: d['id']):
    what_I_get[part['id'][0]].append(part)

L’autre différence entre ce que tu proposes et ma solution, c’est que les codes qui ne seraient pas représentés dans parts ne seront pas dans what_I_get. Mais c’est facile à résoudre:

1
2
3
what_I_get = {code: [] for code in codes}
for part in sorted(parts, key=lambda d: d['id']):
    what_I_get[part['id'][0]].append(part)

Édité par yoch

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte