heapq с настраиваемым предикатом сравнения

82

Я пытаюсь создать кучу с настраиваемым предикатом сортировки. Поскольку входящие в него значения относятся к «определяемому пользователем» типу, я не могу изменить их встроенный предикат сравнения.

Есть ли способ сделать что-то вроде:

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

Или, что еще лучше, я мог бы обернуть функции heapq в свой собственный контейнер, чтобы мне не нужно было передавать предикат.

всехар
источник

Ответы:

120

Согласно документации heapq , способ настроить порядок кучи - сделать так, чтобы каждый элемент в куче был кортежем, причем первый элемент кортежа был тем, который принимает обычные сравнения Python.

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

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

# -*- 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)

jsbueno
источник
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.
Чираз Бен Абделькадер,
Кроме того, здесь есть очень похожее решение аналогичного вопроса: stackoverflow.com/questions/2501457/…
Chiraz BenAbdelkader
1
Я тестировал это, используя __gt__вместо этого, и он тоже работает. Почему не имеет значения, какой магический метод мы используем? Я ничего не могу найти в heapqдокументации. Может быть, это связано с тем, как Python вообще выполняет сравнения?
Джош Кларк,
1
При сравнении в heapqPython __lt__()сначала ищет . Если не определено, будет искать __gt__(). Если ни один из них не определен, он бросает TypeError: '<' not supported between instances of 'Node' and 'Node'. Это можно подтвердить, указав __lt__()и __gt__(), поместив в каждый оператор печати и получив __lt__()return NotImplemented.
Fanchen Bao
19

Документация по heapq предполагает, что элементы кучи могут быть кортежами, в которых первый элемент является приоритетным и определяет порядок сортировки.

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

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

srgerg
источник
Это правильное решение, как heappush и heappushpop работает непосредственно с кортежами
ромашку
2

Ограничение обоих ответов состоит в том, что они не позволяют рассматривать связи как связи. В первом случае связи разрываются путем сравнения элементов, во втором - путем сравнения порядка ввода. Гораздо быстрее позволить связям быть связями, и если их будет много, это может иметь большое значение. Исходя из вышеизложенного и из документации, неясно, можно ли этого достичь в heapq. Кажется странным, что heapq не принимает ключ, в то время как функции, производные от него в том же модуле, принимают.
PS: Если вы перейдете по ссылке в первом комментарии («возможный дубликат ...»), есть еще одно предложение по определению файла, которое кажется решением.

bbphd
источник
2
setattr(ListNode, "__lt__", lambda self, other: self.val <= other.val)

Используйте это для сравнения значений объектов в heapq

Ритика Гупта
источник