From 6808fabe346347caa91571e6ac1673b02e738bd2 Mon Sep 17 00:00:00 2001 From: DimitryPuchkov Date: Wed, 18 Dec 2024 19:35:10 +0700 Subject: [PATCH 1/4] add undoredo --- persistent_data_structures/base_persistent.py | 12 ++++++++++++ persistent_data_structures/persistent_map.py | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/persistent_data_structures/base_persistent.py b/persistent_data_structures/base_persistent.py index a4b967b..7123100 100644 --- a/persistent_data_structures/base_persistent.py +++ b/persistent_data_structures/base_persistent.py @@ -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): """Возвращает состояние персистентной структуры данных на указанной версии. @@ -37,6 +39,16 @@ def update_version(self, version): raise ValueError(f'Version "{version}" does not exist') self._current_state = version + def undo(self): + """Отменяет последнее изменение.""" + if self._current_state > 0: + self._current_state -= 1 + + def redo(self): + """Отменяет отмененное изменение.""" + if self._current_state < self._last_state: + self._current_state += 1 + def _create_new_state(self) -> None: """Создает новую версию.""" self._last_state += 1 diff --git a/persistent_data_structures/persistent_map.py b/persistent_data_structures/persistent_map.py index 9efb825..e7e75a3 100644 --- a/persistent_data_structures/persistent_map.py +++ b/persistent_data_structures/persistent_map.py @@ -20,7 +20,12 @@ 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 + if self._container is not None: + self._container[self._location] = self._history[self._last_state] def __getitem__(self, key: any) -> any: """Возвращает элемент текущей версии по указанному ключу. From a3552d2880a1117f2050c52d49316a0331400f8b Mon Sep 17 00:00:00 2001 From: DimitryPuchkov Date: Sat, 21 Dec 2024 12:24:58 +0700 Subject: [PATCH 2/4] add transaction mem in map --- persistent_data_structures/base_persistent.py | 2 -- persistent_data_structures/persistent_map.py | 24 ++++++++++++++----- persistent_data_structures/transaction.py | 15 ++++++++++++ 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 persistent_data_structures/transaction.py diff --git a/persistent_data_structures/base_persistent.py b/persistent_data_structures/base_persistent.py index 7123100..5277957 100644 --- a/persistent_data_structures/base_persistent.py +++ b/persistent_data_structures/base_persistent.py @@ -15,8 +15,6 @@ 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): """Возвращает состояние персистентной структуры данных на указанной версии. diff --git a/persistent_data_structures/persistent_map.py b/persistent_data_structures/persistent_map.py index e7e75a3..392eeee 100644 --- a/persistent_data_structures/persistent_map.py +++ b/persistent_data_structures/persistent_map.py @@ -12,6 +12,8 @@ def __init__(self, initial_state: dict = {}) -> None: :param initial_state: Начальное состояние персистентной структуры данных. """ super().__init__(initial_state) + self._locked_key = set() + self._global_lock = False def __setitem__(self, key: any, value: any) -> None: """Обновляет или создает элемент по указанному ключу в новой версии. @@ -19,13 +21,12 @@ def __setitem__(self, key: any, value: any) -> None: :param key: Ключ :param value: Значение """ + if key in self._locked_key or self._global_lock: + raise KeyError(f'Key "{key}" is locked') + self._locked_key.add(key) self._create_new_state() - if isinstance(value, BasePersistent): - value._container = self - value._location = key self._history[self._last_state][key] = value - if self._container is not None: - self._container[self._location] = self._history[self._last_state] + self._locked_key.remove(key) def __getitem__(self, key: any) -> any: """Возвращает элемент текущей версии по указанному ключу. @@ -55,8 +56,15 @@ 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') + if key in self._locked_key or self._global_lock: + raise KeyError(f'Key "{key}" is locked') + self._locked_key.add(key) self._create_new_state() - return self._history[self._last_state].pop(key) + popped_item = self._history[self._last_state].pop(key) + self._locked_key.remove(key) + return popped_item def remove(self, key: any) -> None: """Удаляет элемент по указанному ключу в новой версии. @@ -67,5 +75,9 @@ def remove(self, key: any) -> None: def clear(self) -> None: """Очищает ассоциативный массив в новой версии.""" + if self._global_lock or len(self._locked_key) > 0: + raise KeyError('Can not access the all map') + self._global_lock = True self._create_new_state() self._history[self._current_state] = {} + self._global_lock = False 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 From 4cfa1560f86df15d01b4635c343007fbd78fe118 Mon Sep 17 00:00:00 2001 From: DimitryPuchkov Date: Mon, 20 Jan 2025 23:13:54 +0700 Subject: [PATCH 3/4] transaction memory --- persistent_data_structures/base_persistent.py | 8 ++++++ .../persistent_array.py | 8 ++++++ persistent_data_structures/persistent_list.py | 16 +++++++++++ persistent_data_structures/persistent_map.py | 28 ++++++------------- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/persistent_data_structures/base_persistent.py b/persistent_data_structures/base_persistent.py index 5277957..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,17 +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 392eeee..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,14 +7,6 @@ class PersistentMap(BasePersistent): Представляет собой словарь, который сохраняет историю изменений. """ - def __init__(self, initial_state: dict = {}) -> None: - """Инициализирует персистентный ассоциативный массив. - - :param initial_state: Начальное состояние персистентной структуры данных. - """ - super().__init__(initial_state) - self._locked_key = set() - self._global_lock = False def __setitem__(self, key: any, value: any) -> None: """Обновляет или создает элемент по указанному ключу в новой версии. @@ -21,12 +14,11 @@ def __setitem__(self, key: any, value: any) -> None: :param key: Ключ :param value: Значение """ - if key in self._locked_key or self._global_lock: - raise KeyError(f'Key "{key}" is locked') - self._locked_key.add(key) + self._mutex.acquire() self._create_new_state() self._history[self._last_state][key] = value - self._locked_key.remove(key) + sleep(1.0) + self._mutex.release() def __getitem__(self, key: any) -> any: """Возвращает элемент текущей версии по указанному ключу. @@ -58,12 +50,10 @@ def pop(self, key: any) -> any: """ if key not in self._history[self._current_state]: raise KeyError(f'Key "{key}" does not exist') - if key in self._locked_key or self._global_lock: - raise KeyError(f'Key "{key}" is locked') - self._locked_key.add(key) + self._mutex.acquire() self._create_new_state() popped_item = self._history[self._last_state].pop(key) - self._locked_key.remove(key) + self._mutex.release() return popped_item def remove(self, key: any) -> None: @@ -75,9 +65,7 @@ def remove(self, key: any) -> None: def clear(self) -> None: """Очищает ассоциативный массив в новой версии.""" - if self._global_lock or len(self._locked_key) > 0: - raise KeyError('Can not access the all map') - self._global_lock = True + self._mutex.acquire() self._create_new_state() self._history[self._current_state] = {} - self._global_lock = False + self._mutex.release() From 2584513b7308d02f4b2b3ebd85a40c6bd664b34a Mon Sep 17 00:00:00 2001 From: Cinnamonness Date: Wed, 22 Jan 2025 16:26:21 +0700 Subject: [PATCH 4/4] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=BD=D0=B7=D0=B0=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=BD=D0=BE=D1=81=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_array.py | 65 +++++++++++++++++++++++++++++- tests/test_list.py | 86 +++++++++++++++++++++++++++++++++++++++- tests/test_map.py | 97 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 245 insertions(+), 3 deletions(-) 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