Python: Tuplas / diccionarios como claves, seleccionar, ordenar

104

supongamos que tengo cantidades de frutas de diferentes colores, por ejemplo, 24 plátanos azules, 12 manzanas verdes, 0 fresas azules y así sucesivamente. Me gustaría organizarlos en una estructura de datos en Python que permita una fácil selección y clasificación. Mi idea era ponerlos en un diccionario con tuplas como claves, por ejemplo,

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

o incluso diccionarios, por ejemplo,

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

Me gustaría recuperar una lista de todas las frutas azules o plátanos de todos los colores, por ejemplo, o ordenar este diccionario por el nombre de la fruta. ¿Hay formas de hacer esto de una manera limpia?

Bien podría ser que los diccionarios con tuplas como claves no sean la forma adecuada de manejar esta situación.

¡Todas las sugerencias son bienvenidas!

Nico Schlömer
fuente
26
Parece que quieres una base de datos ...
Adam Rosenfield
4
Será mejor que defina una clsas para modelar estos datos, en lugar de intentar coordinar diferentes colecciones de estos valores
Cuga
2
@AdamRosenfield tal vez esté construyendo uno.
Prof. Falken
Solo quería agregar que un diccionario no es hash, por lo que la segunda sintaxis sobre la que pregunta no es posible porque {'fruit': 'banana', 'color': 'blue'}, que es un diccionario, no se puede usar como clave para otro diccionario. causaría un TypeError: unhashable type: 'dict'.
epeleg

Respuestas:

147

Personalmente, una de las cosas que me encanta de Python es la combinación tuple-dict. Lo que tiene aquí es efectivamente una matriz 2d (donde x = nombre de la fruta ey = color), y generalmente soy partidario del dictado de tuplas para implementar matrices 2d, al menos cuando algo como numpyo una base de datos no es más apropiado . En resumen, creo que tienes un buen enfoque.

Tenga en cuenta que no puede usar dictados como claves en un dictado sin hacer un trabajo adicional, por lo que no es una muy buena solución.

Dicho esto, también debería considerar namedtuple () . De esa forma podrías hacer esto:

>>> 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'

Ahora puedes usar tu dictado de fruitcount:

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

Otros trucos:

>>> 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')]

Haciendo eco de chmullig, para obtener una lista de todos los colores de una fruta, tendría que filtrar las claves, es decir

bananas = [fruit for fruit in fruits if fruit.name=='banana']
remitente
fuente
#senderle Escribiste como comentario a otra respuesta "Pero mi intuición es que una base de datos es excesiva para las necesidades del OP;"; Así que prefiere crear una subclase de tupla con nombre. Pero, ¿qué más son instancias de clases sino micro-bases de datos con sus propias herramientas para procesar sus datos?
eyquem
¿Podría extraer de esas sublistas con name='banana'?
Nico Schlömer
2
Como señaló chmullig, tendría que filtrar las claves, es decir, bananas = filter(lambda fruit: fruit.name=='banana', fruits)o bananas = [fruit for fruit in fruits if fruit.name=='banana']. Ésta es una forma en la que los dictados anidados son potencialmente más eficientes; todo se reduce a las formas en que planea usar los datos.
remitente
¿No facilitaría las cosas agregar una clave más en la tupla nombrada? Yo diría que agregue un nuevo atributocount
openrijal
18

Su mejor opción será crear una estructura de datos simple para modelar lo que tiene. Luego, puede almacenar estos objetos en una lista simple y ordenarlos / recuperarlos de la forma que desee.

Para este caso, usaría la siguiente clase:

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)

Luego, simplemente puede construir instancias de "Fruta" y agregarlas a una lista, como se muestra de la siguiente manera:

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

La lista simple fruitsserá mucho más fácil, menos confusa y mejor mantenida.

Algunos ejemplos de uso:

Todas las salidas a continuación son el resultado después de ejecutar el fragmento de código dado seguido de:

for fruit in fruits:
    print fruit

Lista sin clasificar:

Muestra:

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

Ordenado alfabéticamente por nombre:

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

Muestra:

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

Ordenados por cantidad:

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

Muestra:

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

Donde color == rojo:

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

Muestra:

Name: apple, Color: red, Quantity: 12
Cuga
fuente
17

Base de datos, dictado de dictados, diccionario de lista de diccionarios, tupla con nombre (es una subclase), sqlite, redundancia ... No creía lo que veía. Qué más ?

"Bien podría ser que los diccionarios con tuplas como claves no sean la forma adecuada de manejar esta situación".

"Mi intuición es que una base de datos es excesiva para las necesidades del OP";

¡Si! pensé

Entonces, en mi opinión, una lista de tuplas es suficiente:

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

resultado

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
fuente
1
Hola, me gusta su solución, sin embargo, no aborda problemas de complejidad operativa. todos los tipos de búsqueda son delineador (O (n)) en el tamaño de la lista. mientras que tendría sentido que el OP quisiera que algunas acciones fueran más rápidas que otras (por ejemplo, obtener el recuento de plátanos amarillos sería algo que esperaría que fuera posible en O (1).
epeleg
13

Un diccionario probablemente no sea lo que debería usar en este caso. Una biblioteca con más funciones sería una mejor alternativa. Probablemente una base de datos real. El más fácil sería sqlite . Puede mantener todo en la memoria pasando la cadena ': memoria:' en lugar de un nombre de archivo.

Si desea continuar por este camino, puede hacerlo con los atributos adicionales en la clave o el valor. Sin embargo, un diccionario no puede ser la clave de otro diccionario, pero una tupla sí. Los documentos explican lo que está permitido. Debe ser un objeto inmutable, que incluya cadenas, números y tuplas que contengan solo cadenas y números (y más tuplas que contengan solo esos tipos de forma recursiva ...).

Podría hacer su primer ejemplo con d = {('apple', 'red') : 4}, pero será muy difícil consultar lo que desea. Necesitarías hacer algo como esto:

#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
fuente
4
No voté en contra de esta respuesta, y no lo haría, porque a escalas más grandes, las bases de datos son (¡obviamente!) La mejor manera de hacerlo. Pero mi intuición es que una base de datos es excesiva para las necesidades del OP; ¿quizás eso explica el voto negativo?
remitente
4

Con claves como tuplas, simplemente filtre las claves con el segundo componente dado y ordénelas:

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

La clasificación funciona porque las tuplas tienen un orden natural si sus componentes tienen un orden natural.

Con las claves como objetos bastante completos, simplemente filtra por k.color == 'blue'.

Realmente no puede usar dictados como claves, pero puede crear una clase más simple como class Foo(object): passy agregarle cualquier atributo sobre la marcha:

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

Estas instancias pueden servir como claves de dictado, ¡pero cuidado con su mutabilidad!

9000
fuente
3

Podría tener un diccionario donde las entradas sean una lista de otros diccionarios:

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

Salida:

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

Editar: como señaló eumiro, podría usar un diccionario de diccionarios:

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

Salida:

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

GreenMatt
fuente
2
Diccionario de lista de diccionarios? ¿Quizás el diccionario de diccionario sería suficiente?
eumiro
@eumiro: Gracias, tienes razón, y esa fue mi idea original. Sin embargo, lo convertí en un dictado de listas de dictados mientras codificaba el ejemplo original. He añadido un ejemplo de dictado de dictados.
GreenMatt
Los diccionarios anidados tienden a ser confusos. Por favor vea mi respuesta
Cuga
@Cuga: Estoy de acuerdo en que los dictados de dictados, etc. pueden resultar confusos. Solo estoy proporcionando un ejemplo ilustrativo para responder a la pregunta de @ Nico como se le preguntó.
GreenMatt
Pido disculpas: no quise dar a entender que tu solución es incorrecta; claramente funciona y en algunas situaciones podría ser el ideal. Quería compartir mi opinión sobre la situación.
Cuga
2

Este tipo de datos se extrae de manera eficiente de una estructura de datos similar a Trie. También permite una clasificación rápida. Sin embargo, es posible que la eficiencia de la memoria no sea tan buena.

Un trie tradicional almacena cada letra de una palabra como un nodo en el árbol. Pero en su caso, su "alfabeto" es diferente. Estás almacenando cadenas en lugar de caracteres.

podría verse algo como esto:

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

ver este enlace: trie en python

Scott Morken
fuente
2

Desea utilizar dos teclas de forma independiente, por lo que tiene dos opciones:

  1. Almacene los datos de forma redundante con dos dictados como {'banana' : {'blue' : 4, ...}, .... }y {'blue': {'banana':4, ...} ...}. Luego, buscar y ordenar es fácil, pero debe asegurarse de modificar los dictados juntos.

  2. Guárdelo solo un dict y luego escriba funciones que iteren sobre ellos, por ejemplo:

    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()]
highBandWidth
fuente
No puedo entender por qué el código de mi respuesta no aparece en el formato correcto. Intenté editar y marcar las dos últimas líneas como código, ¡pero no funciona!
highBandWidth
1
ha creado una lista numerada y el analizador está interpretando el código (4 espacios con sangría) como una continuación del segundo elemento de esa lista. Sangra el código otros 4 espacios para un total de 8, y el analizador reconocerá el código como código y lo formateará correctamente.
remitente