Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
370 changes: 16 additions & 354 deletions README.md

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions persistent_data_structures/base_persistent.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def __init__(self, initial_state=None) -> None:
self._history = {0: initial_state}
self._current_state = 0
self._last_state = 0
self._container = None
self._location = None

def get_version(self, version):
"""Возвращает состояние персистентной структуры данных на указанной версии.
Expand All @@ -37,8 +39,33 @@ def update_version(self, version):
raise ValueError(f'Version "{version}" does not exist')
self._current_state = version

def undo(self):
"""Отменяет последнее изменение."""
if self._container is not None:
raise NotImplementedError(f'Cannot undo inside container "{self._container}"')
if self._current_state > 0:
self._current_state -= 1

def redo(self):
"""Отменяет отмененное изменение."""
if self._container is not None:
raise NotImplementedError(f'Cannot redo inside container "{self._container}"')
if self._current_state < self._last_state:
self._current_state += 1

def _create_new_state(self) -> None:
"""Создает новую версию."""
# Персистентные структуры могут быть вложенными,
# поэтому нужно сохранять историю изменений в родительской персистентной структуре
if self._container is not None:
# В текущей версии родительской структуры подменяем имеющуюся вложенную структуру
# на ее копию, таким образом изменения во вложенной структуре не будут отражаться на
# прощлых версиях родительской структуры
self._container._history[
self._container._current_state
][self._location] = deepcopy(self)
self._last_state += 1
self._history[self._last_state] = deepcopy(self._history[self._current_state])
self._current_state = self._last_state
if self._container is not None:
self._container[self._location] = self
17 changes: 17 additions & 0 deletions persistent_data_structures/persistent_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ def add(self, value: any) -> None:
:param value (int): Значение нового элемента, который добавляется в массив.
"""
self._create_new_state()
if isinstance(value, BasePersistent):
value._container = self
value._location = self.size
self._history[self._last_state] = np.append(self._history[self._last_state], value)
self.size += 1

Expand All @@ -70,6 +73,10 @@ def pop(self, index: int) -> any:
self._create_new_state()
self._history[self._last_state] = np.delete(self._history[self._last_state], index)
self.size -= 1
# Сдвигаем индексы всех элементов BasePersistent после удаленного элемента
for i in range(index, self.size):
if isinstance(self._history[self._current_state][i], BasePersistent):
self._history[self._current_state][i]._location -= 1
return removed_element

def __setitem__(self, index: int, value: any) -> None:
Expand All @@ -83,6 +90,9 @@ def __setitem__(self, index: int, value: any) -> None:
"""
if index < 0 or index >= self.size:
raise ValueError("Invalid index")
if isinstance(value, BasePersistent):
value._container = self
value._location = index
self._create_new_state()
self._history[self._last_state][index] = value

Expand All @@ -97,9 +107,16 @@ def insert(self, index: int, value: any) -> None:
"""
if index < 0 or index > self.size:
raise ValueError("Invalid index")
if isinstance(value, BasePersistent):
value._container = self
value._location = index
self._create_new_state()
self._history[self._last_state] = np.insert(self._history[self._last_state], index, value)
self.size += 1
# Сдвигаем индексы всех элементов BasePersistent после добавленного элемента
for i in range(index+1, self.size):
if isinstance(self._history[self._current_state][i], BasePersistent):
self._history[self._current_state][i]._location += 1

def remove(self, index: int) -> None:
"""Удаление элемента в новой версии массива по индексу.
Expand Down
72 changes: 42 additions & 30 deletions persistent_data_structures/persistent_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ def add(self, data: any) -> None:
"""
self._create_new_state()
head, tail = self._history[self._last_state]
if isinstance(data, BasePersistent):
data._container = self
data._location = self.size
new_node = Node(data)
if tail is None:
head = tail = new_node
Expand All @@ -76,13 +79,23 @@ def add_first(self, data: any) -> None:
"""
self._create_new_state()
head, tail = self._history[self._last_state]
if isinstance(data, BasePersistent):
data._container = self
data._location = 0
new_node = Node(data, next_node=head)
if head:
head.prev = new_node
head = new_node
if tail is None:
tail = new_node
self.size += 1
# сдвигаем индексы персистентных элементов
current = head
while current.next_node:
current = current.next_node
if isinstance(current.value, BasePersistent):
current.value._location += 1

self._history[self._last_state] = (head, tail)

def insert(self, index: int, data: any) -> None:
Expand All @@ -94,8 +107,15 @@ def insert(self, index: int, data: any) -> None:
:return: None
:raises IndexError: Если индекс выходит за пределы списка.
"""
if index < 0 or index > self.size:
raise IndexError("Index out of range")

self._create_new_state()
head, tail = self._history[self._last_state]
if isinstance(data, BasePersistent):
data._container = self
data._location = index

current = head
count = 0
while current:
Expand All @@ -107,11 +127,12 @@ def insert(self, index: int, data: any) -> None:
if current == head:
head = new_node
self.size += 1
break
count += 1

if isinstance(current.value, BasePersistent) and count > index:
current.value._location += 1
count += 1
current = current.next_node
else:
raise IndexError("Index out of range")
self._history[self._last_state] = (head, tail)

def pop(self, index: int) -> any:
Expand All @@ -122,12 +143,15 @@ def pop(self, index: int) -> any:
:return: Значение удаленного элемента.
:raises IndexError: Если индекс выходит за пределы списка.
"""
head, tail = self._history[self._current_state]
if index < 0 or index >= self.size:
raise IndexError("Index out of range")
self._create_new_state()
head, tail = self._history[self._last_state]
popped_value = self.__getitem__(index)
current = head
count = 0
while current:
if count == index:
value = current.value
if current.prev:
current.prev.next_node = current.next_node
if current.next_node:
Expand All @@ -136,40 +160,25 @@ def pop(self, index: int) -> any:
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

if isinstance(current.value, BasePersistent) and count > index:
current.value._location -= 1
count += 1
current = current.next_node
raise IndexError("Index out of range")
return popped_value

def remove(self, value: any) -> None:
def remove(self, index: int) -> None:
"""
Удаляет элемент из списка в новой версии.
Удаляет элемент по индексу из списка в новой версии.

:param data: Данные элемента для удаления.
:param index: Индекс элемента для удаления.
:return: None
:raises ValueError: Если элемент не найден в списке.
:raises IndexError: Если индекс выходит за пределы списка.
"""
head, tail = self._history[self._current_state]
current = head
while current:
if current.value == value:
if current.prev:
current.prev.next_node = current.next_node
if current.next_node:
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
current = current.next_node
raise ValueError(f"Value {value} not found in the list")
self.pop(index)

def get(self, version: int = None, index: int = None) -> any:
"""
Expand Down Expand Up @@ -234,6 +243,9 @@ def __setitem__(self, index: int, value: any) -> None:
:raises IndexError: Если индекс выходит за пределы списка.
"""
self._create_new_state()
if isinstance(value, BasePersistent):
value._container = self
value._location = index
head, tail = self._history[self._last_state]
current = head
count = 0
Expand Down
10 changes: 8 additions & 2 deletions persistent_data_structures/persistent_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def __setitem__(self, key: any, value: any) -> None:
:param value: Значение
"""
self._create_new_state()
if isinstance(value, BasePersistent):
value._container = self
value._location = key
self._history[self._last_state][key] = value

def __getitem__(self, key: any) -> any:
Expand Down Expand Up @@ -50,8 +53,11 @@ def pop(self, key: any) -> any:
:param key: Ключ
:return: Удаленный элемент
"""
if key not in self._history[self._current_state]:
raise KeyError(f'Key "{key}" does not exist')
self._create_new_state()
return self._history[self._last_state].pop(key)
popped_item = self._history[self._last_state].pop(key)
return popped_item

def remove(self, key: any) -> None:
"""Удаляет элемент по указанному ключу в новой версии.
Expand All @@ -63,4 +69,4 @@ def remove(self, key: any) -> None:
def clear(self) -> None:
"""Очищает ассоциативный массив в новой версии."""
self._create_new_state()
self._history[self._current_state] = {}
self._history[self._last_state] = {}
6 changes: 3 additions & 3 deletions tests/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ def test_pop(linked_list):

def test_remove(linked_list):
"""Тест 5. Проверка удаления элемента по значению"""
linked_list.remove(4)
with pytest.raises(ValueError):
linked_list.remove(4)
linked_list.remove(1)
with pytest.raises(IndexError):
linked_list.remove(8)


def test_get(linked_list):
Expand Down
Loading