более питонический способ выполнения вычислений по словарю

#python #dictionary #math

#python #словарь #математика

Вопрос:

Я пытаюсь использовать словарь для разделения его элементов, чтобы получить значение. Если это значение соответствует моим критериям, я хочу остановиться. Мне удалось заставить это работать, но я чувствую, что должен быть более короткий путь. Если у вас есть какие-либо предложения или советы, я был бы признателен.

 dict1 = {1:123, 2:220, 3:290, 4:300, 5:329} 
prev = 1 
prev_key = 1 
for key,value in dict1.items():
    z = prev/value
    if z >= 0.95 and z<= 1.05:
        print("key is",prev_key)
        break
    prev = value
    prev_key = key
  

В этом случае ответом будет prev_key = 3. Заранее спасибо 🙂

Комментарии:

1. Запуск вашего кода никогда не входит в предложение if.

2. Обратите внимание, что в зависимости от версии python словарь может не иметь уникального порядка итераций.

3. @Darina извините, да, я обновил его до 3: 290. мой плохой

4. @tobias_k делает хороший вывод. Первым шагом должно быть преобразование словаря в два списка и, возможно, сортировка.

5. Я работаю на python 3.6 Чего я добиваюсь, преобразуя его в списки?

Ответ №1:

Во-первых, как упоминалось в комментариях, словари не имеют надежного порядка итераций в старых версиях python. Я бы сделал следующее

 # convert dictionary to list of key/value tuple pairs
l = [(k, v) for (k, v) in dict1.items())

# iterate over the list of tuples, using zip to get the previous and current value at once
for tup1, tup2 in zip(l, l[1:]):
    z = t1[1] / t2[1]
    if 0.95 <= z <= 1.05:
        print('key is', t1[0])
        break
  

Словари упорядочены по вставке в Python 3.6, так что все должно быть в порядке. Но если вам нужно сначала отсортировать список по значению ключа (выглядит так в вашем MWE), вы можете сделать l = sorted(l, key = lambda x: x[0]) — это сортирует список по первому значению кортежей. Если вы полагаетесь на порядок ключей словаря для своего кода, вам следует использовать OrderedDict .

Комментарии:

1. Забыл об prev инициализации — это может быть достигнуто добавлением l = [(1,1)] к списку.

Ответ №2:

Вы могли бы сделать его немного короче (и, ИМХО, более читаемым), (а) используя цепочку сравнения (что также означает, что вам не нужно z ) и (б) выполняя несколько назначений объектов, которые принадлежат друг другу в одной строке:

 dict1 = {1:123, 2:220, 3:290, 4:300, 5:329} 
prev = prev_key = 1 
for key,value in dict1.items():
    if 0.95 <= prev/value <= 1.05:
        print("key is", prev_key)
        break
    prev, prev_key = value, key
  

Для более агрессивного сокращения вы могли бы использовать next вместо цикла и прерывания и использовать zip(*[iter(...)]*2) рецепт для итерации пар значений; обратите внимание, однако, что это плохо сочетается с инициализацией prev с 1 помощью ; вы бы предпочли иметь первую запись 1: 1 в самом dict .

 print("key is", next(prev_key for (prev_key, prev), (key, value) in zip(*[iter(dict1.items())]*2)
                              if 0.95 <= prev/value <= 1.05))
  

Однако, на мой взгляд, первая (более длинная) версия намного более удобочитаема.

Или разверните dict1.values() в список вместе с (1, 1) , а затем zip с самим собой, смещенным на единицу. Вместе с более короткими именами переменных это тоже вполне читаемо.

 lst = [(1, 1), *dict1.items()]
print("key is", next(k1 for (k1, v1), (k2, v2) in zip(lst, lst[1:])
                        if 0.95 <= v1/v2 <= 1.05))
  

Беспокоить версии с next вызовет исключение, если такой элемент не найден. вы можете, например, использовать next((...), default=None) для противодействия этому.