Рубрики
Без рубрики

Три реализации сумки в Python

Одной из первых структур данных, обсуждаемых в алгоритмах Sedgewick, является сумка. Сумка – это структура данных … Теги с питоном, алгоритмами, структурами данных, информатикой.

Одной из первых структур данных, обсуждаемых в алгоритмах Sedgewick, является сумка. Сумка – это структура данных, которая содержит неупорядоченную коллекцию. Мы говорим неупорядочен, потому что это в отличие от упорядоченных коллекций, таких как обычные массивы. Поскольку мы не обеспокоены заказом, мы можем хранить элементы любым способом.

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

Интерфейс сумки

Изображение ниже описывает интерфейс сумки на Java:

Он описывает пять операций:

  1. Конструктор Bag () берет ноль аргументов
  2. Метод Добавить (элемент) Вставляет предмет в сумку.
  3. Метод isempty () Рассказывает, если сумка пуста.
  4. Метод размер () сообщает нам размер сумки.
  5. Интерфейс Итерабильный <пункт> В Java позволяет использовать для .. в .. петля.

Реализация итерации в Python

В отличие от Java, Python не имеет официальной концепции напечатанного интерфейса. Тем не менее, у него есть средства, позволяющие объекту быть итерационным, так же, как Java, используя __iter__ и _next__ Анкет Ниже приведен пример того, как эти методы работают в Python:

class DoubleYourNumber:
  def __init__(self, start=0, stop=10):
    self.start = start
    self.stop  = stop
  def __iter__(self):
    self.current = self.start
    return self
  def __next__(self):
    if self.current <= self.stop:
      value = self.current * 2
      self.current += 1
      return value
    else:
      raise StopIteration

Приведенное выше пример класса, который поддерживает Итератор Python интерфейс. Ниже приведен пример того, как этот класс можно использовать для итерации:

>>> for value in  DoubleYourNumber(stop=5):
...    print(value)
...
0
2
4
6
8
10

Вы можете получить тот же эффект, используя итер и следующий прямо на объекте итератора:

>>> doubler = DoubleYourNumber(stop=5)
>>> iter_doubler = iter(doubler)
>>> next(iter_doubler)
0
>>> next(iter_doubler)
2
>>> next(iter_doubler)
4
>>> next(iter_doubler)
6
>>> next(iter_doubler)
8
>>> next(iter_doubler)
10
>>> next(iter_doubler)
Traceback (most recent call last):
...
StopIteration

В некотором смысле, вы можете думать об итераторах как о следующем В то время как Схема петли:

iter_object = iter(object)

while True:
  try:
    value = next(iter_object)
    # Do something with `value`
  except StopIteration:
    break # Leave the infinite while loop

Мы будем использовать этот шаблон в наших реализациях сумки.

ЕДИНИЦА

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

import unittest

from bag_implementation import Bag

class BagUnitTest(unittest.TestCase):
  def test_new_bag_is_empty(self):
    """
    A Bag should initially be empty.
    """
    bag = Bag()
    self.assertEqual(bag.is_empty(), True)

  def test_add_increases_size_by_one(self):
    """
    Calling bag.add(...) should increase it's size by one.
    """
    bag = Bag()
    bag.add(1)
    self.assertEqual(bag.size(), 1)

  def test_bag_can_be_iterated_over(self):
    """
    Bag uses the iterable Python interface.
    """
    bag = Bag()
    for i in [1,2,3]:
      bag.add(i)

    sum = 0
    for i in bag:
      sum += i

    self.assertEqual(sum, 6)

Создание сумки с использованием встроенного списка

Первая реализация сумки – это в основном обертка вокруг список . С тех пор список это упорядоченная коллекция, если мы отказались от упорядоченного ограничения список можно использовать в качестве неупорядоченной коллекции. Ниже приведена возможная реализация с использованием список :

class BagWithList(object):
  def __init__(self):
    self.collection = list()

  def add(self, item):
    self.collection.append(item)

  def size(self):
    return len(self.collection)

  def is_empty(self):
    return len(self.collection) == 0

  def __iter__(self):
    return iter(self.collection)

Официальный Python Wiki имеет Страница о сложности времени некоторых из встроенных структур данных. В частности, список Операции имеют следующий Big-O в среднем случае:

O (1) список Добавлять
O (1) список Получить длину
На) список Итерация

Также эта структура данных использует пространство O (n).

Использование связанного списка

По умолчанию Python не поставляется с версией связанного списка в качестве собственной структуры данных. Заставляя нас написать нашу собственную ниже, с пользовательским итератором.

class LinkedListNode(object):
  def __init__(self, value):
    self.value     = value
    self.next_node = None

  def add(self, value):
    if self.next_node == None:
      self.next_node = LinkedListNode(value)
    else:
      self.next_node.add(value)

  def size(self):
    if self.next_node == None:
      return 1
    else:
      return 1 + self.next_node.size()

  def __iter__(self):
    return LinkedListIter(self)

class LinkedListIter(object):
  def __init__(self, current_node):
    self.current_node = current_node

  def __next__(self):
    if self.current_node == None:
      raise StopIteration
    else:
      value = self.current_node.value
      self.current_node = self.current_node.next_node
      return value

Мы могли бы использовать эту реализацию связанного списка в нашей реализации сумки.

class BagWithLinkedList(object):
  def __init__(self):
    self.linked_list = None

  def add(self, value):
    if self.linked_list == None:
      self.linked_list = LinkedListNode(value)
    else:
      self.linked_list.add(value)

  def size(self):
    if self.linked_list == None:
      return 0
    else:
      return self.linked_list.size()

  def is_empty(self):
    return self.linked_list == None

  def __iter__(self):
    if self.linked_list == None:
      return iter([])
    else:
      return iter(self.linked_list)

Временная сложность этих методов не так привлекательна, как предыдущая. Существуют и другие варианты использования для связанных списков, которые показывают, как эффективно использовать его. Несмотря на это, он по -прежнему использует сложность пространства O (n), как список Версия сделала.

На) Связанный список Добавлять
На) Связанный список Получить длину
На) Связанный список Итерация

Использование DefaultDict из коллекций в качестве MultiSet

Набор является общеизвестным математическим объектом. Набор связан только с членством, или элемент принадлежит к сбору или нет. Как сумки, наборы неупорядочены. Однако, в отличие от сумок, они позволяют элементу появляться только один раз. Если мы расслабим это ограничение и отслеживаем количество раз, когда появляется элемент, то у нас есть Multiset Анкет

MultiSets появляются в C ++ как Встроенная структура данных Анкет Тем не менее, у Python ничего не названо как MultiSet. Вместо этого есть Коллекции. Счетчик и Collections.defaultdict Анкет Первый отслеживает как счета, так и приказ вставки. Нам нужно только отслеживать счет, поэтому мы будем использовать DefaultDict с значением по умолчанию 0 с помощью DefaultDict (int) Анкет

from collections import defaultdict

class Multiset(object):
  def __init__(self):
    self.counts = defaultdict(int)
    self.total = 0    

  def add(self, item):
    self.counts[item] += 1
    self.total += 1

  def size(self):
    return self.total

class MultisetIterator(object):
  def __init__(self, multiset):
    self.multiset   = multiset
    self.keys       = list(multiset.counts.keys()) # Choose a fixed ordering
    self.key_index  = 0 # Current `keys` index
    self.key_repeat = 0 # Number of times we've repeated the current key

  def current_key(self):
    return self.keys[self.key_index]

  def current_count(self):
    return self.multiset.counts[self.current_key()]

  def __next__(self):
    key   = self.current_key()
    count = self.current_count()

    if self.key_repeat == count:
      if self.key_index + 1 < len(self.keys):
        self.key_index += 1
        self.key_repeat = 0
      else:
        raise StopIteration

    self.key_repeat += 1
    return self.current_key()

Далее мы используем наш Multiset В реализации Сумка Анкет

class BagWithMultiset(object):
  def __init__(self):
    self.multiset = Multiset()

  def add(self, item):
    self.multiset.add(item)

  def size(self):
    return self.multiset.size()

  def is_empty(self):
    return self.multiset.size() == 0

  def __iter__(self):
    return MultisetIterator(self.multiset)

Эта версия сумки может быть самой эффективной, учитывая сложность времени и пространства.

Multiset Добавлять На) O (1)
Multiset Получить длину O (1) O (1)
Multiset Итерация На) На)

Эти результаты почти наравне с список реализация. Возможно, худший случай для приложения, стоит вернуться. Однако сложность пространства не является O (n), но на самом деле O (k), где k – это количество уникальных элементов в коллекции. Если есть много повторяющихся значений, это может быть более выгодным, чем две другие версии сумки.

Вывод

Мы рассмотрели основы итераторов Python и рассмотрели три разные версии Сумка : один с использованием список , один из которых использует нашу реализацию списка вручную, и, наконец, версия MultiSet с использованием Python’s DefaultDict . Эта структура данных не очень распространена и не имеет много преимуществ для упорядоченных массивов. Тем не менее, из -за его простоты мы можем использовать структуру данных сумки в качестве введения в алгоритмы.

С этим .. Счастливой алгоритминг, друзья!

Оригинал: “https://dev.to/farleyknight/three-implementations-of-a-bag-in-python-585p”