From 77d4b183b319f8a671352a61df4c10b22b0a2b56 Mon Sep 17 00:00:00 2001 From: ermmmaks Date: Mon, 15 Dec 2025 15:18:11 +0300 Subject: [PATCH 1/3] Add walker and hamming and tests for them --- src/hamming.py | 66 ++++++++++++++++++++++++++++++++++++++++++++ src/walker.py | 54 ++++++++++++++++++++++++++++++++++++ test/test_hamming.py | 13 +++++++++ test/test_walker.py | 39 ++++++++++++++++++++++++++ 4 files changed, 172 insertions(+) create mode 100644 src/hamming.py create mode 100644 src/walker.py create mode 100644 test/test_hamming.py create mode 100644 test/test_walker.py diff --git a/src/hamming.py b/src/hamming.py new file mode 100644 index 0000000..521668a --- /dev/null +++ b/src/hamming.py @@ -0,0 +1,66 @@ +def reducent_bits(n): + """ + Рассчитывает редуцентные биты p + """ + p = 0 + while (2**p) < n + p + 1: + p += 1 + return p + +def encode(string): + """ + Принимает строку из битов + Кодирует строку кодом Хэмминга + Возвращает строку битов в кодировке Хэмминга + """ + bits = [int(b) for b in string] + n = len(bits) + p = reducent_bits(n) + + encoded_bits = [0] * (n + p) + + idx = 0 + for i in range(1, len(encoded_bits) + 1): + if not(i & (i - 1) == 0): + encoded_bits[i-1] = bits[idx] + idx += 1 + + for i in range(p): + parity_pos = 2**i + checked_bits = [] + for j in range(1, len(encoded_bits) + 1): + if j & parity_pos: + checked_bits.append(encoded_bits[j-1]) + + encoded_bits[parity_pos-1] = sum(checked_bits) % 2 + + return "".join(map(str, encoded_bits)) + +def decode(encoded_string): + """ + Принимает закодированную строку из 0 и 1 + Декодирует и проверяет + В случае ошибки возвращает -1 (успех) или индекс позиции с ошибкой + """ + received_bits = [int(b) for b in encoded_string] + n = len(received_bits) + p = 0 + while (2**p) < n: + p += 1 + + status = 0 + + for i in range(p): + parity_pos = 2**i + checked_bits = [] + for j in range(1, n + 1): + if j & parity_pos: + checked_bits.append(received_bits[j-1]) + + if sum(checked_bits) % 2 != 0: + status += parity_pos + + if status == 0: + return -1 + else: + return status \ No newline at end of file diff --git a/src/walker.py b/src/walker.py new file mode 100644 index 0000000..7f4335e --- /dev/null +++ b/src/walker.py @@ -0,0 +1,54 @@ +import random +from collections import deque + +class WalkersAlias: + def __init__(self, events_and_probs): + if not events_and_probs: + raise ValueError("Список событий пуст") + + self.events = [item[0] for item in events_and_probs] + probs = [item[1] for item in events_and_probs] + self.ln = len(self.events) + + if min(probs) < 0: + raise ValueError("Вероятность не может быть отрицательной") + + result = sum(probs) + if not(abs(result - 1.0) < 1e-6): + raise ValueError("Сумма вероятностей должна быть близка 1") + + columns_probs = [p * self.ln for p in probs] + + self.alias_table = [-1] * self.ln + self.prob_table = [0.0] * self.ln + + short = deque([i for i, p in enumerate(columns_probs) if p < 1.0]) + long = deque([i for i, p in enumerate(columns_probs) if p >= 1.0]) + + while short and long: + s = short.popleft() + l = long.popleft() + + self.prob_table[s] = columns_probs[l] + self.alias_table[s] = l + + columns_probs[l] -= (1.0 - columns_probs[s]) + + if columns_probs[l] < 1.0: + short.append(l) + else: + long.append(l) + + while short: + self.prob_table[short.popleft()] = 1.0 + while long: + self.prob_table[long.popleft()] = 1.0 + + def get_random(self): + e = random.randint(0, self.ln - 1) + n = random.random() + + if n <= self.prob_table[e]: + return self.events[e] + else: + return self.events[self.alias_table[e]] \ No newline at end of file diff --git a/test/test_hamming.py b/test/test_hamming.py new file mode 100644 index 0000000..c0479fd --- /dev/null +++ b/test/test_hamming.py @@ -0,0 +1,13 @@ +import pytest +from src.hamming import encode, decode + +def test_encode(): + assert encode('1011') == '0110011' + assert encode('11010110') == '001010100110' + +def test_decode(): + encoded_data4 = '0110011' + assert decode(encoded_data4) == -1 + + encoded_data8 = '001010100110' + assert decode(encoded_data8) == -1 \ No newline at end of file diff --git a/test/test_walker.py b/test/test_walker.py new file mode 100644 index 0000000..3d45662 --- /dev/null +++ b/test/test_walker.py @@ -0,0 +1,39 @@ +import pytest +import math +from src.walker import WalkersAlias + +N_TESTS = 100000 +INACCURACY = 0.01 + +def test_same_distribution(): + distribution = [("A", 0.5), ("B", 0.5)] + walker = WalkersAlias(distribution) + results = {"A": 0, "B": 0} + + for _ in range(N_TESTS): + event = walker.get_random() + results[event] += 1 + + assert math.isclose(results["A"] / N_TESTS, 0.5, abs_tol=INACCURACY) + assert math.isclose(results["B"] / N_TESTS, 0.5, abs_tol=INACCURACY) + +def test_multiple_events(): + distribution = [("A", 0.65), ("B", 0.1), ("C", 0.2), ("D", 0.05)] + walker = WalkersAlias(distribution) + results = {e: 0 for e, p in distribution} + + for _ in range(N_TESTS): + event = walker.get_random() + results[event] += 1 + + assert math.isclose(results["A"] / N_TESTS, 0.65, abs_tol=INACCURACY) + assert math.isclose(results["B"] / N_TESTS, 0.1, abs_tol=INACCURACY) + assert math.isclose(results["C"] / N_TESTS, 0.2, abs_tol=INACCURACY) + assert math.isclose(results["D"] / N_TESTS, 0.05, abs_tol=INACCURACY) + +def test_inaccuracy(): + p = 0.1 + distribution = [("A", p)] * 10 + walker = WalkersAlias(distribution) + + assert walker.ln == 10 \ No newline at end of file From 5a5b10e47063f04a0b109171572be2f49e3a3b9d Mon Sep 17 00:00:00 2001 From: ermmmaks Date: Mon, 15 Dec 2025 15:28:28 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=9D=D0=B0=D1=88=D0=BB=D0=B8=D1=81=D1=8C?= =?UTF-8?q?=20=D0=B7=D0=B0=D1=80=D0=B0=D0=B7=D1=8B,=20=D1=82=D0=B5=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D1=8C=20=D0=BF=D1=80=D0=BE=D1=85=D0=BE=D0=B4=D1=8F?= =?UTF-8?q?=D1=82=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/walker.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/walker.py b/src/walker.py index 7f4335e..b05ff76 100644 --- a/src/walker.py +++ b/src/walker.py @@ -1,4 +1,5 @@ import random +import math from collections import deque class WalkersAlias: @@ -29,7 +30,7 @@ def __init__(self, events_and_probs): s = short.popleft() l = long.popleft() - self.prob_table[s] = columns_probs[l] + self.prob_table[s] = columns_probs[s] self.alias_table[s] = l columns_probs[l] -= (1.0 - columns_probs[s]) @@ -39,10 +40,10 @@ def __init__(self, events_and_probs): else: long.append(l) - while short: - self.prob_table[short.popleft()] = 1.0 - while long: - self.prob_table[long.popleft()] = 1.0 + while short: + self.prob_table[short.popleft()] = 1.0 + while long: + self.prob_table[long.popleft()] = 1.0 def get_random(self): e = random.randint(0, self.ln - 1) From e54facd1916890cdddc49a97d9293d10b7c0547e Mon Sep 17 00:00:00 2001 From: ermmmaks Date: Mon, 15 Dec 2025 15:32:45 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BA=D0=BE=D1=81=D0=BC=D0=B5=D1=82=D0=B8=D0=BA=D1=83?= =?UTF-8?q?,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20=D0=BD=D0=B0=20=D0=B3?= =?UTF-8?q?=D0=B8=D1=82=D1=85=D0=B0=D0=B1=D0=B5=20=D0=BF=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D1=8F=D0=BB=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/walker.py | 27 +++++++++++++-------------- test/test_hamming.py | 1 - test/test_walker.py | 1 - 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/walker.py b/src/walker.py index b05ff76..934914c 100644 --- a/src/walker.py +++ b/src/walker.py @@ -1,5 +1,4 @@ import random -import math from collections import deque class WalkersAlias: @@ -27,18 +26,18 @@ def __init__(self, events_and_probs): long = deque([i for i, p in enumerate(columns_probs) if p >= 1.0]) while short and long: - s = short.popleft() - l = long.popleft() + shrt = short.popleft() + lng = long.popleft() - self.prob_table[s] = columns_probs[s] - self.alias_table[s] = l + self.prob_table[shrt] = columns_probs[shrt] + self.alias_table[shrt] = lng - columns_probs[l] -= (1.0 - columns_probs[s]) + columns_probs[lng] -= (1.0 - columns_probs[shrt]) - if columns_probs[l] < 1.0: - short.append(l) + if columns_probs[lng] < 1.0: + short.append(lng) else: - long.append(l) + long.append(lng) while short: self.prob_table[short.popleft()] = 1.0 @@ -46,10 +45,10 @@ def __init__(self, events_and_probs): self.prob_table[long.popleft()] = 1.0 def get_random(self): - e = random.randint(0, self.ln - 1) - n = random.random() + xi = random.randint(0, self.ln - 1) + eta = random.random() - if n <= self.prob_table[e]: - return self.events[e] + if eta <= self.prob_table[xi]: + return self.events[xi] else: - return self.events[self.alias_table[e]] \ No newline at end of file + return self.events[self.alias_table[xi]] \ No newline at end of file diff --git a/test/test_hamming.py b/test/test_hamming.py index c0479fd..8469ff5 100644 --- a/test/test_hamming.py +++ b/test/test_hamming.py @@ -1,4 +1,3 @@ -import pytest from src.hamming import encode, decode def test_encode(): diff --git a/test/test_walker.py b/test/test_walker.py index 3d45662..9c76c8a 100644 --- a/test/test_walker.py +++ b/test/test_walker.py @@ -1,4 +1,3 @@ -import pytest import math from src.walker import WalkersAlias