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
18 changes: 18 additions & 0 deletions persistent_data_structures/base_persistent.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from copy import deepcopy
from threading import Lock


class BasePersistent:
Expand All @@ -15,6 +16,7 @@ def __init__(self, initial_state=None) -> None:
self._history = {0: initial_state}
self._current_state = 0
self._last_state = 0
self._mutex = Lock()

def get_version(self, version):
"""Возвращает состояние персистентной структуры данных на указанной версии.
Expand All @@ -35,7 +37,23 @@ def update_version(self, version):
"""
if version < 0 or version >= len(self._history):
raise ValueError(f'Version "{version}" does not exist')
self._mutex.acquire()
self._current_state = version
self._mutex.release()

def undo(self):
"""Отменяет последнее изменение."""
if self._current_state > 0:
self._mutex.acquire()
self._current_state -= 1
self._mutex.release()

def redo(self):
"""Отменяет отмененное изменение."""
if self._current_state < self._last_state:
self._mutex.acquire()
self._current_state += 1
self._mutex.release()

def _create_new_state(self) -> None:
"""Создает новую версию."""
Expand Down
8 changes: 8 additions & 0 deletions persistent_data_structures/persistent_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ def add(self, value: any) -> None:

:param value (int): Значение нового элемента, который добавляется в массив.
"""
self._mutex.acquire()
self._create_new_state()
self._history[self._last_state] = np.append(self._history[self._last_state], value)
self.size += 1
self._mutex.release()

def pop(self, index: int) -> any:
"""Удаление элемента в новой версии массива и возвращение его значения.
Expand All @@ -66,10 +68,12 @@ def pop(self, index: int) -> any:
"""
if index < 0 or index >= self.size:
raise ValueError("Invalid index")
self._mutex.acquire()
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)
self.size -= 1
self._mutex.release()
return removed_element

def __setitem__(self, index: int, value: any) -> None:
Expand All @@ -83,8 +87,10 @@ def __setitem__(self, index: int, value: any) -> None:
"""
if index < 0 or index >= self.size:
raise ValueError("Invalid index")
self._mutex.acquire()
self._create_new_state()
self._history[self._last_state][index] = value
self._mutex.release()

def insert(self, index: int, value: any) -> None:
"""Вставка нового элемента в массив в указанную позицию в новой версии.
Expand All @@ -97,9 +103,11 @@ def insert(self, index: int, value: any) -> None:
"""
if index < 0 or index > self.size:
raise ValueError("Invalid index")
self._mutex.acquire()
self._create_new_state()
self._history[self._last_state] = np.insert(self._history[self._last_state], index, value)
self.size += 1
self._mutex.release()

def remove(self, index: int) -> None:
"""Удаление элемента в новой версии массива по индексу.
Expand Down
16 changes: 16 additions & 0 deletions persistent_data_structures/persistent_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def add(self, data: any) -> None:
:param data: Данные, которые нужно добавить в список.
:return: None
"""
self._mutex.acquire()
self._create_new_state()
head, tail = self._history[self._last_state]
new_node = Node(data)
Expand All @@ -66,6 +67,7 @@ def add(self, data: any) -> None:
tail = new_node
self.size += 1
self._history[self._last_state] = (head, tail)
self._mutex.release()

def add_first(self, data: any) -> None:
"""
Expand All @@ -74,6 +76,7 @@ def add_first(self, data: any) -> None:
:param data: Данные, которые нужно добавить в начало списка.
:return: None
"""
self._mutex.acquire()
self._create_new_state()
head, tail = self._history[self._last_state]
new_node = Node(data, next_node=head)
Expand All @@ -84,6 +87,7 @@ def add_first(self, data: any) -> None:
tail = new_node
self.size += 1
self._history[self._last_state] = (head, tail)
self._mutex.release()

def insert(self, index: int, data: any) -> None:
"""
Expand All @@ -94,6 +98,7 @@ def insert(self, index: int, data: any) -> None:
:return: None
:raises IndexError: Если индекс выходит за пределы списка.
"""
self._mutex.acquire()
self._create_new_state()
head, tail = self._history[self._last_state]
current = head
Expand All @@ -113,6 +118,7 @@ def insert(self, index: int, data: any) -> None:
else:
raise IndexError("Index out of range")
self._history[self._last_state] = (head, tail)
self._mutex.release()

def pop(self, index: int) -> any:
"""
Expand All @@ -122,6 +128,7 @@ def pop(self, index: int) -> any:
:return: Значение удаленного элемента.
:raises IndexError: Если индекс выходит за пределы списка.
"""
self._mutex.acquire()
head, tail = self._history[self._current_state]
current = head
count = 0
Expand All @@ -139,10 +146,12 @@ def pop(self, index: int) -> any:
self._create_new_state()
self.size -= 1
self._history[self._last_state] = (head, tail)
self._mutex.release()
return value
count += 1
current = current.next_node
raise IndexError("Index out of range")
self._mutex.release()

def remove(self, value: any) -> None:
"""
Expand All @@ -152,6 +161,7 @@ def remove(self, value: any) -> None:
:return: None
:raises ValueError: Если элемент не найден в списке.
"""
self._mutex.acquire()
head, tail = self._history[self._current_state]
current = head
while current:
Expand All @@ -167,9 +177,11 @@ def remove(self, value: any) -> None:
self._create_new_state()
self.size -= 1
self._history[self._last_state] = (head, tail)
self._mutex.release()
return
current = current.next_node
raise ValueError(f"Value {value} not found in the list")
self._mutex.release()

def get(self, version: int = None, index: int = None) -> any:
"""
Expand Down Expand Up @@ -203,9 +215,11 @@ def clear(self) -> None:

:return: None
"""
self._mutex.acquire()
self._create_new_state()
self.size = 0
self._history[self._last_state] = (None, None)
self._mutex.release()

def __getitem__(self, index: int) -> any:
"""
Expand Down Expand Up @@ -233,6 +247,7 @@ def __setitem__(self, index: int, value: any) -> None:
:param value: Новое значение для обновляемого элемента.
:raises IndexError: Если индекс выходит за пределы списка.
"""
self._mutex.acquire()
self._create_new_state()
head, tail = self._history[self._last_state]
current = head
Expand All @@ -246,6 +261,7 @@ def __setitem__(self, index: int, value: any) -> None:
else:
raise IndexError("Index out of range")
self._history[self._last_state] = (head, tail)
self._mutex.release()

def get_size(self) -> int:
"""
Expand Down
19 changes: 12 additions & 7 deletions persistent_data_structures/persistent_map.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
from persistent_data_structures.base_persistent import BasePersistent
from time import sleep


class PersistentMap(BasePersistent):
"""Персистентный ассоциативный массив.

Представляет собой словарь, который сохраняет историю изменений.
"""
def __init__(self, initial_state: dict = {}) -> None:
"""Инициализирует персистентный ассоциативный массив.

:param initial_state: Начальное состояние персистентной структуры данных.
"""
super().__init__(initial_state)

def __setitem__(self, key: any, value: any) -> None:
"""Обновляет или создает элемент по указанному ключу в новой версии.

:param key: Ключ
:param value: Значение
"""
self._mutex.acquire()
self._create_new_state()
self._history[self._last_state][key] = value
sleep(1.0)
self._mutex.release()

def __getitem__(self, key: any) -> any:
"""Возвращает элемент текущей версии по указанному ключу.
Expand Down Expand Up @@ -50,8 +48,13 @@ 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._mutex.acquire()
self._create_new_state()
return self._history[self._last_state].pop(key)
popped_item = self._history[self._last_state].pop(key)
self._mutex.release()
return popped_item

def remove(self, key: any) -> None:
"""Удаляет элемент по указанному ключу в новой версии.
Expand All @@ -62,5 +65,7 @@ def remove(self, key: any) -> None:

def clear(self) -> None:
"""Очищает ассоциативный массив в новой версии."""
self._mutex.acquire()
self._create_new_state()
self._history[self._current_state] = {}
self._mutex.release()
15 changes: 15 additions & 0 deletions persistent_data_structures/transaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from time import sleep


def transaction(func: callable, *args, **kwargs) -> callable:
transaction_complete = False
return_value = None
while transaction_complete is False:
try:
return_value = func(*args, **kwargs)
transaction_complete = True
except Exception:
print('waiting...')
sleep(0.6)
continue
return return_value
65 changes: 64 additions & 1 deletion tests/test_array.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from persistent_array import PersistentArray
from transaction import transaction
from threading import Thread


# Тестирование методов класса PersistentArray
Expand Down Expand Up @@ -116,3 +117,65 @@ def test_invalid_version_update(persistent_array):
persistent_array.add(2)
with pytest.raises(ValueError):
persistent_array.update_version(10)


def test_transaction_success(persistent_array):
"""Тест 14. Проверка успешного выполнения транзакции"""
def modify_array():
persistent_array[0] = 42
transaction(modify_array)
assert persistent_array[0] == 42


def test_transaction_with_threads(persistent_array):
"""Тест 15. Проверка выполнения транзакции с использованием потоков"""
def modify_array_in_thread():
persistent_array[1] = 99
thread1 = Thread(target=transaction, args=(modify_array_in_thread,))
thread2 = Thread(target=transaction, args=(modify_array_in_thread,))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
assert persistent_array[1] == 99


def test_transaction_state_consistency(persistent_array):
"""Тест 16. Проверка консистентности состояния после транзакций"""
def modify_array_consistently():
persistent_array[2] = 88
persistent_array[3] = 77
transaction(modify_array_consistently)
assert persistent_array[2] == 88
assert persistent_array[3] == 77


def test_mutex_locking(persistent_array):
"""Тест 17. Проверка работы мьютекса"""
counter = 0

def modify_shared_resource():
nonlocal counter
for _ in range(100):
with persistent_array._mutex:
temp = counter
counter = temp + 1
threads = [Thread(target=modify_shared_resource) for _ in range(10)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
assert counter == 1000


def test_nested_transactions(persistent_array):
"""Тест 18. Проверка корректности работы вложенных транзакций"""
def modify_array_nested():
persistent_array[3] = 123

def inner_transaction():
persistent_array[4] = 456
transaction(inner_transaction)
transaction(modify_array_nested)
assert persistent_array[3] == 123
assert persistent_array[4] == 456
Loading
Loading