diff --git a/persistent_data_structures/base_persistent.py b/persistent_data_structures/base_persistent.py index a4b967b..b8f1f58 100644 --- a/persistent_data_structures/base_persistent.py +++ b/persistent_data_structures/base_persistent.py @@ -1,4 +1,5 @@ from copy import deepcopy +from threading import Lock class BasePersistent: @@ -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): """Возвращает состояние персистентной структуры данных на указанной версии. @@ -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: """Создает новую версию.""" diff --git a/persistent_data_structures/persistent_array.py b/persistent_data_structures/persistent_array.py index cee913f..b3d7eb5 100644 --- a/persistent_data_structures/persistent_array.py +++ b/persistent_data_structures/persistent_array.py @@ -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: """Удаление элемента в новой версии массива и возвращение его значения. @@ -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: @@ -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: """Вставка нового элемента в массив в указанную позицию в новой версии. @@ -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: """Удаление элемента в новой версии массива по индексу. diff --git a/persistent_data_structures/persistent_list.py b/persistent_data_structures/persistent_list.py index 75b4fa0..c50c8d7 100644 --- a/persistent_data_structures/persistent_list.py +++ b/persistent_data_structures/persistent_list.py @@ -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) @@ -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: """ @@ -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) @@ -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: """ @@ -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 @@ -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: """ @@ -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 @@ -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: """ @@ -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: @@ -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: """ @@ -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: """ @@ -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 @@ -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: """ diff --git a/persistent_data_structures/persistent_map.py b/persistent_data_structures/persistent_map.py index 9efb825..fb3c4c1 100644 --- a/persistent_data_structures/persistent_map.py +++ b/persistent_data_structures/persistent_map.py @@ -1,4 +1,5 @@ from persistent_data_structures.base_persistent import BasePersistent +from time import sleep class PersistentMap(BasePersistent): @@ -6,12 +7,6 @@ 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: """Обновляет или создает элемент по указанному ключу в новой версии. @@ -19,8 +14,11 @@ 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: """Возвращает элемент текущей версии по указанному ключу. @@ -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: """Удаляет элемент по указанному ключу в новой версии. @@ -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() diff --git a/persistent_data_structures/transaction.py b/persistent_data_structures/transaction.py new file mode 100644 index 0000000..9a20f84 --- /dev/null +++ b/persistent_data_structures/transaction.py @@ -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 diff --git a/tests/test_array.py b/tests/test_array.py index 00b2cca..6de19f0 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1,6 +1,7 @@ import pytest - from persistent_array import PersistentArray +from transaction import transaction +from threading import Thread # Тестирование методов класса PersistentArray @@ -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 diff --git a/tests/test_list.py b/tests/test_list.py index 75f6742..b435b73 100644 --- a/tests/test_list.py +++ b/tests/test_list.py @@ -1,6 +1,7 @@ import pytest - from persistent_list import PersistentLinkedList +from transaction import transaction +from threading import Thread # Тестирование методов класса PersistentLinkedList @@ -95,3 +96,86 @@ def test_check_is_empty(linked_list): assert linked_list.check_is_empty() is True linked_list.add(10) assert linked_list.check_is_empty() is False + + +def test_transaction_success(linked_list): + """Тест 12. Проверка успешного выполнения транзакции""" + def modify_list(): + linked_list.add_first(3) + transaction(modify_list) + assert linked_list[0] == 3 + + +def test_transaction_with_threads(linked_list): + """Тест 13. Проверка выполнения транзакции с использованием потоков""" + def modify_list_in_thread(): + linked_list.add(4) + thread1 = Thread(target=transaction, args=(modify_list_in_thread,)) + thread2 = Thread(target=transaction, args=(modify_list_in_thread,)) + thread1.start() + thread2.start() + thread1.join() + thread2.join() + assert linked_list.get_size() == 7 + assert linked_list.get(index=5) == 4 + assert linked_list.get(index=6) == 4 + + +def test_transaction_state_consistency(linked_list): + """Тест 14. Проверка консистентности состояния после транзакций""" + def modify_list_consistently(): + linked_list.add(6) + linked_list.add(7) + transaction(modify_list_consistently) + assert linked_list.get(index=5) == 6 + assert linked_list.get(index=6) == 7 + + +def test_mutex_locking(linked_list): + """Тест 15. Проверка работы мьютекса""" + counter = 0 + + def modify_shared_resource(): + nonlocal counter + for _ in range(100): + with linked_list._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(linked_list): + """Тест 16. Проверка корректности работы вложенных транзакций""" + def outer_transaction(): + linked_list.add(1) + + def inner_transaction(): + linked_list.add(2) + transaction(inner_transaction) + transaction(outer_transaction) + assert linked_list.get(index=0) == 1 + assert linked_list.get(index=1) == 2 + + +def test_multiple_changes_in_transaction(linked_list): + """Тест 17. Проверка транзакции с несколькими изменениями""" + def modify_multiple_elements(): + linked_list.add_first(10) + linked_list.add_first(20) + transaction(modify_multiple_elements) + assert linked_list.get(index=1) == 10 + assert linked_list.get(index=0) == 20 + + +def test_transaction_versioning(linked_list): + """Тест 18. Проверка корректности версий после транзакции""" + def modify_list(): + linked_list.add_first(42) + transaction(modify_list) + assert linked_list.get(index=0) == 42 + assert linked_list._current_state == 1 diff --git a/tests/test_map.py b/tests/test_map.py index 580e6b6..cbf5a35 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -1,6 +1,7 @@ import pytest - from persistent_map import PersistentMap +from transaction import transaction +from threading import Thread # Тестирование методов класса PersistentMap @@ -81,3 +82,97 @@ def test_version_history(persistent_map): 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_transaction_success(persistent_map): + """Тест 11. Проверка успешного выполнения транзакции""" + def modify_map(): + persistent_map['c'] = 3 + transaction(modify_map) + assert persistent_map['c'] == 3 + + +def test_transaction_with_threads(persistent_map): + """Тест 12. Проверка выполнения транзакции с использованием потоков""" + def modify_map_in_thread(): + persistent_map['d'] = 4 + thread1 = Thread(target=transaction, args=(modify_map_in_thread,)) + thread2 = Thread(target=transaction, args=(modify_map_in_thread,)) + thread1.start() + thread2.start() + thread1.join() + thread2.join() + assert persistent_map['d'] == 4 + + +def test_transaction_state_consistency(persistent_map): + """Тест 13. Проверка консистентности состояния после транзакций""" + def modify_map_consistently(): + persistent_map['f'] = 6 + persistent_map['g'] = 7 + transaction(modify_map_consistently) + assert persistent_map['f'] == 6 + assert persistent_map['g'] == 7 + + +def test_mutex_locking(persistent_map): + """Тест 14. Проверка работы мьютекса""" + counter = 0 + + def modify_shared_resource(): + nonlocal counter + for _ in range(100): + with persistent_map._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_map): + """Тест 15. Проверка корректности работы вложенных транзакций""" + def outer_transaction(): + persistent_map['outer'] = 1 + + def inner_transaction(): + persistent_map['inner'] = 2 + transaction(inner_transaction) + transaction(outer_transaction) + assert persistent_map['outer'] == 1 + assert persistent_map['inner'] == 2 + + +def test_transaction_isolation(persistent_map): + """Тест 16. Проверка изоляции транзакций""" + def modify_map(): + persistent_map['e'] = 5 + thread1 = Thread(target=transaction, args=(modify_map,)) + thread2 = Thread(target=transaction, args=(modify_map,)) + thread1.start() + thread2.start() + thread1.join() + thread2.join() + assert persistent_map['e'] == 5 + + +def test_multiple_changes_in_transaction(persistent_map): + """Тест 17. Проверка транзакции с несколькими изменениями""" + def modify_multiple_keys(): + persistent_map['x'] = 10 + persistent_map['y'] = 20 + transaction(modify_multiple_keys) + assert persistent_map['x'] == 10 + assert persistent_map['y'] == 20 + + +def test_transaction_versioning(persistent_map): + """Тест 18. Проверка корректности версий после транзакции""" + def modify_map(): + persistent_map['new_key'] = 42 + transaction(modify_map) + assert persistent_map['new_key'] == 42 + assert persistent_map._current_state == 1