Сортировка сложного словаря Python только по одному из его значений

Я пишу небольшой инструмент оптимизации для покупки марок на почте.

в процессе я использую словарь, который я сортирую в соответствии с тем, что я узнал в этом другом "известном" вопросе: сортировка словаря Python по значению

в моем случае мой словарь немного сложнее:
- один четыре элемента-объект tuple сделать ключ
- и еще!--5-->пять элементов-кортежей чтобы сделать данные.

источником этого словаря является итерация, где каждый успешный цикл добавляет одну строку:

MyDicco[A, B, C, D] = eval, post, number, types, over

это всего лишь крошечный пример тривиального запуска, пытаясь за 75 центов:
{
(0, 0, 1, 1): (22, 75, 2, 2, 0)
(0, 0, 0, 3): (31, 75, 3, 1, 0)
(0, 0, 2, 0): (2521, 100, 2, 1, 25)
(0, 1, 0, 0): (12511, 200, 1, 1, 125)
(1, 0, 0, 0): (27511, 350, 1, 1, 275)
}

пока я использую этот код для сортировки (is работает):

MyDiccoSorted = sorted(MyDicco.items(), key=operator.itemgetter(1))

Я сортирую по моей оценке, потому что сортировка - это все о том, чтобы принести лучшее решение на вершину. Оценка-оценка - это всего лишь одна данность из кортежа из пяти элементов (в примере это оценки: 22, 31, 2521, 12511 и 27511).

Как вы можете видеть в примере выше, это сортировка (как я хочу) по второму кортежу, индексу 1. Но мне пришлось (ворчливо) принести свой "оценочный балл" на переднюю часть моего второго кортежа. Очевидно, что код использует весь второй Кортеж для процесса сортировки, который является тяжелым и не нужен.


вот мой вопрос: Как я могу пожалуйста, разобраться точнее. Я не хочу Сортировать по всему второму кортежу моего словаря: я хочу точно нацелить первый элемент.
И в идеале я хотел бы поставить это значение возвращается в исходное положение, а именно быть последним элементом во втором кортеж - и все же Сортировать по нему.


Я прочитал и экспериментировал с синтаксисом оператора.itemgetter() но не удалось просто " схватить ""первый элемент моего второго элемента". https://docs.python.org/3/library/operator.html?highlight=operator.itemgetter#operator.itemgetter

(Примечание: допустимо использовать кортежи как ключи и значения, согласно to:
https://docs.python.org/3/tutorial/datastructures.html?highlight=dictionary и они отлично работают для моего проекта; этот вопрос касается только лучшей сортировки)


для тех, кто любит немного истории (вы будете кричать на меня, что я должен использовать какой-то другой метод, но я сейчас изучаю словари (что является одной из целей этого проекта)):

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

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

есть другие модули, которые помогите превратить теоретическое оптимальное решение в то, что действительно можно приобрести в любой день, стратегическим диалогом-руководством...

о моем словаре в этом вопросе: Я перебираю все разумные (достаточно высокие, чтобы сделать необходимую почтовую оплату и переплатить только до доли одной марки) комбинации марок-значений.

затем я вычисляю значение "Успех", которое основано на количестве необходимых марок( приоритет), количестве необходимых типов (ниже приоритет) (потому что покупка разных марок занимает дополнительное время на прилавке) и очень высокий штраф за переплату. Поэтому самая низкая ценность означает самый высокий успех.

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

Если вам интересно и хотите прочитать пример (первая строка):
Колонной являются:

  • количество марок 350 центов
  • количество марок 200 центов
  • количество марок 50 копеек
  • количество марок 25 центов
  • оценка-результат
  • из расчета применяется почтовые расходы
  • общее количество штампов
  • общее количество штампов-типа
  • переплата в центах, если любой

или словами: (предполагая, что почтовая служба предлагает существующие марки 350, 200, 50 и 25 центов), я могу применить почтовые расходы 75 центов, используя 1x 50 центов и 1x 25 центов. Это дает мне успех-рейтинг 22 (лучший в этом списке), почтовые расходы-75 центов, нужны две марки двух разных значений и имеют переплату 0 центов.

7 ответов


Вы можете просто использовать двойной индекс, что-то вроде этого должно работать:

MyDiccoSorted = sorted(MyDicco.items(), key=lambda s: s[1][2])

просто набор 2 к любому индексу идентификатора в кортеже.


мне проще использовать лямбда-выражения чем помнить, различные operator функции.

предполагая, на данный момент, что ваша оценка eval является 3-м элементом вашего кортежа значения (т. е. (post, number, eval, types, over):

MyDiccoSorted = sorted(MyDicco.items(), key=lamba x:x[1][2])

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

def myKey(x): return x[1][2]
MyDiccoSorted = sorted(MyDicco.items(), key=myKey)

вы можете использовать лямбда-выражение вместо operator.itemgetter(), чтобы получить точный элемент для сортировки. Предполагая, что ваш eval является первым пунктом в кортеж values, в противном случае используйте индекс точного элемента, который вы хотите в x[1][0] .Пример -

MyDiccoSorted = sorted(MyDicco.items(), key=lambda x: x[1][0])

как это работает -

A dict.items() возвращает что-то похожее на список кортежей (хотя и не совсем то, что в Python 3.х) пример -

>>> d = {1:2,3:4}
>>> d.items()
dict_items([(1, 2), (3, 4)])

теперь,, в key аргумент принимает объект функции (который может быть лямбда, или operator.itemgetter() который также возвращает функцию или любую простую функцию), функцию, которую вы передаете в key следует принять один аргумент, который будет элементом сортируемого списка.

то key функция вызывается с каждым элементом,и вы должны вернуть правильное значение для сортировки списка. Пример поможет вам понять это -

>>> def foo(x):
...     print('x =',x)
...     return x[1]
...
>>> sorted(d.items(),key=foo)
x = (1, 2)
x = (3, 4)
[(1, 2), (3, 4)]

это то, что вам нужно?

sorted(MyDicco.items(), key=lambda x: x[1][0])

index_of_evaluation_score = 0
MyDiccoSorted = sorted(MyDicco.items(), key=lambda key_value: key_value[1][index_of_evaluation_score])

поставив свою оценку в конце, где вы хотели, вы можете использовать следующее:

MyDicco = {
    (0, 0, 1, 1): (75, 2, 2, 0, 22),
    (0, 0, 0, 3): (75, 3, 1, 0, 31),
    (0, 0, 2, 0): (100, 2, 1, 25, 2521),
    (0, 1, 0, 0): (200, 1, 1, 125, 12511),
    (1, 0, 0, 0): (350, 1, 1, 275, 27511)} 

MyDiccoSorted = sorted(MyDicco.items(), key=lambda x: x[1][4])

print MyDiccoSorted

даем:

[((0, 0, 1, 1), (75, 2, 2, 0, 22)), ((0, 0, 0, 3), (75, 3, 1, 0, 31)), ((0, 0, 2, 0), (100, 2, 1, 25, 2521)), ((0, 1, 0, 0), (200, 1, 1, 125, 12511)), ((1, 0, 0, 0), (350, 1, 1, 275, 27511))]

Я думаю, одна из вещей, которые вы могли бы искать стабильный сорт.

функции сортировки в Python обычно являются "стабильными" сортами. Например, если вы сортируете:

 1 4 6
 2 8 1
 1 2 3
 2 1 8

по его первой колонке вы получите:

 1 4 6
 1 2 3
 2 8 1
 2 1 8

порядок строк с одинаковым значением в столбце 1 не изменяется. 1 4 6 сортируется перед 1 2 3 потому что это был исходный порядок этих строк перед сортировкой столбца 1. Сортировка была "стабильной" с версии 2.2 Питон. Подробнее здесь.

на другой ноте меня интересует, сколько вам пришлось объяснить свой код. Это признак того, что код выиграет от рефакторинга, чтобы сделать его цель более ясной.

именованные кортежи могут использоваться для удаления трудночитаемых индексов кортежей, которые вы видите во многих ответах здесь, например key=lambda x: x[1][0]-- что это значит? Что он делает?

вот версия с использованием именованных кортежей, которая помогает читателям (самое главное, вам!) понять, что ваш код пытается сделать. Обратите внимание, как лямбда теперь объясняет себя намного лучше.

from collections import namedtuple

StampMix = namedtuple('StampMix', ['c350', 'c200', 'c50', 'c25'])
Stats = namedtuple('Stats', ['score', 'postage', 'stamps', 'types', 'overpayment'])

data = {
    (0, 0, 1, 1): (22, 75, 2, 2, 0),
    (0, 0, 0, 3): (31, 75, 3, 1, 0),
    (0, 0, 2, 0): (2521, 100, 2, 1, 25),
    (0, 1, 0, 0): (12511, 200, 1, 1, 125),
    (1, 0, 0, 0): (27511, 350, 1, 1, 275)
}

candidates = {}
for stampmix, stats in data.items():
    candidates[StampMix(*stampmix)] = Stats(*stats)

print(sorted(candidates.items(), key=lambda candidate: candidate[1].score))

вы можете увидеть преимущества этого подхода в выходной:

>>> python namedtuple.py
(prettied-up output follows...)
[
    (StampMix(c350=0, c200=0, c50=1, c25=1), Stats(score=22, postage=75, stamps=2, types=2, overpayment=0)),
    (StampMix(c350=0, c200=0, c50=0, c25=3), Stats(score=31, postage=75, stamps=3, types=1, overpayment=0)),
    (StampMix(c350=0, c200=0, c50=2, c25=0), Stats(score=2521, postage=100, stamps=2, types=1, overpayment=25)),
    (StampMix(c350=0, c200=1, c50=0, c25=0), Stats(score=12511, postage=200, stamps=1, types=1, overpayment=125)),
    (StampMix(c350=1, c200=0, c50=0, c25=0), Stats(score=27511, postage=350, stamps=1, types=1, overpayment=275))
]

и он поможет с вашими алгоритмами. Например:

def score(stats):
    return stats.postage * stats.stamps * stats.types + 1000 * stats.overpayment