Перевірте, чи вказаний ключ вже існує в словнику, і збільшуйте його


294

Як давати словник, як я можу дізнатись, чи для даного ключа у словнику вже встановлено значення, яке не має значення None?

Тобто я хочу це зробити:

my_dict = {}

if (my_dict[key] != None):
  my_dict[key] = 1
else:
  my_dict[key] += 1

Тобто, я хочу збільшити значення, якщо воно вже є, або встановити його на 1 в іншому випадку.


11
Маленький код nitpick: код встановлює my_dict [ключ] до 1, якщо там вже щось є, і збільшує його, якщо його немає. Я думаю, ти хочеш ==, а не! =.
QuantumFool

Відповіді:


331

Ви шукаєте collections.defaultdict(доступно для Python 2.5+). Це

from collections import defaultdict

my_dict = defaultdict(int)
my_dict[key] += 1

зробить те, що ти хочеш.

Для звичайних Python dicts, якщо для даного ключа немає значення, ви не отримаєте Noneпід час доступу до dict - а KeyErrorбуде піднято. Тож якщо ви хочете використовувати звичайний dict, замість свого коду ви б використовували

if key in my_dict:
    my_dict[key] += 1
else:
    my_dict[key] = 1

8
Згідно з його прикладом, цього має бути достатньо, щоб встановити "засудження за замовчуванням (лямбда: 0)" і пропустити весь пункт "якщо".
Діестан

Це працює, але заплутує ключі та значення (що робить його чимось дивним). 'some_value' має бути 'some_key'
mikemaccana

@nailer: виправлено, дякую. Я спочатку використовував "some_value", оскільки це ім'я змінної у питанні, але я згоден, що зараз зрозуміліше.
dF.

20
... або для звичайних dicts ви можете зробити my_dict[key] = my_dict.get(key, 0) + 1.
minmaxavg

Як поширити це на вкладені словники? dict [key1] [key2] + = 1?
Пабло Руїс Руїс

300

Я вважаю за краще це робити в одному рядку коду.

my_dict = {}

my_dict [some_key] = my_dict.get (some_key, 0) + 1

Словники мають функцію get, яка приймає два параметри - потрібний ключ та значення за замовчуванням, якщо воно не існує. Я віддаю перевагу цьому методу перед засудженням, оскільки ви хочете опрацювати лише той випадок, коли ключ не існує в цьому рядку коду, не скрізь.


1
@AndrewWilkinson моя погана. Я не прочитав вашої відповіді так ретельно, як я повинен був.
масаєр

59

Мені особисто подобається користуватися setdefault()

my_dict = {}

my_dict.setdefault(some_key, 0)
my_dict[some_key] += 1

setdefaultдивним. Це не змінює значення, якщо таке вже встановлено some_key. Наприклад, d={1:2}; d.setdefault(1, 0)не порушує значення d[1].
wsaleem

49

Для цього вам потрібна key in dictідіома.

if key in my_dict and not (my_dict[key] is None):
  # do something
else:
  # do something else

Однак, ймовірно, варто подумати про використання defaultdict(як запропонував dF).


1
Зверніть увагу, що принаймні 2,6 has_key () було видалено на користь ключа в d. Я думаю, що так було і в 2.5.
Девід Локк

Зауважте, що можна писати my_dict[key] is not None, що зрозуміліше (принаймні ІМХО)
brandizzi

@brandizzi - погодьтесь,if key in my_dict and my_dict[key]:
Роб Грант

18

Щоб відповісти на запитання " як я можу дізнатись, чи вказаний індекс у цьому диктаті вже встановлений на значення , яке не має значення ", я вважаю за краще таке:

try:
  nonNone = my_dict[key] is not None
except KeyError:
  nonNone = False

Це відповідає вже застосованій концепції EAFP (простіше просити пробачення, ніж дозволу). Він також уникає пошуку дублікатів ключів у словнику, як це було б у key in my_dict and my_dict[key] is not Noneтому, що цікаво, якщо пошук дорого коштує.

Для фактичної проблеми, яку ви поставили, тобто збільшення інта, якщо вона існує, або встановлення її до значення за замовчуванням, я також рекомендую

my_dict[key] = my_dict.get(key, default) + 1

як у відповіді Ендрю Вілкінсона.

Є третє рішення, якщо ви зберігаєте змінні об’єкти у своєму словнику. Поширений приклад для цього - мультимапа , де ви зберігаєте список елементів для своїх ключів. У цьому випадку ви можете використовувати:

my_dict.setdefault(key, []).append(item)

Якщо значення для ключа у словнику не існує, метод setdefault встановить його другим параметром setdefault. Він поводиться так само, як і стандартний my_dict [key], повертаючи значення для ключа (яке може бути нещодавно встановленим значенням).


що виглядає справді пітонічно (для стороннього, як я), що будь-яке запитання має щонайменше 3 достовірні відповіді :)
davka

@davka: Ну, три випадки використання майже однакові, але різні: а) з’ясуйте, чи є в словнику елемент, що не входить None; b) виберіть значення зі словника або використовуйте за замовчуванням, якщо значення не існує в) отримати значення зі словника та зберегти за замовчуванням, якщо значення ще не існує.
другий.

Я знаю :) це не критика, мене просто розвеселив цей факт
davka

У коментарі до відповіді @ ryeguy Стюарт Вудвард припускає, що "накладні витрати на обробку винятків у мовах завжди на порядок більше, ніж пошук у хеш-таблиці, який визначає, чи існує елемент у словнику чи ні", тоді як ви говорите "Це також уникає пошуку дублікатів ключів у словнику ... якщо пошук дорого коштує "- чи має хто-небудь вимірювання того, де обробка винятків відбувається швидше чи повільніше, ніж пошук подвійного ключа?
Майкл Фірт

1
@MichaelFirth Я побіжно шукав винятки Python накладні: stackoverflow.com/questions/2522005/… це повільніше, але не дуже багато. Майте на увазі, що концепція вищого рівня кидання винятків обробляється дуже різно на різних мовах, і ви не можете узагальнити плюси і мінуси. Отже, хоча "Винятки мають накладні витрати в 10 разів" можуть бути правильними для Java, це не для Python (або Swift чи інших).
другий.

13

Погодився з cgoldberg. Як я це роблю:

try:
    dict[key] += 1
except KeyError:
    dict[key] = 1

Тому або робіть це як вище, або використовуйте диктант за замовчуванням, як запропонували інші. Не використовуйте, якщо заяви. Це не піфонічне.


8
Як це, якщо висловлювання не піфонічні?
Адам Паркін

2
Я думаю, що це один із випадків, коли EAFP Python - це не найкращий спосіб. Ваш приклад вище має дублюючи код; що якщо одного дня ми хочемо +=2чи -=1? Ви повинні пам'ятати, щоб змінити обидва рядки. Зараз це може здатися дрібницею, але це такі дурні маленькі «тривіальні» помилки, які можуть повернутися, щоб вкусити вас.
Cam Jackson

3
Це виглядає приємно і працює чудово, але я зазвичай уникаю цього робити, тому що я вважав, що накладні витрати в обробці Exception на мовах завжди на порядок більше, ніж пошук у хеш-таблиці, який визначає, чи існує елемент у словнику чи ні.
Стюарт Вудвард

11

Як видно з безлічі відповідей, існує кілька рішень. Один екземпляр LBYL (подивіться, перш ніж стрибати) ще не згаданий, метод has_key ():

my_dict = {}

def add (key):
    if my_dict.has_key(key):
        my_dict[key] += 1
    else:
        my_dict[key] = 1

if __name__ == '__main__':
    add("foo")
    add("bar")
    add("foo")
    print my_dict

6
has_key () повільніше, ніж оператор "in", і менш читабельний.
Абган

9
... і він був устарений у Python 2.6 та видалений у Python 3.
Тім Піткер

7

Те, як ви намагаєтеся це зробити, називається LBYL (дивіться, перш ніж стрибати), оскільки ви перевіряєте умови, перш ніж намагатися збільшити свою вартість.

Інший підхід називається EAFP (простіше просити прощення, ніж дозволу). У такому випадку ви просто спробуйте виконати операцію (збільшити значення). Якщо це не вдалося, ви виберете виняток і встановите значення 1. Це трохи пітонічний спосіб зробити це (IMO).

http://mail.python.org/pipermail/python-list/2003-May/205182.html


5

Трохи пізно, але це має спрацювати.

my_dict = {}
my_dict[key] = my_dict[key] + 1 if key in my_dict else 1

Нічого собі, як програміст Java, це досить божевільна конструкція. Це схоже на дивно упорядкований термінальний оператор?
forresthopkinsa

5

Це не відповідає безпосередньо на питання, але мені здається, ви можете захотіти функціональні можливості колекцій .

from collections import Counter

to_count = ["foo", "foo", "bar", "baz", "foo", "bar"]

count = Counter(to_count)

print(count)

print("acts just like the desired dictionary:")
print("bar occurs {} times".format(count["bar"]))

print("any item that does not occur in the list is set to 0:")
print("dog occurs {} times".format(count["dog"]))

print("can iterate over items from most frequent to least:")
for item, times in count.most_common():
    print("{} occurs {} times".format(item, times))

Це призводить до виходу

Counter({'foo': 3, 'bar': 2, 'baz': 1})
acts just like the desired dictionary:
bar occurs 2 times
any item that does not occur in the list is set to 0:
dog occurs 0 times
can iterate over items from most frequent to least:
foo occurs 3 times
bar occurs 2 times
baz occurs 1 times

Лічильник працює так само, як і defaultdict(int)з деякою додатковою функціональністю, тому він би працював ідеально під час роботи виключно з цілими числами, але ви не показуєте жодної відповідної поведінки.
Tadhg McDonald-Jensen

4

Ось один лайнер, який я нещодавно придумав для вирішення цієї проблеми. Він заснований на методі словника setdefault :

my_dict = {}
my_dict[key] = my_dict.setdefault(key, 0) + 1

0

Я шукав його, не знайшов його в Інтернеті, потім спробував удачу з Try / Error та знайшов його

my_dict = {}

if my_dict.__contains__(some_key):
  my_dict[some_key] += 1
else:
  my_dict[some_key] = 1

1
Ви не повинні використовувати __contains__у виробничому коді. btw. __contains__те саме, що використовувати is.
користувач1767754

1
my_dict.__contains__(some_key)еквівалентно some_key in my_dict, перевантаження для inоператора немаєis
Tadhg McDonald-Jensen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.