heapq із користувацьким предикатом порівняння


82

Я намагаюся створити купу з користувацьким предикатом сортування. Оскільки значення, що входять до нього, мають "визначений користувачем" тип, я не можу змінити їх вбудований предикат порівняння.

Чи є спосіб зробити щось на зразок:

h = heapq.heapify([...], key=my_lt_pred)
h = heapq.heappush(h, key=my_lt_pred)

Або ще краще, я міг би обернути функції heapq у своєму власному контейнері, тому мені не потрібно продовжувати передавати предикат.



Відповіді:


120

Згідно з документацією heapq , спосіб настроїти порядок купи полягає в тому, щоб кожен елемент у купі був кортежем, причому перший елемент кортежу - це той, що приймає звичайні порівняння Python.

Функції в модулі heapq трохи громіздкі (оскільки вони не є об'єктно-орієнтованими), і завжди вимагають явного передавання нашого об'єкта кучі (скручений список) як першого параметра. Ми можемо вбити двох зайців одним каменем, створивши дуже простий клас обгортки, який дозволить нам вказати keyфункцію та представити купу як об’єкт.

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

# -*- coding: utf-8 -*-
import heapq

class MyHeap(object):
   def __init__(self, initial=None, key=lambda x:x):
       self.key = key
       self.index = 0
       if initial:
           self._data = [(key(item), i, item) for i, item in enumerate(initial)]
           self.index = len(self._data)
           heapq.heapify(self._data)
       else:
           self._data = []

   def push(self, item):
       heapq.heappush(self._data, (self.key(item), self.index, item))
       self.index += 1

   def pop(self):
       return heapq.heappop(self._data)[2]

(Додатковою self.indexчастиною є уникнення зіткнень, коли оцінене значення ключа є тиражем, а збережене значення не можна безпосередньо порівняти - інакше heapq може вийти з ладу з TypeError)


4
Дуже мило! Ви можете навіть піти далі і використовувати трійки (self.key (item), id, item), де id може бути цілим числом, що обробляється як атрибут класу, і збільшується після кожного натискання. Таким чином, ви уникаєте винятків, що виникають, коли key (item1) = key (item2). Тому що ключі були б унікальними.
zeycus

4
Я насправді намагався вставити це (або щось на основі цього) у stdlib Python, і пропозиція відхилилася.
jsbueno

1
шкода, відповідає об'єктно-орієнтованому стилю більшості функцій Python, а ключовий аргумент забезпечує додаткову гнучкість.
zeycus

Я використав список замість кортежу для, наприклад, [self.key (item), id, item], і він працює чудово, поки перший індекс є ключовим.
Діпак Ядав

5
Це зазнало б невдачі, якщо елементи не порівнянні та існують зв'язки у ключових значеннях. Я б поклав id(item)як середній елемент кортежу, щоб розірвати зв'язки.
Георгі Янчев

47

Визначте клас, у якому перевизначення __lt__()функції. Див. Приклад нижче (працює в Python 3.7):

import heapq

class Node(object):
    def __init__(self, val: int):
        self.val = val

    def __repr__(self):
        return f'Node value: {self.val}'

    def __lt__(self, other):
        return self.val < other.val

heap = [Node(2), Node(0), Node(1), Node(4), Node(2)]
heapq.heapify(heap)
print(heap)  # output: [Node value: 0, Node value: 2, Node value: 1, Node value: 4, Node value: 2]

heapq.heappop(heap)
print(heap)  # output: [Node value: 1, Node value: 2, Node value: 2, Node value: 4]


4
Це здається найчистішим рішенням на сьогоднішній день!
Роймунсон,

Абсолютно погоджуюся з попередніми двома коментарями. Здається, це краще, чистіше рішення для Python 3.
Chiraz BenAbdelkader

Крім того , тут дуже аналогічне рішення на аналогічне питання: stackoverflow.com/questions/2501457 / ...
Chiraz BenAbdelkader

1
Я протестував це за допомогою __gt__цього, і воно також працює. Чому не має значення, який магічний метод ми використовуємо? Я не можу знайти нічого в heapqдокументації. Можливо, це пов’язано з тим, як Python взагалі робить порівняння?
Джош Кларк

1
При порівнянні в heapq, Python шукає __lt__()перший. Якщо його не визначено, він буде шукати __gt__(). Якщо ні те, ні інше не визначено, воно кидає TypeError: '<' not supported between instances of 'Node' and 'Node'. Це можна підтвердити, визначивши і те, __lt__()і __gt__(), помістивши в кожну заяву на друк, і отримавши __lt__()повернення NotImplemented.
Фанчен Бао

19

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

Проте більш доречним для вашого запитання є те, що документація включає обговорення із зразком коду того, як можна реалізувати власні функції обгортки heapq для вирішення проблем стабільності сортування та елементів з однаковим пріоритетом (серед інших питань).

У двох словах, їх рішення полягає в тому, щоб кожен елемент у heapq був потрійним з пріоритетом, кількістю записів та елементом, який потрібно вставити. Кількість записів гарантує, що елементи з однаковим пріоритетом сортуються в порядку, в якому вони були додані до heapq.


Це правильне рішення, як heappush, так і heappushpop працюють безпосередньо з кортежами
Дейзі

2

Обмеження обох відповідей полягає в тому, що вони не дозволяють розглядати зв’язки як зв’язки. У першому зв’язки розриваються порівнянням предметів, у другому порівнянням порядку введення. Швидше просто дозволити зв'язкам бути краватками, і якщо їх багато, це може мати велике значення. Виходячи з вищевикладеного та з документів, незрозуміло, чи можна цього досягти у heapq. Це здається дивним, що heapq не приймає ключ, тоді як функції, похідні від нього в тому ж модулі, приймають.
PS: Якщо ви перейдете за посиланням у першому коментарі ("можливий дублікат ..."), є ще одна пропозиція визначити le, що здається рішенням.


2
setattr(ListNode, "__lt__", lambda self, other: self.val <= other.val)

Використовуйте це для порівняння значень об'єктів у heapq

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