Я закончил тем, что реализовал обертку для heapq
, добавив dict для поддержания уникальных элементов очереди. Результат должен быть довольно эффективным для всех операторов:
class PriorityQueueSet(object):
"""
Combined priority queue and set data structure.
Acts like a priority queue, except that its items are guaranteed to be
unique. Provides O(1) membership test, O(log N) insertion and O(log N)
removal of the smallest item.
Important: the items of this data structure must be both comparable and
hashable (i.e. must implement __cmp__ and __hash__). This is true of
Python's built-in objects, but you should implement those methods if you
want to use the data structure for custom objects.
"""
def __init__(self, items=[]):
"""
Create a new PriorityQueueSet.
Arguments:
items (list): An initial item list - it can be unsorted and
non-unique. The data structure will be created in O(N).
"""
self.set = dict((item, True) for item in items)
self.heap = self.set.keys()
heapq.heapify(self.heap)
def has_item(self, item):
"""Check if ``item`` exists in the queue."""
return item in self.set
def pop_smallest(self):
"""Remove and return the smallest item from the queue."""
smallest = heapq.heappop(self.heap)
del self.set[smallest]
return smallest
def add(self, item):
"""Add ``item`` to the queue if doesn't already exist."""
if item not in self.set:
self.set[item] = True
heapq.heappush(self.heap, item)
This is one of the reasons automated testing/test driven development is even more important in dynamically typed languages. I haven't used Clojure (I mostly use Ruby), so unfortunately I can't recommend a specific testing framework.
Первое, что я хотел бы упомянуть, это то, что Брюс Экель написал очень интересную статью под названием Strong Typing vs Strong Testing (ссылка находится по адресу этот момент, к сожалению, но, надеюсь, скоро наступит).
Его идея состоит в том, что при работе с компилируемыми языками компилятор действует как первый автоматический этап автоматического тестирования. При переходе на динамический язык вы теряете этот первый уровень автоматического тестирования. Но в обоих случаях этот первый автоматический уровень - это лишь часть тестирования, и даже не очень важная часть.
Он считает, что если вы правильно разрабатываете программы, то есть выполняете какие-то тесты и регрессионные тесты, отсутствие компилятора в любом случае вынудит вас добавить еще несколько, несколько базовых тестов, поэтому это не так уж и важно. потеря.
Итак, я думаю, что первый ответ, который я дам вам, - сосредоточьтесь на своем тестировании, что вы все равно должны делать, и такие изменения не должны сильно на вас повлиять.
Второе Я ' Я хотел бы упомянуть, что многие динамические языки, которые я видел (например, Python), имеют гораздо лучшие возможности для изменения того, что делают методы / классы, не нарушая существующий код.
Например, с Python, если ваш метод принимал два параметра, но теперь требует третьего, вы всегда можете добавить параметр по умолчанию, не нарушая какой-либо существующий код, но теперь вы можете его использовать. Это очень простой прием, но в случае с Python (и я предполагаю, что и с большинством других динамических языков) эти методы могут стать намного интереснее; поскольку они динамичны,
При переходе на динамические языки вы теряете некоторый уровень рефакторинга и безопасности типов. Чем больше информации у компилятора, тем больше он может сделать для вас во время компиляции.
Тим Брей обсуждает его здесь , критика которого Седриком здесь , и сообщение на artima, где это подробно обсуждается.
Вы делаете то же самое, что и, если бы метод был частью общедоступного интерфейса, которым вы не были единственным пользователем.
Вы добавляете новый метод с дополнительным модулем и измените старую, чтобы вызвать новую с подходящим значением по умолчанию.
О, и если ваша программа настолько велика, убедитесь, что у вас есть хорошие тесты (test-is должен сделать ее проще, чем Java)
Test coverage is definitely important. But a dynamically typed language will allow you to work in a different way. In a strongly typed language (like Java), a change in the interface needs to modify all the callers. In Ruby, you could do this-- but probably won't. Instead, you'll probably add flexibility to the method on one of a few ways. Namely:
Вы не можете полностью отказаться от поддержки компилятора в Clojure. В конкретном примере, который вы даете, это арность измененной функции, которая будет обнаружена путем компиляции кода Clojure. Я все еще делаю переход от строгого к динамическому типу, и это меня утешает!