Python: кортежи / словари как ключи, выбор, сортировка

104

Предположим, у меня есть много фруктов разного цвета, например, 24 синих банана, 12 зеленых яблок, 0 голубых ягод клубники и так далее. Я хотел бы организовать их в структуре данных в Python, которая позволяет легко выбирать и сортировать. Моя идея заключалась в том, чтобы поместить их в словарь с кортежами в качестве ключей, например,

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

или даже словари, например,

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

Я хотел бы получить список всех синих фруктов или, например, бананов всех цветов, или отсортировать этот словарь по названию фруктов. Есть ли способы сделать это чисто?

Вполне возможно, что словари с кортежами в качестве ключей - не лучший способ справиться с этой ситуацией.

Все предложения приветствуются!

Нико Шлёмер
источник
26
Похоже, вам нужна база данных ...
Адам Розенфилд
4
Лучше определить класс для моделирования этих данных, чем пытаться скоординировать разные коллекции этих значений
Cuga
2
@AdamRosenfield, может быть, он его строит.
Проф. Фалькен,
Просто хотел добавить, что словарь не хешируется, поэтому второй синтаксис, о котором вы спрашиваете, невозможен, потому что {'fruit': 'banana', 'color': 'blue'}, который является словарем, не может использоваться в качестве ключа для другого словаря. это вызовет TypeError: unhashable type: 'dict'.
epeleg 02

Ответы:

147

Лично мне нравится в Python комбинация кортеж-дикт. То, что у вас есть, фактически представляет собой 2d-массив (где x = имя плода и y = цвет), и я обычно сторонник dict кортежей для реализации 2-мерных массивов, по крайней мере, когда что-то вроде numpyили база данных не более подходящие . Короче говоря, я думаю, у вас есть хороший подход.

Обратите внимание, что вы не можете использовать dicts в качестве ключей в dict без дополнительной работы, так что это не очень хорошее решение.

Тем не менее, вы также должны рассмотреть namedtuple () . Таким образом вы могли сделать это:

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

Теперь вы можете использовать свой fruitcount dict:

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

Другие уловки:

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

Повторяя chmullig, чтобы получить список всех цветов одного фрукта, вам нужно будет отфильтровать ключи, т.е.

bananas = [fruit for fruit in fruits if fruit.name=='banana']
отправитель
источник
#senderle Вы написали как комментарий к другому ответу: «Но мне кажется, что база данных слишком велика для нужд OP;»; Итак, вы предпочитаете создавать подкласс namedtuple. Но что еще представляют собой экземпляры классов, как не микробазы данных со своими собственными инструментами для обработки своих данных?
eyquem 02
Могу ли я извлечь из этих списков подсписки name='banana'?
Нико Шлёмер 03
2
Как указал чмуллиг, вам придется фильтровать ключи, т.е. bananas = filter(lambda fruit: fruit.name=='banana', fruits)или bananas = [fruit for fruit in fruits if fruit.name=='banana']. Это один из способов повышения эффективности вложенных dicts; все сводится к тому, как вы планируете использовать данные.
senderle 03
не упростит ли добавление еще одного ключа в названный кортеж? Я бы сказал добавить новый атрибутcount
openrijal
18

Лучшим вариантом будет создание простой структуры данных для моделирования того, что у вас есть. Затем вы можете сохранить эти объекты в простом списке и сортировать / извлекать их любым удобным вам способом.

В этом случае я бы использовал следующий класс:

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)

Затем вы можете просто создать экземпляры «Fruit» и добавить их в список, как показано следующим образом:

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

Простой список fruitsбудет намного проще, менее запутанным и поддерживается лучше.

Некоторые примеры использования:

Все приведенные ниже результаты являются результатом выполнения данного фрагмента кода, за которым следует:

for fruit in fruits:
    print fruit

Несортированный список:

Отображает:

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

В алфавитном порядке по названию:

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

Отображает:

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

По количеству:

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

Отображает:

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

Где цвет == красный:

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

Отображает:

Name: apple, Color: red, Quantity: 12
Cuga
источник
17

База данных, dict dicts, словарь списка словарей, именованный кортеж (это подкласс), sqlite, избыточность ... Я не поверил своим глазам. Что еще ?

«Вполне возможно, что словари с кортежами в качестве ключей - не лучший способ справиться с этой ситуацией».

"интуитивно мне кажется, что базы данных слишком много для нужд OP;"

Да! я думал

Так что, на мой взгляд, списка кортежей достаточно:

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

результат

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
источник
1
Привет, мне нравится ваше решение, но оно не решает проблемы сложности работы. все типы поиска являются лайнером (O (n)) по размеру списка. в то время как было бы разумно, чтобы ОП хотел, чтобы некоторые действия выполнялись быстрее, чем другие (например, получение количества желтых бананов было бы тем, что я ожидал бы, возможно, в O (1).
epeleg
13

Словарь, вероятно, не то, что вам следует использовать в этом случае. Более полнофункциональная библиотека была бы лучшей альтернативой. Наверное, настоящая база данных. Самый простой - sqlite . Вы можете сохранить все это в памяти, передав строку ': memory:' вместо имени файла.

Если вы действительно хотите продолжить этот путь, вы можете сделать это с помощью дополнительных атрибутов в ключе или значении. Однако словарь не может быть ключом к другому словарю, а кортеж может. Документы объясняется, что допустимо. Это должен быть неизменяемый объект, который включает строки, числа и кортежи, содержащие только строки и числа (и другие кортежи, содержащие только эти типы рекурсивно ...).

Вы можете сделать свой первый пример d = {('apple', 'red') : 4}, но будет очень сложно запросить то, что вы хотите. Вам нужно будет сделать что-то вроде этого:

#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
источник
4
Я не стал и не стал бы отрицать этот ответ, потому что в больших масштабах базы данных (очевидно!) - лучший способ. Но я чувствую, что базы данных слишком много для нужд OP; возможно, это объясняет отрицательный голос?
senderle 02
4

С ключами в виде кортежей вы просто фильтруете ключи с заданным вторым компонентом и сортируете его:

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

Сортировка работает, потому что кортежи имеют естественный порядок, если их компоненты имеют естественный порядок.

С ключами как достаточно полноценными объектами вы просто фильтруете по k.color == 'blue' .

Вы не можете использовать dicts в качестве ключей, но вы можете создать простейший класс, например, class Foo(object): passи добавлять к нему любые атрибуты на лету:

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

Эти экземпляры могут служить в качестве ключей dict, но будьте осторожны с их изменчивостью!

9000
источник
3

У вас может быть словарь, в котором записи представляют собой список других словарей:

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

Вывод:

{'банан': [{'желтый': 24}], 'яблоко': [{'красный': 12}, {'зеленый': 14}]}

Изменить: как указал Эумиро, вы можете использовать словарь словарей:

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

Вывод:

{'банан': {'желтый': 24}, 'яблоко': {'зеленый': 14, 'красный': 12}}

GreenMatt
источник
2
Словарь списка словарей? Может хватит словаря словаря?
eumiro 02
@eumiro: Спасибо, ты прав, это была моя первоначальная идея. Однако я превратил его в диктат списков диктовок, пока кодировал исходный пример. Я добавил пример dict of dicts.
GreenMatt 02
Вложенные словари могут сбивать с толку. Пожалуйста, посмотрите мой ответ
Cuga 02
@Cuga: Я согласен, что диктовки и т. Д. Могут запутать. Я просто привожу иллюстративный пример, чтобы ответить на заданный вопрос @Nico.
GreenMatt 02
Прошу прощения: я не имел в виду, что ваше решение неверно; он явно работает и в некоторых ситуациях может быть идеальным. Я хотел поделиться своим мнением о ситуации.
Cuga 02
2

Этот тип данных эффективно извлекается из структуры данных, подобной Trie. Это также позволяет выполнять быструю сортировку. Однако эффективность памяти может быть не такой уж большой.

Традиционное дерево хранит каждую букву слова как узел в дереве. Но в вашем случае ваш «алфавит» другой. Вы храните строки вместо символов.

это может выглядеть примерно так:

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

см. эту ссылку: trie in python

Скотт Моркен
источник
2

Вы хотите использовать два ключа независимо друг от друга, поэтому у вас есть два варианта:

  1. Сохраните данные с дублированием с помощью двух dicts как {'banana' : {'blue' : 4, ...}, .... }и {'blue': {'banana':4, ...} ...}. Тогда поиск и сортировка станут простыми, но вы должны убедиться, что вы изменяете словари вместе.

  2. Сохраните только один dict, а затем напишите функции, которые перебирают их, например:

    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 04
1
вы создали нумерованный список, и синтаксический анализатор интерпретирует код (с отступом 4 пробела) как продолжение второго элемента этого списка. Сделайте отступ кода еще на 4 пробела, всего 8, и парсер распознает код как код и правильно его отформатирует.
senderle