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, можливо, він будує його.
Проф. Фолкен

Просто хотілося додати, що словник не є доступним для використання, тому другий синтаксис, у якому ви не можете запитати, неможливо, оскільки {'ruit ':' banana ',' color ':' blue '}, що є словником, не може використовуватися як ключ для іншого словника. це призведе до типу TypeError: unhashable type: 'dict ".
epeleg

Відповіді:


147

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

Зауважте, що ви не можете використовувати дикти як клавіші в дикті, не роблячи додаткових робіт, тому це не дуже вдале рішення.

З цього приводу слід також врахувати nametuple () . Таким чином ви могли це зробити:

>>> 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 = {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 Ви написали як коментар до іншої відповіді "Але я відчуваю, що база даних надмірна для потреб ОП;"; Тому ви віддаєте перевагу створенню підкласу namedtuple. Але які ще є випадки класів, якщо не мікробази із власними інструментами для обробки своїх даних?
ейкуем

Чи міг би я з цих вилучити підлісти name='banana'?
Ніко Шльомер

2
Як зазначав chmullig, вам доведеться фільтрувати клавіші, тобто bananas = filter(lambda fruit: fruit.name=='banana', fruits)або bananas = [fruit for fruit in fruits if fruit.name=='banana']. Це один із способів, яким вкладені дикти є потенційно ефективнішими; все зводиться до способів використання даних.
senderle

чи не додавання більшої клавіші в названий кортеж полегшить ситуацію? Я б сказав, додай новий атрибут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

17

База даних, диктант диктів, словник списку словників, названий кортеж (це підклас), sqlite, надмірність ... Я не повірив своїм очам. Що ще ?

"Цілком може бути, що словники з кортежами як ключами не є правильним способом вирішення цієї ситуації".

"я відчуваю, що база даних є надмірною для потреб ОП;"

Ага! я думав

Тож, на мою думку, переліку кортежів достатньо:

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

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

4
Я не хотів і не хотів би відповісти на цю відповідь, оскільки на великих масштабах бази даних (очевидно!) Найкращий шлях. Але я відчуваю, що база даних є надмірною для потреб ОП; можливо, це пояснює сутінг?
senderle

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

Ви не можете використовувати дикти як клавіші, але ви можете створити найпростіший клас на кшталт class Foo(object): passі додати до нього будь-які атрибути на ходу:

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

Ці екземпляри можуть слугувати клавішами, але остерігайтеся їх змінності!


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

Вихід:

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


2
Словник списку словників? Може, словника словника вистачить?
eumiro

@eumiro: Спасибі, ти маєш рацію, і це була моя оригінальна ідея. Однак я перетворив це на набір списків диктів, кодуючи початковий приклад. Я додав приклад диктату.
GreenMatt

Вкладені словники, як правило, заплутані. Будь ласка, дивіться мою відповідь
Cuga

@Cuga: Я погоджуюся, що дикти диктів тощо можуть стати заплутаними. Я лише надаю наочний приклад, щоб відповісти на запитання @ Ніко, як його задали.
GreenMatt

Прошу вибачення: я не мав на увазі, що ваше рішення неправильне; це чітко працює, і в деяких ситуаціях це може бути ідеальним. Я хотів поділитися своєю ситуацією.
Cuga

2

Цей тип даних ефективно витягується із структури даних, схожих на Trie. Це також дозволяє швидко сортувати. Ефективність пам'яті, можливо, не така вже й велика.

Традиційна трійка зберігає кожну букву слова як вузол на дереві. Але у вашому випадку ваш "алфавіт" інший. Ви зберігаєте рядки замість символів.

це може виглядати приблизно так:

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

дивіться за цим посиланням: trie в python


2

Ви хочете використовувати дві клавіші самостійно, тому у вас є два варіанти:

  1. Зберігайте дані надмірно двома диктами як {'banana' : {'blue' : 4, ...}, .... }і{'blue': {'banana':4, ...} ...} . Тоді пошук і сортування простий, але ви повинні переконатися, що ви змінюєте дикти разом.

  2. Зберігайте його лише в одному диктаті, а потім записуйте функції, які повторюються над ними, наприклад:

    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

1
ви створили нумерований список, і аналізатор інтерпретує код (з відступом 4 пробілів) як продовження другого елемента цього списку. Введіть у коді ще 4 пробіли на загальну кількість 8, і аналізатор розпізнає код як код і правильно його відформатує.
senderle
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.