diff --git a/README.md b/README.md
index 556de0f..bc57e97 100644
--- a/README.md
+++ b/README.md
@@ -1,372 +1,68 @@
-# Persistent-Data-Structures
+# Эффективность по памяти
-Курсовой проект по дисциплине "Современные методы программирования" - "Persistent data structures"
-
-## Разработчики:
-* *Карицкая Полина, 24225.1*
-* *Пучков Дмитрий, 24225.1*
+Реализация дополнительного требования курсовой работы по дисциплине "Современные методы программирования" - "Persistent data structures"
---
## Оглавление
-- [Persistent-Data-Structures](#persistent-data-structures)
- - [Разработчики:](#разработчики)
- - [Оглавление](#оглавление)
- - [Описание задания](#описание-задания)
- - [Базовые требования](#базовые-требования)
- - [Дополнительные требования](#дополнительные-требования)
- - [Календарный план](#календарный-план)
- - [Ожидаемое решение](#ожидаемое-решение)
- - [Теоретическая часть](#теоретическая-часть)
- - [Персистентные структуры данных](#персистентные-структуры-данных)
- - [Fat node](#fat-node)
- - [Path copying](#path-copying)
- - [Более эффективное по скорости доступа представление структур данных](#более-эффективное-по-скорости-доступа-представление-структур-данных)
- - [API](#api)
- - [Примеры использования](#примеры-использования)
- - [Массив (Persistent Array)](#массив-persistent-array)
- - [Список (Persistent Linked List)](#список-persistent-linked-list)
- - [Aссоциативный массив (Persistent Map)](#aссоциативный-массив-persistent-map)
- - [Используемые источники](#используемые-источники)
-
----
-## Описание задания
-
-Реализовать библиотеку в Python со структурами данных в persistent-вариантах.
+- [Эффективность по памяти](#эффективность-по-памяти)
+ - [B-деревья](#B-деревья)
+ - [Чем эффективнее простого копирования?](#чем-эффективнее-простого-копирования)
+ - [Когда выгодно использовать B-деревья?](#когда-выгодно-использовать-B-деревья)
+ - [Ограничения](#ограничения)
---
-## Базовые требования
-- [x] Массив (константное время доступа, переменная длина)
-- [x] Двусвязный список
-- [x] Ассоциативный массив (на основе Hash-таблицы, либо бинарного дерева)
+## B-деревья
----
-## Дополнительные требования
-- [ ] Реализовать более эффективное по памяти представление структур данных;
-- [ ] Реализовать универсальный undo-redo механизм для перечисленных структур с поддержкой каскадности (для вложенных структур);
-- [ ] Реализовать поддержку транзакционной памяти (STM).
-
----
-## Календарный план
+**B-дерево(B-tree)** - сбалансированное дерево поиска, в котором каждый узел содержит множество ключей и имеет более двух потомков.
-| **Сроки** | **Этап работы** | **Разделение ответственностей** |
-|--------------------|----------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
-| **до 23.11.2024** | - Создание каркаса проекта
- Создание репозитория на GitHub | - Ответственный за настройку проекта и репозитория: *Полина*
- Ответственный за организацию структуры файлов и начальную настройку CI/CD: *Дмитрий* |
-| **до 07.12.2024** | **Реализация базовой функциональности:**
- Массив (Persistent Array)
- Двусвязный список (Persistent Linked List)
- Ассоциативный массив (Persistent Map)
- Создание единого API | - Ответственный за реализацию Persistent Array: *совместно*
- Ответственный за реализацию Persistent Linked List: *Полина*
- Ответственный за реализацию Persistent Map: *Дмитрий*
- Ответственный за создание API: *совместно* |
-| **до 21.12.2024** | **Реализация дополнительной функциональности:**
- Реализовать более эффективное по скорости доступа представление структур данных, чем fat-node
- Обеспечить произвольную вложенность данных (по аналогии с динамическими языками)
- Реализовать универсальный undo-redo механизм для перечисленных структур с поддержкой каскадности | - Ответственный за улучшение представления структур данных: *совместно*
- Ответственный за реализацию вложенности данных: *совместно*
- Ответственный за разработку механизма undo-redo: *совместно* |
+**B-дерево порядка m обладает следующими свойствами:**
----
-## Ожидаемое решение
+*Свойство 1:* Глубина всех листьев одинакова.
-Ожидаемое решение состоит в разработке библиотеки, которая будет поддерживать следующие структуры данных:
+*Свойство 2:* Все узлы, кроме корня должны иметь как минимум (m/2) – 1 ключей и максимум m-1 ключей.
-1. **Persistent Array** - массив с возможностью добавления элементов без изменения предыдущих состояний. Операции должны поддерживать константное время доступа.
-
-2. **Persistent Linked List** - двусвязный список с поддержкой добавления и удаления элементов, при этом сохраняется возможность обратиться к предыдущим состояниям списка.
+*Свойство 3:* Все узлы без листьев, кроме корня (т.е. все внутренние узлы), должны иметь минимум m/2 потомков.
-3. **Persistent Map** - ассоциативный массив, реализованный на основе хеш-таблицы или бинарного дерева поиска. Это будет позволять эффективно работать с данными, при этом поддерживать возможность обращения к предыдущим версиям данных.
+*Свойство 4:* Если корень – это узел не содержащий листьев, он должен иметь минимум 2 потомка.
-4. **Оптимизация доступа к данным** - предложим более эффективное представление данных для ускорения операций доступа по сравнению с fat-node.
+*Свойство 5:* Узел без листьев с n-1 ключами должен иметь n потомков.
-5. **Типизация данных** - обеспечим произвольную вложенность данных, при этом не отказываясь от строгой типизации через generic или template.
+*Свойство 6:* Все ключи в узле должны располагаться в порядке возрастания их значений.
-6. **Undo/Redo Механизм** - реализуем механизм для всех структур данных, который позволит отменять и повторять изменения, причем для вложенных структур будет поддерживаться каскадность.
+На иллюстрации продемонстрировано B-дерево 4 порядка.
+
-Каждая из структур данных будет поддерживать все основные операции: вставку, удаление, обновление, а также предоставлять возможность работы с предыдущими версиями данных через персистентность данных. Вся библиотека будет иметь единый API для работы с данными и их модификацией. Также будет обеспечена возможность взаимодействия с данными в виде вложенных структур.
+Представленная реализация с использованием B-деревьев позволяет сэкономить память по сравнению с простым копированием всей структуры данных для каждой новой версии. Экономия достигается за счет общего использования неизмененных данных, вместо создания полного дубликата при каждом изменении.
---
-## Теоретическая часть
-
-### Персистентные структуры данных
-
-**Персистентные структуры данных** сохраняют предыдущие версии при изменении. Структура называется *fully persistent*, если все её версии доступны для изменений. В *partially persistent* структурах можно изменять только последнюю версию, но доступ к предыдущим возможен. Эти структуры часто реализуются с использованием алгоритмов, таких как path copying, node copying и fat node, а также с применением бинарных деревьев поиска и красно-черных деревьев.
-
-Персистентные структуры используются в вычислительной геометрии, объектно-ориентированном программировании, текстовых редакторах, симуляциях и системах контроля версий, таких как Git.
-
-### Fat node
-
-*Fat node* используется для создания персистентных структур данных, где изменения сохраняются только в измененных узлах дерева, а сами узлы могут расширяться, чтобы хранить все версии. Основной принцип заключается в том, чтобы хранить обновления и версии данных в списках, что позволяет эффективно отслеживать изменения в структуре.
-
-**Преимущества:**
-
-* Экономия памяти, так как изменяются только те части данных, которые действительно изменились;
-* Быстрая работа с историей изменений.
-
-**Недостатки:**
+## Чем эффективнее простого копирования?
-* Может быть менее эффективен для структур с частыми изменениями, так как увеличение размера узлов приводит к дополнительным затратам на память.
+1. Экономия памяти:
-Можно посмотреть [визуализацию метода *fat node*](https://kumom.io/persistent-bst).
+ * При каждом обновлении создается копия только измененной части состояния, а неизмененные данные продолжают разделяться между версиями;
+ * В простом копировании для каждой новой версии полностью дублируется все состояние, даже если изменения затрагивают лишь малую часть.
-### Path copying
+2. Скорость:
-Метод *path copying* используется для создания персистентных структур данных, где при изменении узла создается его копия, и нужно пройти по всем узлам от измененного до корня, чтобы обновить ссылки на новый узел. Этот метод упрощает доступ к данным, так как для поиска нужной версии достаточно пройти путь от корня.
+ * Благодаря копированию только измененной части, создание новой версии работает быстрее, чем дублирование всего состояния.
-**Преимущества:**
+3. Хранение данных:
-* Быстрый доступ к данным;
-* Простота в реализации.
-
-**Недостатки:**
-
-* Большие затраты на память, поскольку изменения могут потребовать копирования всей структуры;
-* Могут возникать дополнительные накладные расходы при частых модификациях.
-
-Метод *path copying* также используется для создания полностью персистентных структур данных.
-
-На иллюстрации продеминстрирован пример использования *path copying* на бинарном дереве поиска. При внесении изменения создается новый корень, при этом старый корень сохраняется для дальнейшего использования (он показан темно-серым цветом). Заметим также, что старое и новое деревья частично делят общую структуру.
-
-
-
-### Более эффективное по скорости доступа представление структур данных
-
-Поскольку одно из требований — использование более эффективного подхода по сравнению с методом *fat-node*, в проекте будет применяться подход ...
+ * B-дерево позволяет организовать состояние версий так, что поддержка множества изменений становится управляемой и эффективной.
---
-## API
-
-Для работы с персистентными структурами данных используйте следующий API:
-
-Создание объекта:
-```python
-from persistent_data_structure import PersistentLinkedList, PersistentArray, PersistentMap
-
-lst = PersistentLinkedList()
-arr = PersistentLinkedList()
-dct = PersistentMap()
-```
-
-Обращение к элементу текщей версии:
-```python
-lst[index]
-arr[index]
-dct[key]
-```
-
-Обращение по индексу к элементу произвольной версии для массива и списка:
-```python
-arr.get(version, index)
-lst.get(version, index)
-```
-
-Обращение по ключу к элементу произвольной версии для мапы:
-```python
-dct.get(version, key)
-```
-
-Добавление элемента в конец в новую версию для массива и списка:
-```python
-arr.add(element)
-lst.add(element)
-```
-
-Добавление элемента в начало в новую версию списка:
-```python
-lst.add_first(element)
-```
+## Когда выгодно использовать B-деревья?
-Удаление элемента по индексу для массива и списка в новой версии и возвращение элемента:
-```python
-arr.pop(index)
-lst.pop(index)
-```
+1. Частичные изменения состояния:
-Удаление элемента по ключу для мапы в новой версии и возвращение элемента:
-```python
-dct.pop(key)
-```
+ * Когда изменения касаются лишь небольшой части состояния, а остальное остается неизменным.
-Обновление элемента по индексу в новой версии массива или списка:
-```python
-arr[index] = element
-lst[index] = element
-```
+2. Множество версий:
-Обновление элемента по ключу в новой версии мапы:
-```python
-dct['key'] = element
-```
-
-Вставка элемента в новую версию по указанному индексу для массива или списка:
-```python
-arr.insert(index, element)
-lst.insert(index, element)
-```
-
-Удаление элемента в новой версии массива или списка по индексу:
-```python
-arr.remove(index)
-lst.remove(index)
-```
-
-Удаление элемента в новой версии мапы по ключу:
-```python
-dct.remove(key)
-```
-
-Получение размера массива или списка для текущей версии:
-```python
-arr.get_size()
-lst.get_size()
-```
-
-Проверка на пустоту для массива или списка:
-```python
-arr.check_is_empty()
-lst.check_is_empty()
-```
-
-Возвращение состояние объекта для указанной версии:
-```python
-arr.get_version(version)
-lst.get_version(version)
-dct.get_version(version)
-```
-
-Обновление текущей версии объекта до указанной:
-```python
-arr.update_version(version)
-lst.update_version(version)
-dct.update_version(version)
-```
-
-Получение элемента текущей версии массива или списка по указанному индексу.
-```python
-arr.__getitem__(index)
-lst.__getitem__(index)
-```
-
-Получение элемента текущей версии мапы по указанному ключу.
-```python
-dct.__getitem__(key)
-```
-
-Обновление или создание элемента по указанному индексу в новой версии для массива или списка.
-```python
-arr.__setitem__(index)
-lst.__setitem__(index)
-```
-
-Обновление или создание элемента по указанному ключу в новой версии для мапы.
-```python
-dct.__setitem__(key)
-```
-
-Очистка объекта, при этом создается новая версия.
-```python
-arr.clear()
-lst.clear()
-dct.clear()
-```
-
----
-## Примеры использования
-
-### Массив (Persistent Array)
-
-**Примеры использования:**
-
-```
- >>> array = PersistentArray(size=5, default_value=0) # Создаем массив из 5 элементов, заполненных нулями
- >>> array[0] # Получаем значение первого элемента
- 0
- >>> array[0] = 10 # Обновляем значение первого элемента в новой версии
- >>> array[0] # Проверяем значение первого элемента в текущей версии
- 10
- >>> array.add(20) # Добавляем новый элемент в конец массива в новую версию
- >>> array[5] # Проверяем значение нового элемента
- 20
- >>> array.pop(1) # Удаляем элемент с индексом 1 в новой версии
- 0
- >>> array.insert(2, 15) # Вставляем значение 15 на индекс 2 в новой версии
- >>> array[2] # Проверяем вставленное значение
- 15
- >>> array.remove(3) # Удаляем элемент с индексом 3
- >>> array.get(1, 0) # Получаем значение элемента с индексом 0 из версии 1
- 0
- >>> array.clear() # Очищаем массив, создавая новую версию
- >>> array.get_size() # Проверяем размер массива в текущей версии
- 0
- >>> array.check_is_empty() # Проверяем, пуст ли массив
- True
- >>> array.get(0, 2) # Пытаемся получить элемент из очищенного массива в версии 0
- Traceback (most recent call last):
- ...
- ValueError: Invalid index
-```
-
-### Список (Persistent Linked List)
-
-**Примеры использования:**
-
-```
- >>> linked_list = PersistentLinkedList([1, 2, 3]) # Создаем список с начальными элементами
- >>> linked_list.add(4) # Добавляем элемент в конец списка
- >>> linked_list[3] # Получаем элемент по индексу в текущей версии
- 4
- >>> linked_list.add_first(0) # Добавляем элемент в начало списка
- >>> linked_list[0] # Получаем элемент в текущей версии по индексу
- 0
- >>> linked_list.insert(2, 1.5) # Вставляем элемент на индекс 2
- >>> linked_list[2] # Проверяем значение вставленного элемента
- 1.5
- >>> linked_list.get(1, 2) # Получаем элемент из версии 1 по индексу 2
- 3
- >>> linked_list.pop(1) # Удаляем элемент по индексу 1 в новой версии
- 2
- >>> linked_list.remove(3) # Удаляем элемент
- >>> linked_list.clear() # Очищаем список, создавая новую версию
- >>> linked_list.get(0, 2) # Пытаемся получить элемент из очищенной версии
- Traceback (most recent call last):
- ...
- IndexError: Index out of range
- >>> linked_list.update_version(2) # Возвращаемся к версии 2
- >>> linked_list[0] # Получаем элемент по индексу в версии 2
- 1
- >>> linked_list.get_size() # Получаем размер списка в текущей версии
- 4
- >>> linked_list.check_is_empty() # Проверяем, пуст ли список в текущей версии
- False
-```
-
-### Aссоциативный массив (Persistent Map)
-
-**Примеры использования:**
-
-```
- >>> map = PersistentMap({'foo': 'bar'}) # Создаем пустой ассоциативный массив
- >>> map['key'] = 'value' # Добавляем элемент
- >>> map['key'] = 'value2' # Изменяем элемент в следующей версии
- >>> map['key'] # Получаем элемент последней версии
- 'value2'
- >>> map.get(1, 'key')
- {'key': 'value'}
- >>> map.remove('key') # Удаляем элемент в новой версии
- >>> map.clear() # Очищаем ассоциативный массив в новой версии
- >>> map.get(0, 'key') # Пытаемся получить элемент отсутствующий в переданной версии
- Traceback (most recent call last):
- ...
- KeyError: Key "key" does not exist
-```
+ * Если нужно поддерживать большое количество версий состояния.
---
-## Используемые источники
-
-1. Milan Straka, "Functional Data Structures and Algorithms", Computer Science Institute of Charles University, Prague 2013
-
-2. Крис Окасаки, "Чисто функциональные структуры данных", 2018
-
-3. Статья на Geeks for geeks ["Persistent data structures"](https://www.geeksforgeeks.org/persistent-data-structures/)
-
-4. Несколько статей по персистентным векторам:
-
- * [Understanding Clojure's Persistent Vectors, pt. 1](https://hypirion.com/musings/understanding-persistent-vector-pt-1)
- * [Understanding Clojure's Persistent Vectors, pt. 2](https://hypirion.com/musings/understanding-persistent-vector-pt-2)
- * [Understanding Clojure's Persistent Vectors, pt. 3](https://hypirion.com/musings/understanding-persistent-vector-pt-3)
-
-5. Driscoll J. R. et al. Making data structures persistent //Proceedings of the eighteenth annual ACM symposium on Theory of computing. – 1986. – С. 109-121.
-
-6. Серия видео-лекций по персистентным структурам данных:
+## Ограничения
- * ["Visualizing Persistent Data Structures" by Dann Toliver](https://www.youtube.com/watch?v=2XH_q494U3U)
- * [Persistent Data Structures, MIT](https://www.youtube.com/watch?v=T0yzrZL1py0)
\ No newline at end of file
+* Если изменения охватывают большую часть структуры данных (или всю), память перестает экономиться, поскольку почти весь массив нужно будет копировать.
\ No newline at end of file
diff --git a/b-tree.png b/b-tree.png
new file mode 100644
index 0000000..9eb0995
Binary files /dev/null and b/b-tree.png differ
diff --git a/persistent_data_structures/base_persistent.py b/persistent_data_structures/base_persistent.py
index a4b967b..c96db26 100644
--- a/persistent_data_structures/base_persistent.py
+++ b/persistent_data_structures/base_persistent.py
@@ -1,44 +1,81 @@
from copy import deepcopy
+from typing import Optional, Any
+
+
+class NodeState:
+ """Класс, представляющий состояние узла."""
+ def __init__(self, data: Optional['NodeState'] = None):
+ self.data = data
+ self.next_node: Optional['NodeState'] = None
+
+
+class Node:
+ """Класс узла для хранения состояния в B-дереве."""
+ def __init__(self, state: NodeState | None) -> None:
+ """
+ Инициализирует узел с заданным состоянием.
+
+ :param state: Состояние узла.
+ """
+ self.state: NodeState | None = state
+ self.children: dict[int, Node] = {}
class BasePersistent:
- """Базовый класс для персистентных стркутур данных.
-
- Каждая персистентная структура будет хранить в себе историю изменений в виде словаря с ключами
- версиями и значениями - состояниями. Также персистентная структура будет хранить номер ткущей
- и номер последней версии.
- """
- def __init__(self, initial_state=None) -> None:
- """Инициализирует персистентную структуру данных.
+ """Базовый класс для персистентных структур данных с использованием B-дерева."""
+
+ def __init__(self, initial_state: Optional[NodeState] = None) -> None:
+ """
+ Инициализирует персистентную структуру данных.
+
:param initial_state: Начальное состояние персистентной структуры данных.
"""
- self._history = {0: initial_state}
- self._current_state = 0
- self._last_state = 0
+ self.root: Node = Node(initial_state)
+ self._current_version: int = 0
+ self._last_version: int = 0
+ self._version_map: dict[int, Node] = {0: self.root}
- def get_version(self, version):
- """Возвращает состояние персистентной структуры данных на указанной версии.
+ def get_version(self, version: int) -> dict[Any, Any]:
+ """
+ Возвращает состояние персистентной структуры данных на указанной версии.
:param version: Номер версии.
:return: Состояние персистентной структуры данных на указанной версии.
:raises ValueError: Если указанная версия не существует.
"""
- if version < 0 or version >= len(self._history):
+ if version not in self._version_map:
raise ValueError(f'Version "{version}" does not exist')
- return self._history[version]
+ return self._version_map[version].state
- def update_version(self, version):
- """Обновляет текущую версию персистентной структуры данных до указанной.
+ def set_version(self, version: int) -> None:
+ """
+ Обновляет текущую версию персистентной структуры данных до указанной.
:param version: Номер версии.
:raises ValueError: Если указанная версия не существует.
"""
- if version < 0 or version >= len(self._history):
+ if version not in self._version_map:
raise ValueError(f'Version "{version}" does not exist')
- self._current_state = version
+ self._current_version = version
+ self.root = self._version_map[version]
def _create_new_state(self) -> None:
- """Создает новую версию."""
- self._last_state += 1
- self._history[self._last_state] = deepcopy(self._history[self._current_state])
- self._current_state = self._last_state
+ """
+ Создает новую версию персистентной структуры данных с
+ минимальным дублированием данных.
+
+ Этот метод копирует текущее состояние структуры данных и создает
+ новую версию, добавляя ее в карту версий.
+ Дублирование данных минимизируется путем использования глубокого
+ копирования состояния узла.
+
+ :raises ValueError: Если текущая версия не существует в карте версий.
+ """
+ self._last_version += 1
+ parent_node = self._version_map[self._current_version]
+ new_state = deepcopy(parent_node.state)
+ new_node = Node(new_state)
+ parent_node.children[self._last_version] = new_node
+ self._version_map[self._last_version] = new_node
+ self._current_version = self._last_version
+ self.root = new_node
diff --git a/persistent_data_structures/persistent_array.py b/persistent_data_structures/persistent_array.py
index cee913f..7558152 100644
--- a/persistent_data_structures/persistent_array.py
+++ b/persistent_data_structures/persistent_array.py
@@ -1,30 +1,28 @@
import numpy as np
-
-from persistent_data_structures.base_persistent import BasePersistent
+from base_persistent import BasePersistent
class PersistentArray(BasePersistent):
- """Персистентный массив.
- Класс PersistentArray реализует неизменяемый массив с
- возможностью хранения нескольких версий, где каждая
- версия является изменением предыдущей.
- """
+ """Персистентный массив с использованием B-дерева."""
def __init__(self, size: int = 1024, default_value: int = 0) -> None:
- """Инициализирует новый массив с несколькими версиями.
+ """
+ Инициализирует новый массив с несколькими версиями.
Создается первая версия массива, которая состоит из элементов,
равных default_value.
+
:param size: Начальный размер массива (по умолчанию 1024).
:param default_value: Значение по умолчанию для элементов массива (по умолчанию 0).
"""
- self.size = size
- self.default_value = default_value
- initial_state = np.full(size, default_value)
+ self.size: int = size
+ self.default_value: int = default_value
+ initial_state: np.ndarray = np.full(size, default_value)
super().__init__(initial_state)
def __getitem__(self, index: int) -> any:
- """Получение значения из текущей версии массива по индексу.
+ """
+ Получение значения из текущей версии массива по индексу.
:param index: Индекс элемента в текущей версии массива.
:return: Значение элемента в текущей версии массива по заданному индексу.
@@ -32,65 +30,54 @@ def __getitem__(self, index: int) -> any:
"""
if index < 0 or index >= self.size:
raise ValueError("Invalid index")
- return self._history[self._current_state][index]
+ return self.root.state[index]
- def get(self, version: int, index: int) -> any:
- """Получение значения элемента для определенной версии массива по индексу.
+ def __setitem__(self, index: int, value: any) -> None:
+ """
+ Обновление значения элемента в новой версии массива.
- :param version: Номер версии, из которой нужно получить элемент.
- :param index: Индекс элемента в указанной версии массива.
- :return: Значение элемента в указанной версии массива по заданному индексу.
- :raises ValueError: Если версия или индекс выходят за пределы допустимого диапазона.
+ Обновляет значение элемента из текущей версии массива по индексу
+ и помещает получившийся массив в новую версию.
+
+ :param index: Индекс элемента, который необходимо обновить.
+ :param value: Новое значение для обновляемого элемента.
+ :raises ValueError: Если индекс выходит за пределы допустимого диапазона.
"""
- if version > self._current_state or version < 0:
- raise ValueError(f'Version "{version}" does not exist')
if index < 0 or index >= self.size:
raise ValueError("Invalid index")
- return self._history[version][index]
+ self._create_new_state()
+ self.root.state[index] = value
def add(self, value: any) -> None:
- """Добавление нового элемента в конец массива в новую версию.
+ """
+ Добавление нового элемента в конец массива в новую версию.
- :param value (int): Значение нового элемента, который добавляется в массив.
+ :param value: Значение нового элемента, который добавляется в массив.
"""
self._create_new_state()
- self._history[self._last_state] = np.append(self._history[self._last_state], value)
+ self.root.state = np.append(self.root.state, value)
self.size += 1
def pop(self, index: int) -> any:
- """Удаление элемента в новой версии массива и возвращение его значения.
+ """
+ Удаление элемента в новой версии массива и возвращение его значения.
- :param index (int): Индекс элемента, который необходимо удалить.
- :return int: Значение удаленного элемента.
+ :param index: Индекс элемента, который необходимо удалить.
+ :return: Значение удаленного элемента.
:raises ValueError: Если индекс выходит за пределы допустимого диапазона.
"""
if index < 0 or index >= self.size:
raise ValueError("Invalid index")
- removed_element = self._history[self._current_state][index]
self._create_new_state()
- self._history[self._last_state] = np.delete(self._history[self._last_state], index)
+ removed_element = self.root.state[index]
+ self.root.state = np.delete(self.root.state, index)
self.size -= 1
return removed_element
- def __setitem__(self, index: int, value: any) -> None:
- """Обновление значения элемента в новую версии массива.
-
- Обновляет значение элемента из текущей версии массива по индексу
- и помещает получившийся массив в новую версию.
- :param index: Индекс элемента, который необходимо обновить.
- :param value: Новое значение для обновляемого элемента.
- :raises ValueError: Если индекс выходит за пределы допустимого диапазона.
- """
- if index < 0 or index >= self.size:
- raise ValueError("Invalid index")
- self._create_new_state()
- self._history[self._last_state][index] = value
-
def insert(self, index: int, value: any) -> None:
- """Вставка нового элемента в массив в указанную позицию в новой версии.
+ """
+ Вставка нового элемента в массив в указанную позицию в новой версии.
- Вставляет новый элемент в указанную позицию в текущей версии массива и помещает
- получившийся массив в новую версию.
:param index: Позиция, в которую нужно вставить новый элемент.
:param value: Значение нового элемента, который нужно вставить.
:raises ValueError: Если индекс выходит за пределы допустимого диапазона.
@@ -98,13 +85,31 @@ def insert(self, index: int, value: any) -> None:
if index < 0 or index > self.size:
raise ValueError("Invalid index")
self._create_new_state()
- self._history[self._last_state] = np.insert(self._history[self._last_state], index, value)
+ self.root.state = np.insert(self.root.state, index, value)
self.size += 1
+ def get(self, version: int, index: int) -> any:
+ """
+ Получение значения элемента для определенной версии массива по индексу.
+
+ :param version: Номер версии, из которой нужно получить элемент.
+ :param index: Индекс элемента в указанной версии массива.
+ :return: Значение элемента в указанной версии массива по заданному индексу.
+ :raises ValueError: Если версия или индекс выходят за пределы допустимого диапазона.
+ """
+ if version < 0 or version > self._last_version:
+ raise ValueError(f'Version "{version}" does not exist')
+ state = self.get_version(version)
+ if index < 0 or index >= len(state):
+ raise ValueError("Invalid index")
+ return state[index]
+
def remove(self, index: int) -> None:
- """Удаление элемента в новой версии массива по индексу.
+ """
+ Удаление элемента в новой версии массива по индексу.
Удаляет элемент из текущей версии массива по индексу и помещает результат в новую версию.
+
:param index: Индекс элемента, который необходимо удалить.
:raises ValueError: Если индекс выходит за пределы допустимого диапазона.
"""
@@ -112,15 +117,26 @@ def remove(self, index: int) -> None:
raise ValueError("Invalid index")
self.pop(index)
+ def get_version_state(self, version: int) -> np.ndarray:
+ """
+ Получение состояния массива для указанной версии.
+
+ :param version: Номер версии.
+ :return: Состояние массива для указанной версии.
+ """
+ return self.get_version(version)
+
def get_size(self) -> int:
- """Получение текущего размера массива.
+ """
+ Получение текущего размера массива.
:return: Количество элементов в текущей версии массива.
"""
return self.size
- def check_is_empty(self):
- """Проверка, является ли массив пустым в текущей версии.
+ def check_is_empty(self) -> bool:
+ """
+ Проверка, является ли массив пустым в текущей версии.
:return: True, если массив пуст, иначе False.
"""
diff --git a/persistent_data_structures/persistent_list.py b/persistent_data_structures/persistent_list.py
index 75b4fa0..a1b7f5f 100644
--- a/persistent_data_structures/persistent_list.py
+++ b/persistent_data_structures/persistent_list.py
@@ -1,44 +1,41 @@
-from persistent_data_structures.base_persistent import BasePersistent
+from base_persistent import BasePersistent, Node
+from typing import Any, Optional
-class Node:
- """
- Класс для узлов двусвязного списка.
- """
+class ListNode:
+ """Класс узла для хранения состояния в двусвязном списке."""
- def __init__(self, value: any = None, prev: 'Node' = None, next_node: 'Node' = None) -> None:
+ def __init__(self, value: Any = None, prev: Optional['ListNode'] = None,
+ next_node: Optional['ListNode'] = None) -> None:
"""
- Инициализирует новый узел.
+ Инициализация узла списка.
- :param value: Значение для хранения в узле (по умолчанию None).
+ :param value: Значение, которое будет храниться в узле.
:param prev: Ссылка на предыдущий узел (по умолчанию None).
:param next_node: Ссылка на следующий узел (по умолчанию None).
"""
self.value = value
self.prev = prev
self.next_node = next_node
+ self.children = {}
class PersistentLinkedList(BasePersistent):
- """Персистентный двусвязный список.
- Класс PersistentLinkedList реализует неизменяемый двусвязный список
- с возможностью хранения нескольких версий, где каждая
- версия является изменением предыдущей.
- """
+ """Персистентный двусвязный список, использующий базовый класс для версионирования."""
- def __init__(self, initial_state: list = None) -> None:
+ def __init__(self, initial_state: Optional[list[Any]] = None) -> None:
"""
- Инициализирует персистентный двусвязный список.
+ Инициализация персистентного списка.
- :param initial_state: Начальное состояние списка, если оно передано.
- :return: None
+ :param initial_state: Начальный список данных для создания состояния (по умолчанию None).
"""
- super().__init__(None)
+ super().__init__(initial_state)
self.size = 0
head = tail = None
+
if initial_state:
for data in initial_state:
- node = Node(data)
+ node = ListNode(data)
if head is None:
head = tail = node
else:
@@ -46,28 +43,26 @@ def __init__(self, initial_state: list = None) -> None:
node.prev = tail
tail = node
self.size = len(initial_state)
- self._history[0] = (head, tail)
- def add(self, data: any) -> None:
- """
- Добавляет элемент в конец списка в новой версии.
+ self._version_map[0] = Node(head)
- :param data: Данные, которые нужно добавить в список.
- :return: None
- """
+ def add(self, data: Any) -> None:
+ """Добавляет элемент в конец списка в новой версии."""
self._create_new_state()
- head, tail = self._history[self._last_state]
- new_node = Node(data)
- if tail is None:
- head = tail = new_node
- else:
+ head = self._version_map[self._current_version].state
+ tail = self._get_tail(head)
+ new_node = ListNode(data)
+
+ if tail:
tail.next_node = new_node
new_node.prev = tail
- tail = new_node
+ else:
+ head = new_node
+
+ self._version_map[self._current_version].state = head
self.size += 1
- self._history[self._last_state] = (head, tail)
- def add_first(self, data: any) -> None:
+ def add_first(self, data: Any) -> None:
"""
Добавляет элемент в начало списка в новой версии.
@@ -75,32 +70,35 @@ def add_first(self, data: any) -> None:
:return: None
"""
self._create_new_state()
- head, tail = self._history[self._last_state]
- new_node = Node(data, next_node=head)
+ head = self._version_map[self._current_version].state
+ new_node = ListNode(data, next_node=head)
+
if head:
head.prev = new_node
+
head = new_node
- if tail is None:
- tail = new_node
+ self._version_map[self._current_version].state = head
self.size += 1
- self._history[self._last_state] = (head, tail)
- def insert(self, index: int, data: any) -> None:
- """
- Вставляет элемент в список по указанному индексу.
-
- :param index: Индекс, на котором нужно вставить элемент.
- :param data: Данные, которые нужно вставить.
- :return: None
- :raises IndexError: Если индекс выходит за пределы списка.
- """
+ def insert(self, index: int, data: Any) -> None:
+ """Вставляет элемент в список по указанному индексу."""
self._create_new_state()
- head, tail = self._history[self._last_state]
+ head = self._version_map[self._current_version].state
current = head
count = 0
+
+ if index == 0:
+ new_node = ListNode(data, next_node=head)
+ if head:
+ head.prev = new_node
+ head = new_node
+ self._version_map[self._current_version].state = head
+ self.size += 1
+ return
+
while current:
if count == index:
- new_node = Node(data, prev=current.prev, next_node=current)
+ new_node = ListNode(data, prev=current.prev, next_node=current)
if current.prev:
current.prev.next_node = new_node
current.prev = new_node
@@ -112,9 +110,10 @@ def insert(self, index: int, data: any) -> None:
current = current.next_node
else:
raise IndexError("Index out of range")
- self._history[self._last_state] = (head, tail)
- def pop(self, index: int) -> any:
+ self._version_map[self._current_version].state = head
+
+ def pop(self, index: int) -> Any:
"""
Удаление элемента в новой версии списка и возвращение его значения.
@@ -122,9 +121,11 @@ def pop(self, index: int) -> any:
:return: Значение удаленного элемента.
:raises IndexError: Если индекс выходит за пределы списка.
"""
- head, tail = self._history[self._current_state]
+ self._create_new_state()
+ head = self._version_map[self._current_version].state
current = head
count = 0
+
while current:
if count == index:
value = current.value
@@ -134,26 +135,28 @@ def pop(self, index: int) -> any:
current.next_node.prev = current.prev
if current == head:
head = current.next_node
- if current == tail:
- tail = current.prev
- self._create_new_state()
self.size -= 1
- self._history[self._last_state] = (head, tail)
- return value
+ break
count += 1
current = current.next_node
- raise IndexError("Index out of range")
+ else:
+ raise IndexError("Index out of range")
- def remove(self, value: any) -> None:
+ self._version_map[self._current_version].state = head
+ return value
+
+ def remove(self, value: Any) -> None:
"""
Удаляет элемент из списка в новой версии.
- :param data: Данные элемента для удаления.
+ :param value: Данные элемента для удаления.
:return: None
:raises ValueError: Если элемент не найден в списке.
"""
- head, tail = self._history[self._current_state]
+ self._create_new_state()
+ head = self._version_map[self._current_version].state
current = head
+
while current:
if current.value == value:
if current.prev:
@@ -162,16 +165,15 @@ def remove(self, value: any) -> None:
current.next_node.prev = current.prev
if current == head:
head = current.next_node
- if current == tail:
- tail = current.prev
- self._create_new_state()
self.size -= 1
- self._history[self._last_state] = (head, tail)
- return
+ break
current = current.next_node
- raise ValueError(f"Value {value} not found in the list")
+ else:
+ raise ValueError(f"Value {value} not found in the list")
+
+ self._version_map[self._current_version].state = head
- def get(self, version: int = None, index: int = None) -> any:
+ def get(self, version: int = None, index: int = None) -> Any:
"""
Возвращает элемент по индексу из указанной версии.
@@ -182,32 +184,36 @@ def get(self, version: int = None, index: int = None) -> any:
:raises IndexError: Если индекс выходит за пределы списка.
"""
if version is None:
- version = self._current_state
- if version > self._current_state or version < 0:
+ version = self._current_version
+
+ if version not in self._version_map:
raise ValueError(f"Version {version} does not exist")
- head, tail = self._history[version]
- if head is None:
- raise IndexError("Index out of range")
+ head = self._version_map[version].state
current = head
count = 0
+
while current:
if count == index:
return current.value
count += 1
current = current.next_node
+
raise IndexError("Index out of range")
- def clear(self) -> None:
- """
- Очищает список, создавая новую версию.
+ def _get_tail(self, head: ListNode) -> Optional[ListNode]:
+ """Возвращает последний узел в списке."""
+ current = head
+ while current and current.next_node:
+ current = current.next_node
+ return current
- :return: None
- """
+ def clear(self) -> None:
+ """Очищает список, создавая новую версию."""
self._create_new_state()
+ self._version_map[self._current_version].state = None
self.size = 0
- self._history[self._last_state] = (None, None)
- def __getitem__(self, index: int) -> any:
+ def __getitem__(self, index: int) -> Any:
"""
Получение значения элемента из текущей версии списка по индексу.
@@ -215,7 +221,7 @@ def __getitem__(self, index: int) -> any:
:return: Значение элемента в текущей версии списка по заданному индексу.
:raises IndexError: Если индекс выходит за пределы списка.
"""
- head, tail = self._history[self._current_state]
+ head = self._version_map[self._current_version].state
current = head
count = 0
while current:
@@ -225,7 +231,7 @@ def __getitem__(self, index: int) -> any:
current = current.next_node
raise IndexError("Index out of range")
- def __setitem__(self, index: int, value: any) -> None:
+ def __setitem__(self, index: int, value: Any) -> None:
"""
Обновление значения элемента в новой версии списка по индексу.
@@ -245,7 +251,61 @@ def __setitem__(self, index: int, value: any) -> None:
current = current.next_node
else:
raise IndexError("Index out of range")
- self._history[self._last_state] = (head, tail)
+ self._version_map[self._last_state] = (head, tail)
+
+ def _deep_copy_list(self, head: ListNode) -> Optional[ListNode]:
+ """Глубокое копирование списка с его узлами."""
+ if not head:
+ return None
+
+ new_head = ListNode(head.value)
+ current = head.next_node
+ new_current = new_head
+
+ while current:
+ new_node = ListNode(current.value)
+ new_current.next_node = new_node
+ new_node.prev = new_current
+ new_current = new_node
+ current = current.next_node
+
+ return new_head
+
+ def set_version(self, version: int) -> None:
+ """
+ Обновляет текущую версию персистентной структуры данных до
+ указанной для двусвязного списка.
+
+ :param version: Номер версии.
+ :raises ValueError: Если указанная версия не существует.
+ """
+ if version not in self._version_map:
+ raise ValueError(f'Version "{version}" does not exist')
+ self._current_version = version
+ self.root = self._version_map[version]
+ head = self._version_map[self._current_version].state
+ self.size: int = 0
+ current = head
+ while current:
+ self.size += 1
+ current = current.next_node
+ self._version_map[self._current_version].state = head
+
+ def _create_new_state(self) -> None:
+ """
+ Создает новую версию состояния для двусвязного списка,
+ минимизируя дублирование данных.
+
+ Этот метод копирует состояние двусвязного списка, создавая
+ новый узел с минимальным дублированием
+ данных, чтобы сохранить версионность структуры данных.
+
+ :raises ValueError: Если текущая версия не существует в карте версий.
+ """
+ self._current_version += 1
+ head = self._version_map[self._current_version - 1].state
+ new_head = self._deep_copy_list(head)
+ self._version_map[self._current_version] = Node(new_head)
def get_size(self) -> int:
"""
@@ -261,5 +321,15 @@ def check_is_empty(self) -> bool:
:return: True, если список пуст, иначе False.
"""
- head, tail = self._history[self._current_state]
+ head = self._version_map[self._current_version].state
return head is None
+
+ def __str__(self) -> str:
+ """Отображение списка для вывода."""
+ head = self._version_map[self._current_version].state
+ result = []
+ current = head
+ while current:
+ result.append(current.value)
+ current = current.next_node
+ return '->'.join(map(str, result))
diff --git a/persistent_data_structures/persistent_map.py b/persistent_data_structures/persistent_map.py
index 9efb825..41e629d 100644
--- a/persistent_data_structures/persistent_map.py
+++ b/persistent_data_structures/persistent_map.py
@@ -1,66 +1,69 @@
-from persistent_data_structures.base_persistent import BasePersistent
+from base_persistent import BasePersistent
+from typing import Any
class PersistentMap(BasePersistent):
"""Персистентный ассоциативный массив.
- Представляет собой словарь, который сохраняет историю изменений.
+ Представляет собой словарь, сохраняющий историю изменений.
"""
- def __init__(self, initial_state: dict = {}) -> None:
+
+ def __init__(self, initial_state: dict[Any, Any] = {}) -> None:
"""Инициализирует персистентный ассоциативный массив.
:param initial_state: Начальное состояние персистентной структуры данных.
"""
super().__init__(initial_state)
- def __setitem__(self, key: any, value: any) -> None:
- """Обновляет или создает элемент по указанному ключу в новой версии.
+ def __setitem__(self, key: Any, value: Any) -> None:
+ """Обновляет или создаёт элемент по указанному ключу в новой версии.
- :param key: Ключ
- :param value: Значение
+ :param key: Ключ, который нужно обновить или создать.
+ :param value: Значение, связанное с указанным ключом.
"""
self._create_new_state()
- self._history[self._last_state][key] = value
+ self._version_map[self._last_version].state[key] = value
- def __getitem__(self, key: any) -> any:
+ def __getitem__(self, key: Any) -> Any:
"""Возвращает элемент текущей версии по указанному ключу.
- :param key: Ключ
- :return: Значение сответствующее указанному ключу или None, если ключ не существует."""
- return self._history[self._current_state][key]
+ :param key: Ключ элемента, который нужно получить.
+ :return: Значение, соответствующее указанному ключу, или None, если ключ не существует.
+ """
+ return self._version_map[self._current_version].state.get(key)
- def get(self, version: int, key: any) -> any:
- """Возвращает элемент с указанной версией и ключом.
+ def get(self, version: int, key: Any) -> Any:
+ """Возвращает элемент из указанной версии по ключу.
- :param version: Номер версии
- :param key: Ключ
- :return: Значение сответствующее указанному ключу или None, если ключ не существует.
- :raises ValueError: Если версия не существует
- :raises KeyError: Если ключ не существует
+ :param version: Номер версии.
+ :param key: Ключ элемента, который нужно получить.
+ :return: Значение, соответствующее указанному ключу.
+ :raises ValueError: Если версия не существует.
+ :raises KeyError: Если ключ не существует.
"""
- if version > self._current_state or version < 0:
+ if version not in self._version_map:
raise ValueError(f'Version "{version}" does not exist')
- if key not in self._history[version]:
+ if key not in self._version_map[version].state:
raise KeyError(f'Key "{key}" does not exist')
- return self._history[version]
+ return self._version_map[version].state[key]
- def pop(self, key: any) -> any:
- """Удаляет элемент по указанному ключу и возвращает его.
+ def pop(self, key: Any) -> Any:
+ """Удаляет и возвращает элемент по указанному ключу.
- :param key: Ключ
- :return: Удаленный элемент
+ :param key: Ключ элемента, который нужно удалить.
+ :return: Удалённый элемент.
"""
self._create_new_state()
- return self._history[self._last_state].pop(key)
+ return self._version_map[self._last_version].state.pop(key)
- def remove(self, key: any) -> None:
+ def remove(self, key: Any) -> None:
"""Удаляет элемент по указанному ключу в новой версии.
- :param key: Ключ
+ :param key: Ключ элемента, который нужно удалить.
"""
self.pop(key)
def clear(self) -> None:
"""Очищает ассоциативный массив в новой версии."""
self._create_new_state()
- self._history[self._current_state] = {}
+ self._version_map[self._current_version].state.clear()
diff --git a/tests/test_array.py b/tests/test_array.py
index 00b2cca..7391072 100644
--- a/tests/test_array.py
+++ b/tests/test_array.py
@@ -1,5 +1,4 @@
import pytest
-
from persistent_array import PersistentArray
@@ -11,14 +10,19 @@ def persistent_array():
def test_initial_state(persistent_array):
- """Тест1. Проверка начального состояния массива"""
+ """Тест 1. Проверка начального состояния массива"""
assert persistent_array.get_size() == 5
assert persistent_array[0] == 0
assert persistent_array[4] == 0
+def test_initial_size(persistent_array):
+ """Тест 2. Проверка начального размера массива"""
+ assert persistent_array.get_size() == 5
+
+
def test_get_version(persistent_array):
- """Тест 2. Проверка получения состояния на определенной версии"""
+ """Тест 3. Проверка извлечения элемента по версии"""
persistent_array.add(1)
persistent_array.add(2)
@@ -27,26 +31,37 @@ def test_get_version(persistent_array):
assert persistent_array.get(2, 6) == 2
-def test_update_version(persistent_array):
- """Тест 3. Проверка обновления текущей версии"""
+def test_set_version(persistent_array):
+ """Тест 4. Проверка обновления текущей версии"""
persistent_array.add(1)
persistent_array.add(2)
- persistent_array.update_version(1)
+ persistent_array.set_version(1)
assert persistent_array[5] == 1
- persistent_array.update_version(2)
+ persistent_array.set_version(2)
assert persistent_array[6] == 2
def test_add_element(persistent_array):
- """Тест 4. Проверка добавления элемента в массив"""
+ """Тест 5. Проверка добавления элемента в массив"""
persistent_array.add(10)
assert persistent_array.get_size() == 6
assert persistent_array[5] == 10
+def test_multiple_add_elements(persistent_array):
+ """Тест 6. Проверка добавления нескольких элементов"""
+ persistent_array.add(10)
+ persistent_array.add(20)
+ persistent_array.add(30)
+ assert persistent_array.get_size() == 8
+ assert persistent_array[5] == 10
+ assert persistent_array[6] == 20
+ assert persistent_array[7] == 30
+
+
def test_pop_element(persistent_array):
- """Тест 5. Проверка удаления элемента из массива"""
+ """Тест 7. Проверка удаления элемента из массива"""
persistent_array.add(10)
persistent_array.add(20)
removed_value = persistent_array.pop(1)
@@ -56,7 +71,7 @@ def test_pop_element(persistent_array):
def test_insert_element(persistent_array):
- """Тест 6. Проверка вставки элемента в массив по индексу"""
+ """Тест 8. Проверка вставки элемента по указанному индексу"""
persistent_array.add(10)
persistent_array.insert(2, 15)
assert persistent_array.get_size() == 7
@@ -64,55 +79,97 @@ def test_insert_element(persistent_array):
def test_remove_element(persistent_array):
- """Тест 7. Проверка удаления элемента по индексу"""
+ """Тест 9. Проверка удаления элемента из массива по индексу"""
persistent_array.add(10)
persistent_array.add(20)
- persistent_array.remove(0)
+ persistent_array.remove(1)
assert persistent_array.get_size() == 6
- assert persistent_array[0] == 0
+ assert persistent_array[1] == 0
+
+def test_add_and_remove_elements(persistent_array):
+ """Тест 10. Проверка добавления и удаления элементов"""
+ persistent_array.add(10)
+ persistent_array.add(20)
+ persistent_array.add(30)
+ assert persistent_array.get_size() == 8
+ persistent_array.remove(5)
+ assert persistent_array.get_size() == 7
+
+
+def test_versioning(persistent_array):
+ """Тест 11. Проверка работы с версиями массива"""
+ persistent_array.add(10)
+ persistent_array.add(20)
+ assert persistent_array.get_version_state(1)[5] == 10
+ assert persistent_array.get_version_state(2)[6] == 20
+ persistent_array.set_version(1)
+ assert persistent_array[5] == 10
+ persistent_array.set_version(2)
+ assert persistent_array[6] == 20
+
+
+def test_invalid_index_in_version(persistent_array):
+ """Тест 12. Проверка недействительного индекса для версии"""
+ persistent_array.add(10)
+ persistent_array.add(20)
+ persistent_array.add(30)
+ with pytest.raises(ValueError):
+ persistent_array.get(1, 10)
-def test_set_item(persistent_array):
- """Тест 8. Проверка обновления элемента в массиве по индексу"""
- persistent_array[0] = 99
- assert persistent_array[0] == 99
+
+def test_get_size(persistent_array):
+ """Тест 13. Проверка размерности массива"""
assert persistent_array.get_size() == 5
+ persistent_array.add(10)
+ assert persistent_array.get_size() == 6
def test_check_is_empty(persistent_array):
- """Тест 9. Проверка, является ли массив пустым"""
+ """Тест 14. Проверка на пустоту"""
assert not persistent_array.check_is_empty()
- persistent_array.remove(0)
- persistent_array.remove(0)
- persistent_array.remove(0)
- persistent_array.remove(0)
+ persistent_array.remove(4)
+ persistent_array.remove(3)
+ persistent_array.remove(2)
+ persistent_array.remove(1)
persistent_array.remove(0)
assert persistent_array.check_is_empty()
-def test_invalid_index_get(persistent_array):
- """Тест 10. Проверка на исключение для недопустимого индекса при получении"""
+def test_single_element_operations(persistent_array):
+ """Тест 15. Проверка операций с массивом из одного элемента"""
+ persistent_array.add(10)
+ persistent_array.remove(0)
+ assert persistent_array.get_size() == 5
+ assert persistent_array[0] == 0
+
+
+def test_invalid_index_getitem(persistent_array):
+ """Тест 16. Проверка недействительного доступа по индексу для getitem"""
with pytest.raises(ValueError):
persistent_array[10]
-def test_invalid_index_set(persistent_array):
- """Тест 11. Проверка на исключение для недопустимого индекса при обновлении"""
+def test_invalid_index_setitem(persistent_array):
+ """Тест 17. Проверка недействительного доступа по индексу для setitem"""
with pytest.raises(ValueError):
- persistent_array[10] = 5
+ persistent_array[10] = 10
-def test_invalid_version_get(persistent_array):
- """Тест 12. Проверка на исключение для недопустимой версии при получении"""
- persistent_array.add(1)
- persistent_array.add(2)
+def test_invalid_index_pop(persistent_array):
+ """Тест 18. Проверка недействительного доступа по индексу для pop"""
with pytest.raises(ValueError):
- persistent_array.get(10, 0)
+ persistent_array.pop(10)
-def test_invalid_version_update(persistent_array):
- """Тест 13. Проверка на исключение для недопустимой версии при обновлении"""
- persistent_array.add(1)
- persistent_array.add(2)
+def test_invalid_index_insert(persistent_array):
+ """Тест 19. Проверка недействительного доступа по индексу для insert"""
with pytest.raises(ValueError):
- persistent_array.update_version(10)
+ persistent_array.insert(10, 10)
+
+
+def test_invalid_version_get(persistent_array):
+ """Тест 20. Проверка недействительного доступа по индексу для get"""
+ persistent_array.add(10)
+ with pytest.raises(ValueError):
+ persistent_array.get(10, 0)
diff --git a/tests/test_list.py b/tests/test_list.py
index 75f6742..eacfd6a 100644
--- a/tests/test_list.py
+++ b/tests/test_list.py
@@ -1,97 +1,138 @@
import pytest
-
from persistent_list import PersistentLinkedList
# Тестирование методов класса PersistentLinkedList
@pytest.fixture
-def linked_list():
- """
- Фикстура для создания экземпляра персистентного двусвязного списка.
- """
- return PersistentLinkedList([1, 2, 3, 4, 5])
+def persistent_list():
+ """Инициализация персистентного двусвязного списка с начальными данными"""
+ return PersistentLinkedList([1, 2, 3, 4])
+
+
+def test_initial_state(persistent_list):
+ """Тест 1. Проверка начального состояния списка"""
+ assert persistent_list.get_size() == 4
+ assert persistent_list.get(version=0, index=0) == 1
+
+
+def test_add(persistent_list):
+ """Тест 2. Проверка добавления элемента в конец списка"""
+ persistent_list.add(5)
+ assert persistent_list.get_size() == 5
+ assert persistent_list.__getitem__(4) == 5
+
+
+def test_add_first(persistent_list):
+ """Тест 3. Проверка добавления элемента в начало списка"""
+ persistent_list.add_first(0)
+ assert persistent_list.get_size() == 5
+ assert persistent_list.__getitem__(0) == 0
+
+
+def test_insert(persistent_list):
+ """Тест 4. Проверка вставки элемента в список по индексу"""
+ persistent_list.insert(2, 99)
+ assert persistent_list.get_size() == 5
+ assert persistent_list.__getitem__(2) == 99
+ assert persistent_list.__getitem__(3) == 3
+
+
+def test_pop(persistent_list):
+ """Тест 5. Проверка удаления элемента по индексу"""
+ removed_value = persistent_list.pop(1)
+ assert removed_value == 2
+ assert persistent_list.get_size() == 3
+ with pytest.raises(IndexError):
+ persistent_list.get(1)
+
+
+def test_remove(persistent_list):
+ """Тест 6. Проверка удаления элемента по значению"""
+ persistent_list.remove(3)
+ assert persistent_list.get_size() == 3
+ with pytest.raises(ValueError):
+ persistent_list.remove(10)
+
+
+def test_add_multiple(persistent_list):
+ """Тест 7. Проверка добавления нескольких элементов в конец списка"""
+ persistent_list.add(5)
+ persistent_list.add(6)
+ persistent_list.add(7)
+ assert persistent_list.get_size() == 7
+ assert persistent_list.__getitem__(4) == 5
+ assert persistent_list.__getitem__(5) == 6
+ assert persistent_list.__getitem__(6) == 7
+
+
+def test_insert_at_start(persistent_list):
+ """Тест 8. Проверка вставки элемента в начало списка"""
+ persistent_list.insert(0, -1)
+ assert persistent_list.get_size() == 5
+ assert persistent_list.__getitem__(0) == -1
+
+
+def test_insert_out_of_range(persistent_list):
+ """Тест 9. Проверка вставки элемента за пределами допустимого диапазона индексов"""
+ with pytest.raises(IndexError):
+ persistent_list.insert(10, 100)
+
+
+def test_remove_non_existing_value(persistent_list):
+ """Тест 10. Проверка удаления несуществующего элемента из списка"""
+ with pytest.raises(ValueError):
+ persistent_list.remove(99)
+
+
+def test_pop_multiple(persistent_list):
+ """Тест 11. Проверка удаления нескольких элементов по индексу"""
+ persistent_list.pop(0)
+ persistent_list.pop(1)
+ assert persistent_list.get_size() == 2
+ assert persistent_list.__getitem__(0) == 2
+ assert persistent_list.__getitem__(1) == 4
+
+
+def test_set_version(persistent_list):
+ """Тест 12. Проверка переключения между версиями"""
+ persistent_list.add(5)
+ persistent_list.add(6)
+ persistent_list.set_version(0)
+ assert persistent_list.get_size() == 4
+ persistent_list.set_version(2)
+ assert persistent_list.get_size() == 6
-def test_add(linked_list):
- """Тест 1. Проверка добавления элемента в конец списка"""
- linked_list.add(6)
- assert linked_list.get(index=5) == 6
+def test_get_invalid_index(persistent_list):
+ """Тест 12. Проверка ошибки при попытке получить элемент по недопустимому индексу"""
+ with pytest.raises(IndexError):
+ persistent_list.get(index=100)
-def test_add_first(linked_list):
- """Тест 2. Проверка добавления элемента в начало списка"""
- linked_list.add_first(0)
- assert linked_list.get(index=0) == 0
+def test_get_version(persistent_list):
+ """Тест 13. Проверка получения состояния списка на определенной версии"""
+ persistent_list.add(5)
+ persistent_list.add(6)
+ assert persistent_list.get(version=0, index=3) == 4
+ assert persistent_list.get(version=2, index=4) == 5
-def test_insert(linked_list):
- """Тест 3. Проверка вставки элемента по индексу"""
- linked_list.insert(2, 10)
- assert linked_list.get(index=2) == 10
- assert linked_list.get(index=3) == 3
+def test_clear(persistent_list):
+ """Тест 14. Проверка очистки списка"""
+ persistent_list.clear()
+ assert persistent_list.get_size() == 0
+ with pytest.raises(IndexError):
+ persistent_list.get(0)
-def test_pop(linked_list):
- """Тест 4. Проверка удаления элемента по индексу"""
- removed_value = linked_list.pop(2)
- assert removed_value == 3
- assert linked_list.get(index=2) == 4
+def test_check_is_empty(persistent_list):
+ """Тест 15. Проверка пуст ли список"""
+ assert not persistent_list.check_is_empty()
+ persistent_list.clear()
+ assert persistent_list.check_is_empty()
-def test_remove(linked_list):
- """Тест 5. Проверка удаления элемента по значению"""
- linked_list.remove(4)
+def test_get_invalid_version(persistent_list):
+ """Тест 16. Проверка ошибки при попытке получить версию, которой не существует"""
with pytest.raises(ValueError):
- linked_list.remove(4)
-
-
-def test_get(linked_list):
- """Тест 6. Проверка получения элемента по индексу"""
- assert linked_list.get(index=0) == 1
- assert linked_list.get(index=4) == 5
-
-
-def test_get_version(linked_list):
- """Тест 7. Проверка получения версии списка"""
- linked_list.add(6)
- linked_list.add(7)
- version_1 = linked_list.get_version(1)
- assert version_1[0] is not None
- version_0_values = []
- current = version_1[0]
- while current:
- version_0_values.append(current.value)
- current = current.next_node
- assert version_0_values == [1, 2, 3, 4, 5, 6]
-
-
-def test_update_version(linked_list):
- """Тест 8. Проверка обновления версии списка"""
- linked_list.add(6)
- linked_list.add(7)
- linked_list.update_version(1)
- assert linked_list.get(index=5) == 6
- linked_list.update_version(0)
- assert linked_list.get(index=4) == 5
-
-
-def test_clear(linked_list):
- """Тест 9. Проверка очистки списка"""
- linked_list.clear()
- assert linked_list.get_size() == 0
- assert linked_list.check_is_empty()
-
-
-def test_get_size(linked_list):
- """Тест 10. Проверка получения размера списка"""
- assert linked_list.get_size() == 5
- linked_list.add(6)
- assert linked_list.get_size() == 6
-
-
-def test_check_is_empty(linked_list):
- """Тест 11. Проверка метода для проверки пустоты списка"""
- linked_list.clear()
- assert linked_list.check_is_empty() is True
- linked_list.add(10)
- assert linked_list.check_is_empty() is False
+ persistent_list.get_version(999)
diff --git a/tests/test_map.py b/tests/test_map.py
index 580e6b6..5daee00 100644
--- a/tests/test_map.py
+++ b/tests/test_map.py
@@ -1,5 +1,4 @@
import pytest
-
from persistent_map import PersistentMap
@@ -13,18 +12,18 @@ def persistent_map():
def test_get_version(persistent_map):
"""Тест 1. Проверка получения состояния на определенной версии"""
persistent_map['c'] = 3
- persistent_map.update_version(1)
+ persistent_map.set_version(1)
assert persistent_map['c'] == 3
- persistent_map.update_version(0)
+ persistent_map.set_version(0)
with pytest.raises(KeyError, match='Key "c" does not exist'):
persistent_map.get(0, 'c')
-def test_update_version(persistent_map):
+def test_set_version(persistent_map):
"""Тест 2. Проверка обновления версии"""
persistent_map['c'] = 3
- persistent_map.update_version(1)
+ persistent_map.set_version(1)
assert persistent_map['c'] == 3
@@ -56,28 +55,80 @@ def test_pop(persistent_map):
"""Тест 7. Проверка удаления элемента с использованием pop"""
value = persistent_map.pop('a')
assert value == 1
- assert 'a' not in persistent_map._history[persistent_map._current_state]
+ assert 'a' not in persistent_map._version_map[persistent_map._current_version].state
def test_remove(persistent_map):
"""Тест 8. Проверка удаления элемента с использованием remove"""
persistent_map.remove('a')
- assert 'a' not in persistent_map._history[persistent_map._current_state]
+ assert 'a' not in persistent_map._version_map[persistent_map._current_version].state
def test_clear(persistent_map):
"""Тест 9. Проверка очистки структуры данных"""
persistent_map.clear()
- assert persistent_map._history[persistent_map._current_state] == {}
+ assert persistent_map._version_map[persistent_map._current_version].state == {}
def test_version_history(persistent_map):
"""Тест 10. Проверка истории версий"""
persistent_map['c'] = 3
- persistent_map.update_version(1)
+ persistent_map.set_version(1)
persistent_map['d'] = 4
- persistent_map.update_version(2)
+ persistent_map.set_version(2)
assert persistent_map.get_version(0) == {'a': 1, 'b': 2}
assert persistent_map.get_version(1) == {'a': 1, 'b': 2, 'c': 3}
assert persistent_map.get_version(2) == {'a': 1, 'b': 2, 'c': 3, 'd': 4}
+
+
+def test_create_new_state(persistent_map):
+ """Тест 11. Проверка создания новой версии с минимальным дублированием данных"""
+ initial_state = persistent_map._version_map[persistent_map._current_version].state.copy()
+ persistent_map['c'] = 3
+ assert persistent_map._version_map[persistent_map._current_version].state != initial_state
+ assert initial_state == {'a': 1, 'b': 2}
+
+
+def test_multiple_versions(persistent_map):
+ """Тест 12. Проверка изменения на несколько версий"""
+ persistent_map['c'] = 3
+ persistent_map.set_version(1)
+ persistent_map['d'] = 4
+ persistent_map.set_version(2)
+
+ persistent_map['e'] = 5
+ persistent_map.set_version(3)
+
+ assert persistent_map.get_version(1) == {'a': 1, 'b': 2, 'c': 3}
+ assert persistent_map.get_version(2) == {'a': 1, 'b': 2, 'c': 3, 'd': 4}
+ assert persistent_map.get_version(3) == {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
+
+
+def test_setitem_overwrite(persistent_map):
+ """Тест 13. Проверка перезаписи существующего элемента"""
+ persistent_map['a'] = 10
+ assert persistent_map['a'] == 10
+
+ persistent_map['a'] = 20
+ assert persistent_map['a'] == 20
+
+
+def test_persistent_state_integrity(persistent_map):
+ """Тест 14. Проверка целостности состояния после последовательных изменений"""
+ persistent_map['c'] = 3
+ version_1 = persistent_map.get_version(1)
+
+ persistent_map['d'] = 4
+ version_2 = persistent_map.get_version(2)
+
+ assert version_1 == {'a': 1, 'b': 2, 'c': 3}
+ assert version_2 == {'a': 1, 'b': 2, 'c': 3, 'd': 4}
+ assert version_1 != version_2
+
+
+def test_get_non_existing_key_version(persistent_map):
+ """Тест 15. Проверка получения несуществующего ключа в существующей версии"""
+ persistent_map.set_version(0)
+ with pytest.raises(KeyError, match='Key "c" does not exist'):
+ persistent_map.get(0, 'c')