Python: tuples / dictionnaires comme clés, sélectionner, trier

104

supposons que j'ai des quantités de fruits de différentes couleurs, par exemple 24 bananes bleues, 12 pommes vertes, 0 fraises bleues et ainsi de suite. Je voudrais les organiser dans une structure de données en Python qui permet une sélection et un tri faciles. Mon idée était de les mettre dans un dictionnaire avec des tuples comme clés, par exemple,

{ ('banana',    'blue' ): 24,
  ('apple',     'green'): 12,
  ('strawberry','blue' ): 0,
  ...
}

ou même des dictionnaires, par exemple,

{ {'fruit': 'banana',    'color': 'blue' }: 24,
  {'fruit': 'apple',     'color': 'green'}: 12,
  {'fruit': 'strawberry','color': 'blue' }: 0,
  ...
}

J'aimerais récupérer une liste de tous les fruits bleus, ou des bananes de toutes les couleurs, par exemple, ou trier ce dictionnaire par le nom du fruit. Existe-t-il des moyens de le faire de manière propre?

Il se peut que les dictionnaires avec des tuples comme clés ne soient pas la bonne façon de gérer cette situation.

Toutes les suggestions sont les bienvenues!

Nico Schlömer
la source
26
On dirait que vous voulez une base de données ...
Adam Rosenfield
4
Vous feriez mieux de définir un clsas pour modéliser ces données, plutôt que d'essayer de coordonner différentes collections de ces valeurs
Cuga
2
@AdamRosenfield en construit peut-être un.
Prof.Falken
Je voulais juste ajouter qu'un dictionnaire n'est pas hachable, donc la deuxième syntaxe que vous demandez n'est pas possible car {'fruit': 'banana', 'color': 'blue'} qui est un dictionnaire ne peut pas être utilisé comme clé pour un autre dictionnaire. cela provoquerait un TypeError: unhashable type: 'dict'.
epeleg

Réponses:

147

Personnellement, l'une des choses que j'aime chez python est la combinaison tuple-dict. Ce que vous avez ici est en fait un tableau 2d (où x = nom du fruit et y = couleur), et je suis généralement un partisan du dict des tuples pour implémenter des tableaux 2d, du moins quand quelque chose comme numpyou une base de données n'est pas plus approprié . Bref, je pense que vous avez une bonne approche.

Notez que vous ne pouvez pas utiliser de dictionnaires comme clés dans un dict sans faire un travail supplémentaire, ce n'est donc pas une très bonne solution.

Cela dit, vous devriez également considérer namedtuple () . De cette façon, vous pourriez faire ceci:

>>> from collections import namedtuple
>>> Fruit = namedtuple("Fruit", ["name", "color"])
>>> f = Fruit(name="banana", color="red")
>>> print f
Fruit(name='banana', color='red')
>>> f.name
'banana'
>>> f.color
'red'

Vous pouvez maintenant utiliser votre dict de comptage de fruits:

>>> fruitcount = {Fruit("banana", "red"):5}
>>> fruitcount[f]
5

Autres astuces:

>>> fruits = fruitcount.keys()
>>> fruits.sort()
>>> print fruits
[Fruit(name='apple', color='green'), 
 Fruit(name='apple', color='red'), 
 Fruit(name='banana', color='blue'), 
 Fruit(name='strawberry', color='blue')]
>>> fruits.sort(key=lambda x:x.color)
>>> print fruits
[Fruit(name='banana', color='blue'), 
 Fruit(name='strawberry', color='blue'), 
 Fruit(name='apple', color='green'), 
 Fruit(name='apple', color='red')]

En écho à chmullig, pour obtenir une liste de toutes les couleurs d'un fruit, il faudrait filtrer les clés, c'est-à-dire

bananas = [fruit for fruit in fruits if fruit.name=='banana']
expéditeur
la source
#senderle Vous avez écrit comme commentaire à une autre réponse "Mais mon instinct est qu'une base de données est excessive pour les besoins du PO;"; Vous préférez donc créer une sous-classe namedtuple. Mais que sont les instances de classes sinon des micro-bases de données avec leurs propres outils pour traiter leurs données?
eyquem
Puis-je à partir de ces sous-listes d'extraits avec name='banana'?
Nico Schlömer
2
Comme l'a souligné chmullig, vous devrez filtrer les clés, c'est bananas = filter(lambda fruit: fruit.name=='banana', fruits)-à- dire ou bananas = [fruit for fruit in fruits if fruit.name=='banana']. C'est une façon dont les dictionnaires imbriqués sont potentiellement plus efficaces; tout dépend de la manière dont vous prévoyez d'utiliser les données.
senderle
L'ajout d'une clé supplémentaire dans le tuple nommé ne faciliterait-il pas les choses? Je dirais ajouter un nouvel attributcount
openrijal
18

Votre meilleure option sera de créer une structure de données simple pour modéliser ce que vous avez. Ensuite, vous pouvez stocker ces objets dans une liste simple et les trier / les récupérer comme vous le souhaitez.

Pour ce cas, j'utiliserais la classe suivante:

class Fruit:
    def __init__(self, name, color, quantity): 
        self.name = name
        self.color = color
        self.quantity = quantity

    def __str__(self):
        return "Name: %s, Color: %s, Quantity: %s" % \
     (self.name, self.color, self.quantity)

Ensuite, vous pouvez simplement construire des instances "Fruit" et les ajouter à une liste, comme indiqué de la manière suivante:

fruit1 = Fruit("apple", "red", 12)
fruit2 = Fruit("pear", "green", 22)
fruit3 = Fruit("banana", "yellow", 32)
fruits = [fruit3, fruit2, fruit1] 

La liste simple fruitssera beaucoup plus facile, moins déroutante et mieux entretenue.

Quelques exemples d'utilisation:

Toutes les sorties ci-dessous sont le résultat après l'exécution de l'extrait de code donné suivi de:

for fruit in fruits:
    print fruit

Liste non triée:

Affiche:

Name: banana, Color: yellow, Quantity: 32
Name: pear, Color: green, Quantity: 22
Name: apple, Color: red, Quantity: 12

Trié par ordre alphabétique par nom:

fruits.sort(key=lambda x: x.name.lower())

Affiche:

Name: apple, Color: red, Quantity: 12
Name: banana, Color: yellow, Quantity: 32
Name: pear, Color: green, Quantity: 22

Trié par quantité:

fruits.sort(key=lambda x: x.quantity)

Affiche:

Name: apple, Color: red, Quantity: 12
Name: pear, Color: green, Quantity: 22
Name: banana, Color: yellow, Quantity: 32

Où couleur == rouge:

red_fruit = filter(lambda f: f.color == "red", fruits)

Affiche:

Name: apple, Color: red, Quantity: 12
Cuga
la source
17

Base de données, dict de dictionnaires, dictionnaire de liste de dictionnaires, tuple nommé (c'est une sous-classe), sqlite, redondance ... Je n'en croyais pas mes yeux. Quoi d'autre ?

"Il se pourrait bien que les dictionnaires avec des tuples comme clés ne soient pas la bonne manière de gérer cette situation."

"mon instinct est qu'une base de données est excessive pour les besoins du PO;"

Ouais! j'ai pensé

Donc, à mon avis, une liste de tuples suffit largement:

from operator import itemgetter

li = [  ('banana',     'blue'   , 24) ,
        ('apple',      'green'  , 12) ,
        ('strawberry', 'blue'   , 16 ) ,
        ('banana',     'yellow' , 13) ,
        ('apple',      'gold'   , 3 ) ,
        ('pear',       'yellow' , 10) ,
        ('strawberry', 'orange' , 27) ,
        ('apple',      'blue'   , 21) ,
        ('apple',      'silver' , 0 ) ,
        ('strawberry', 'green'  , 4 ) ,
        ('banana',     'brown'  , 14) ,
        ('strawberry', 'yellow' , 31) ,
        ('apple',      'pink'   , 9 ) ,
        ('strawberry', 'gold'   , 0 ) ,
        ('pear',       'gold'   , 66) ,
        ('apple',      'yellow' , 9 ) ,
        ('pear',       'brown'  , 5 ) ,
        ('strawberry', 'pink'   , 8 ) ,
        ('apple',      'purple' , 7 ) ,
        ('pear',       'blue'   , 51) ,
        ('chesnut',    'yellow',  0 )   ]


print set( u[1] for u in li ),': all potential colors'
print set( c for f,c,n in li if n!=0),': all effective colors'
print [ c for f,c,n in li if f=='banana' ],': all potential colors of bananas'
print [ c for f,c,n in li if f=='banana' and n!=0],': all effective colors of bananas'
print

print set( u[0] for u in li ),': all potential fruits'
print set( f for f,c,n in li if n!=0),': all effective fruits'
print [ f for f,c,n in li if c=='yellow' ],': all potential fruits being yellow'
print [ f for f,c,n in li if c=='yellow' and n!=0],': all effective fruits being yellow'
print

print len(set( u[1] for u in li )),': number of all potential colors'
print len(set(c for f,c,n in li if n!=0)),': number of all effective colors'
print len( [c for f,c,n in li if f=='strawberry']),': number of potential colors of strawberry'
print len( [c for f,c,n in li if f=='strawberry' and n!=0]),': number of effective colors of strawberry'
print

# sorting li by name of fruit
print sorted(li),'  sorted li by name of fruit'
print

# sorting li by number 
print sorted(li, key = itemgetter(2)),'  sorted li by number'
print

# sorting li first by name of color and secondly by name of fruit
print sorted(li, key = itemgetter(1,0)),'  sorted li first by name of color and secondly by name of fruit'
print

résultat

set(['blue', 'brown', 'gold', 'purple', 'yellow', 'pink', 'green', 'orange', 'silver']) : all potential colors
set(['blue', 'brown', 'gold', 'purple', 'yellow', 'pink', 'green', 'orange']) : all effective colors
['blue', 'yellow', 'brown'] : all potential colors of bananas
['blue', 'yellow', 'brown'] : all effective colors of bananas

set(['strawberry', 'chesnut', 'pear', 'banana', 'apple']) : all potential fruits
set(['strawberry', 'pear', 'banana', 'apple']) : all effective fruits
['banana', 'pear', 'strawberry', 'apple', 'chesnut'] : all potential fruits being yellow
['banana', 'pear', 'strawberry', 'apple'] : all effective fruits being yellow

9 : number of all potential colors
8 : number of all effective colors
6 : number of potential colors of strawberry
5 : number of effective colors of strawberry

[('apple', 'blue', 21), ('apple', 'gold', 3), ('apple', 'green', 12), ('apple', 'pink', 9), ('apple', 'purple', 7), ('apple', 'silver', 0), ('apple', 'yellow', 9), ('banana', 'blue', 24), ('banana', 'brown', 14), ('banana', 'yellow', 13), ('chesnut', 'yellow', 0), ('pear', 'blue', 51), ('pear', 'brown', 5), ('pear', 'gold', 66), ('pear', 'yellow', 10), ('strawberry', 'blue', 16), ('strawberry', 'gold', 0), ('strawberry', 'green', 4), ('strawberry', 'orange', 27), ('strawberry', 'pink', 8), ('strawberry', 'yellow', 31)]   sorted li by name of fruit

[('apple', 'silver', 0), ('strawberry', 'gold', 0), ('chesnut', 'yellow', 0), ('apple', 'gold', 3), ('strawberry', 'green', 4), ('pear', 'brown', 5), ('apple', 'purple', 7), ('strawberry', 'pink', 8), ('apple', 'pink', 9), ('apple', 'yellow', 9), ('pear', 'yellow', 10), ('apple', 'green', 12), ('banana', 'yellow', 13), ('banana', 'brown', 14), ('strawberry', 'blue', 16), ('apple', 'blue', 21), ('banana', 'blue', 24), ('strawberry', 'orange', 27), ('strawberry', 'yellow', 31), ('pear', 'blue', 51), ('pear', 'gold', 66)]   sorted li by number

[('apple', 'blue', 21), ('banana', 'blue', 24), ('pear', 'blue', 51), ('strawberry', 'blue', 16), ('banana', 'brown', 14), ('pear', 'brown', 5), ('apple', 'gold', 3), ('pear', 'gold', 66), ('strawberry', 'gold', 0), ('apple', 'green', 12), ('strawberry', 'green', 4), ('strawberry', 'orange', 27), ('apple', 'pink', 9), ('strawberry', 'pink', 8), ('apple', 'purple', 7), ('apple', 'silver', 0), ('apple', 'yellow', 9), ('banana', 'yellow', 13), ('chesnut', 'yellow', 0), ('pear', 'yellow', 10), ('strawberry', 'yellow', 31)]   sorted li first by name of color and secondly by name of fruit
eyquem
la source
1
Salut, j'aime votre solution mais elle ne résout pas les problèmes de complexité des opérations. tous les types de recherche sont liner (O (n)) dans la taille de la liste. alors qu'il serait logique que l'OP veuille que certaines actions soient plus rapides que d'autres (par exemple, obtenir le nombre de bananes jaunes serait quelque chose que je m'attendrais à être possible en O (1).
epeleg
13

Un dictionnaire n'est probablement pas ce que vous devriez utiliser dans ce cas. Une bibliothèque plus complète serait une meilleure alternative. Probablement une vraie base de données. Le plus simple serait sqlite . Vous pouvez garder le tout en mémoire en passant la chaîne ': memory:' au lieu d'un nom de fichier.

Si vous souhaitez continuer sur ce chemin, vous pouvez le faire avec les attributs supplémentaires de la clé ou de la valeur. Cependant, un dictionnaire ne peut pas être la clé d'un autre dictionnaire, mais un tuple le peut. Les documents expliquent ce qui est autorisé. Il doit s'agir d'un objet immuable, qui comprend des chaînes, des nombres et des tuples qui ne contiennent que des chaînes et des nombres (et plus de tuples ne contenant que ces types de manière récursive ...).

Vous pouvez faire votre premier exemple avec d = {('apple', 'red') : 4}, mais il sera très difficile de rechercher ce que vous voulez. Vous auriez besoin de faire quelque chose comme ceci:

#find all apples
apples = [d[key] for key in d.keys() if key[0] == 'apple']

#find all red items
red = [d[key] for key in d.keys() if key[1] == 'red']

#the red apple
redapples = d[('apple', 'red')]
chmullig
la source
4
Je n'ai pas, et je ne voudrais pas, voter contre cette réponse, car à plus grande échelle, les bases de données sont (évidemment!) La meilleure solution. Mais mon instinct est qu'une base de données est excessive pour les besoins du PO; peut-être que cela explique le vote négatif?
senderle
4

Avec des clés comme tuples, il vous suffit de filtrer les clés avec un deuxième composant donné et de le trier:

blue_fruit = sorted([k for k in data.keys() if k[1] == 'blue'])
for k in blue_fruit:
  print k[0], data[k] # prints 'banana 24', etc

Le tri fonctionne parce que les tuples ont un ordre naturel si leurs composants ont un ordre naturel.

Les clés étant des objets à part entière, il suffit de filtrer par k.color == 'blue' .

Vous ne pouvez pas vraiment utiliser les dictionnaires comme clés, mais vous pouvez créer une classe la plus simple comme class Foo(object): passet y ajouter des attributs à la volée:

k = Foo()
k.color = 'blue'

Ces instances peuvent servir de clés dict, mais attention à leur mutabilité!

9 000
la source
3

Vous pourriez avoir un dictionnaire où les entrées sont une liste d'autres dictionnaires:

fruit_dict = dict()
fruit_dict['banana'] = [{'yellow': 24}]
fruit_dict['apple'] = [{'red': 12}, {'green': 14}]
print fruit_dict

Production:

{'banane': [{'yellow': 24}], 'apple': [{'red': 12}, {'green': 14}]}

Edit: Comme l'a souligné eumiro, vous pouvez utiliser un dictionnaire de dictionnaires:

fruit_dict = dict()
fruit_dict['banana'] = {'yellow': 24}
fruit_dict['apple'] = {'red': 12, 'green': 14}
print fruit_dict

Production:

{'banana': {'yellow': 24}, 'apple': {'green': 14, 'red': 12}}

GreenMatt
la source
2
Dictionnaire de la liste des dictionnaires? Peut-être qu'un dictionnaire de dictionnaire suffirait-il?
eumiro le
@eumiro: Merci, vous avez raison, et c'était mon idée originale. Cependant, je l'ai transformé en un dict de listes de dictés tout en codant l'exemple original. J'ai ajouté un exemple de dictée.
GreenMatt
Les dictionnaires imbriqués ont tendance à prêter à confusion. S'il vous plaît voir ma réponse
Cuga
@Cuga: Je suis d'accord que les dictionnaires, etc. peuvent être déroutants. Je fournis juste un exemple illustratif pour répondre à la question de @ Nico telle que posée.
GreenMatt
Je m'excuse: je ne voulais pas dire que votre solution est erronée; cela fonctionne clairement et dans certaines situations, il pourrait être idéal. Je voulais partager mon point de vue sur la situation.
Cuga
2

Ce type de données est efficacement extrait d'une structure de données de type Trie. Il permet également un tri rapide. L'efficacité de la mémoire n'est peut-être pas si bonne.

Un trie traditionnel stocke chaque lettre d'un mot comme un nœud dans l'arbre. Mais dans votre cas, votre «alphabet» est différent. Vous stockez des chaînes au lieu de caractères.

cela pourrait ressembler à ceci:

root:                Root
                     /|\
                    / | \
                   /  |  \     
fruit:       Banana Apple Strawberry
              / |      |     \
             /  |      |      \
color:     Blue Yellow Green  Blue
            /   |       |       \
           /    |       |        \
end:      24   100      12        0

voir ce lien: trie en python

Scott Morken
la source
2

Vous souhaitez utiliser deux touches indépendamment, vous avez donc deux choix:

  1. Stockez les données de manière redondante avec deux dictionnaires comme {'banana' : {'blue' : 4, ...}, .... }et {'blue': {'banana':4, ...} ...}. Ensuite, la recherche et le tri sont faciles, mais vous devez vous assurer de modifier les dicts ensemble.

  2. Stockez-le juste un dict, puis écrivez des fonctions qui les itèrent par exemple:

    d = {'banana' : {'blue' : 4, 'yellow':6}, 'apple':{'red':1} }
    
    blueFruit = [(fruit,d[fruit]['blue']) if d[fruit].has_key('blue') for fruit in d.keys()]
bande passante élevée
la source
Je ne comprends pas pourquoi le code de ma réponse n'apparaît pas dans le bon format. J'ai essayé de modifier et de marquer les deux dernières lignes en tant que code, mais cela ne fonctionne pas!
highBandWidth
1
vous avez créé une liste numérotée et l'analyseur interprète le code (en retrait de 4 espaces) comme une continuation du deuxième élément de cette liste. Mettez en retrait le code de 4 autres espaces pour un total de 8, et l'analyseur reconnaîtra le code comme code et le formatera correctement.
senderle