From a8c45731528f4408a61c4a2962577e50a4a7c673 Mon Sep 17 00:00:00 2001 From: svxf Date: Wed, 20 Mar 2019 01:11:31 +0400 Subject: [PATCH] init --- Pipfile | 13 +++++ Pipfile.lock | 101 ++++++++++++++++++++++++++++++++++++++ src/base.py | 9 ++++ src/explicit_storage.py | 41 ++++++++++++++++ src/range_based.py | 44 +++++++++++++++++ src/segment_tree.py | 61 +++++++++++++++++++++++ src/segment_tree_array.py | 44 +++++++++++++++++ tests/test_all.py | 100 +++++++++++++++++++++++++++++++++++++ 8 files changed, 413 insertions(+) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 src/base.py create mode 100644 src/explicit_storage.py create mode 100644 src/range_based.py create mode 100644 src/segment_tree.py create mode 100644 src/segment_tree_array.py create mode 100644 tests/test_all.py diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..69c964e --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +numpy = "*" +pytest = "*" + +[dev-packages] + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..093824f --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,101 @@ +{ + "_meta": { + "hash": { + "sha256": "8c364fbe4be6bc611b2c1423f61b9f0a5c5a023d80ba3cc114fe49a5b29a4b8e" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, + "more-itertools": { + "hashes": [ + "sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40", + "sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1" + ], + "markers": "python_version > '2.7'", + "version": "==6.0.0" + }, + "numpy": { + "hashes": [ + "sha256:1980f8d84548d74921685f68096911585fee393975f53797614b34d4f409b6da", + "sha256:22752cd809272671b273bb86df0f505f505a12368a3a5fc0aa811c7ece4dfd5c", + "sha256:23cc40313036cffd5d1873ef3ce2e949bdee0646c5d6f375bf7ee4f368db2511", + "sha256:2b0b118ff547fecabc247a2668f48f48b3b1f7d63676ebc5be7352a5fd9e85a5", + "sha256:3a0bd1edf64f6a911427b608a894111f9fcdb25284f724016f34a84c9a3a6ea9", + "sha256:3f25f6c7b0d000017e5ac55977a3999b0b1a74491eacb3c1aa716f0e01f6dcd1", + "sha256:4061c79ac2230594a7419151028e808239450e676c39e58302ad296232e3c2e8", + "sha256:560ceaa24f971ab37dede7ba030fc5d8fa173305d94365f814d9523ffd5d5916", + "sha256:62be044cd58da2a947b7e7b2252a10b42920df9520fc3d39f5c4c70d5460b8ba", + "sha256:6c692e3879dde0b67a9dc78f9bfb6f61c666b4562fd8619632d7043fb5b691b0", + "sha256:6f65e37b5a331df950ef6ff03bd4136b3c0bbcf44d4b8e99135d68a537711b5a", + "sha256:7a78cc4ddb253a55971115f8320a7ce28fd23a065fc33166d601f51760eecfa9", + "sha256:80a41edf64a3626e729a62df7dd278474fc1726836552b67a8c6396fd7e86760", + "sha256:893f4d75255f25a7b8516feb5766c6b63c54780323b9bd4bc51cdd7efc943c73", + "sha256:972ea92f9c1b54cc1c1a3d8508e326c0114aaf0f34996772a30f3f52b73b942f", + "sha256:9f1d4865436f794accdabadc57a8395bd3faa755449b4f65b88b7df65ae05f89", + "sha256:9f4cd7832b35e736b739be03b55875706c8c3e5fe334a06210f1a61e5c2c8ca5", + "sha256:adab43bf657488300d3aeeb8030d7f024fcc86e3a9b8848741ea2ea903e56610", + "sha256:bd2834d496ba9b1bdda3a6cf3de4dc0d4a0e7be306335940402ec95132ad063d", + "sha256:d20c0360940f30003a23c0adae2fe50a0a04f3e48dc05c298493b51fd6280197", + "sha256:d3b3ed87061d2314ff3659bb73896e622252da52558f2380f12c421fbdee3d89", + "sha256:dc235bf29a406dfda5790d01b998a1c01d7d37f449128c0b1b7d1c89a84fae8b", + "sha256:fb3c83554f39f48f3fa3123b9c24aecf681b1c289f9334f8215c1d3c8e2f6e5b" + ], + "index": "pypi", + "version": "==1.16.2" + }, + "pluggy": { + "hashes": [ + "sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f", + "sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746" + ], + "version": "==0.9.0" + }, + "py": { + "hashes": [ + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + ], + "version": "==1.8.0" + }, + "pytest": { + "hashes": [ + "sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523", + "sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4" + ], + "index": "pypi", + "version": "==4.3.1" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + } + }, + "develop": {} +} diff --git a/src/base.py b/src/base.py new file mode 100644 index 0000000..7c4152e --- /dev/null +++ b/src/base.py @@ -0,0 +1,9 @@ +class BulbSolution: + def __init__(self, n: int): + self.n = n + + def query(self, pos: int) -> bool: + raise NotImplementedError() + + def toggle(self, l: int, r: int): + raise NotImplementedError() diff --git a/src/explicit_storage.py b/src/explicit_storage.py new file mode 100644 index 0000000..d6c49e1 --- /dev/null +++ b/src/explicit_storage.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import numpy as np + +from base import BulbSolution + + +class StoreBulbsNaive(BulbSolution): + def __init__(self, n: int) -> None: + super().__init__(n) + self.bulbs = np.zeros(n, dtype=bool) + + def query(self, pos: int) -> bool: + return self.bulbs[pos] + + def toggle(self, l: int, r: int): + self.bulbs[l:r] ^= True + + +class StoreBulbsSqrt(BulbSolution): + + def __init__(self, n: int): + super().__init__(n) + self.m = int(np.sqrt(n) + 1) + + self.bulbs = np.zeros(self.n, dtype=bool) + self.bulb_chunks = np.zeros(self.m, dtype=bool) + + def query(self, pos: int) -> bool: + return self.bulbs[pos] ^ self.bulb_chunks[pos // self.m] + + def toggle(self, l: int, r: int): + chunk_l = (l + self.m - 1) // self.m + chunk_r = r // self.m + + if chunk_l < chunk_r: + self.bulbs[l:chunk_l * self.m] ^= True + self.bulb_chunks[chunk_l:chunk_r] ^= True + self.bulbs[chunk_r * self.m:r] ^= True + else: + self.bulbs[l:r] ^= True diff --git a/src/range_based.py b/src/range_based.py new file mode 100644 index 0000000..06932d3 --- /dev/null +++ b/src/range_based.py @@ -0,0 +1,44 @@ +from base import BulbSolution + + +class StoreRangesNaive(BulbSolution): + def __init__(self, n: int): + super().__init__(n) + + self.ranges = [] + + def query(self, pos: int) -> bool: + state = False + for l, r in self.ranges: + if l <= pos < r: + state ^= True + + return state + + def toggle(self, l: int, r: int): + self.ranges.append((l, r)) + + +class StoreRangesBinSearch(BulbSolution): + + def __init__(self, n: int): + super().__init__(n) + self.change_points = [] + + def query(self, pos: int) -> bool: + l = 0 + r = len(self.change_points) + + while r - l > 0: + m = (l + r) // 2 + if self.change_points[m] <= pos: + l = m + 1 + else: + r = m + + return l % 2 == 1 + + def toggle(self, l: int, r: int): + self.change_points.append(l) + self.change_points.append(r) + self.change_points = sorted(self.change_points) diff --git a/src/segment_tree.py b/src/segment_tree.py new file mode 100644 index 0000000..327a7bc --- /dev/null +++ b/src/segment_tree.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from base import BulbSolution + + +@dataclass +class SegmentTree: + l: int + r: int + + child_l: SegmentTree + child_r: SegmentTree + + value: bool + + def __init__(self, l, r): + self.l = l + self.r = r + self.value = False + + if self.r - self.l == 1: + self.child_l = None + self.child_r = None + else: + self.m = (l + r) // 2 + self.child_l = SegmentTree(self.l, self.m) + self.child_r = SegmentTree(self.m, self.r) + + def toggle(self, l, r): + if self.l == l and self.r == r: + self.value ^= True + return + + if l < self.m: + self.child_l.toggle(l, min(r, self.m)) + + if r > self.m: + self.child_r.toggle(max(self.m, l), r) + + def query(self, pos): + if self.l == pos and self.r == pos + 1: + return self.value + + if pos < self.m: + return self.value ^ self.child_l.query(pos) + else: + return self.value ^ self.child_r.query(pos) + + +class StoreBulbsSegmentTree(BulbSolution): + def __init__(self, n: int): + super().__init__(n) + self.tree = SegmentTree(0, n) + + def toggle(self, l: int, r: int): + self.tree.toggle(l, r) + + def query(self, pos: int) -> bool: + return self.tree.query(pos) diff --git a/src/segment_tree_array.py b/src/segment_tree_array.py new file mode 100644 index 0000000..712f95e --- /dev/null +++ b/src/segment_tree_array.py @@ -0,0 +1,44 @@ +import numpy as np + +from base import BulbSolution + + +class SegmentTreeArrayBased: + def __init__(self, n): + self.nodes = np.zeros(4 * n, dtype=bool) + + def toggle(self, l, r, node_i, node_l, node_r): + if l == node_l and r == node_r: + self.nodes[node_i] ^= True + return + + node_m = (node_l + node_r) // 2 + + if l < node_m: + self.toggle(l, min(r, node_m), node_i * 2 + 1, node_l, node_m) + + if r > node_m: + self.toggle(max(node_m, l), r, node_i * 2 + 2, node_m, node_r) + + def query(self, pos, node_i, node_l, node_r): + if node_l == pos and node_r == pos + 1: + return self.nodes[node_i] + + node_m = (node_l + node_r) // 2 + + if pos < node_m: + return self.nodes[node_i] ^ self.query(pos, node_i * 2 + 1, node_l, node_m) + else: + return self.nodes[node_i] ^ self.query(pos, node_i * 2 + 2, node_m, node_r) + + +class StoreBulbsSegmentTreeArrayBased(BulbSolution): + def __init__(self, n: int): + super().__init__(n) + self.tree = SegmentTreeArrayBased(n) + + def toggle(self, l: int, r: int): + self.tree.toggle(l, r, 0, 0, self.n) + + def query(self, pos: int) -> bool: + return self.tree.query(pos, 0, 0, self.n) diff --git a/tests/test_all.py b/tests/test_all.py new file mode 100644 index 0000000..4206881 --- /dev/null +++ b/tests/test_all.py @@ -0,0 +1,100 @@ +from time import time_ns + +import numpy as np + +from explicit_storage import StoreBulbsNaive, StoreBulbsSqrt +from range_based import StoreRangesBinSearch, StoreRangesNaive +from segment_tree import StoreBulbsSegmentTree + +ReferenceSolution = StoreBulbsNaive + +TestSolutions = [ + StoreBulbsNaive, + StoreBulbsSqrt, + StoreBulbsSegmentTree, + StoreRangesNaive, + StoreRangesBinSearch, +] + + +def create_actions(n, query_count, toggle_count): + actions = [] + + for i in range(toggle_count): + j = np.random.randint(n + 1) + i = np.random.randint(n) + + if j < n - i: + l = i + r = i + j + 1 + else: + l = n - i - 1 + r = j + + actions.append({ + 'l': l, + 'r': r, + }) + + for i in range(query_count): + actions.append({ + 'pos': np.random.randint(n), + }) + + np.random.shuffle(actions) + + reference = ReferenceSolution(n) + + for action in actions: + if 'l' in action: + reference.toggle(action['l'], action['r']) + else: + action['answer'] = reference.query(action['pos']) + + return actions + + +def run_test_case(n, toggle_count, query_count): + actions = create_actions(n, query_count, toggle_count) + + solutions = [Solution(n) for Solution in TestSolutions] + + toggle_times = np.zeros(len(solutions)) + query_times = np.zeros(len(solutions)) + + for solution_i, solution in enumerate(solutions): + for action_i, action in enumerate(actions): + + start_time = time_ns() + + if 'l' in action: + solution.toggle(action['l'], action['r']) + + toggle_times[solution_i] += time_ns() - start_time + else: + assert solution.query(action['pos']) == action['answer'], action['pos'] + + query_times[solution_i] += time_ns() - start_time + + total_times = toggle_times + query_times + + print() + print(f'n = {n:6} T = {toggle_count:6} Q = {query_count:6}') + for i in np.argsort(total_times): + solution_name = TestSolutions[i].__name__ + toggle_time = toggle_times[i] + query_time = query_times[i] + total_time = total_times[i] + print( + f' {solution_name:40} {toggle_time / 1e6:6.0f}ms + {query_time / 1e6:6.0f}ms = {total_time / 1e6:6.0f}ms') + + +def test_all(): + np.random.seed(0) + + run_test_case(1000, 1000, 1000) + + run_test_case(10_000, 10, 10_000) + run_test_case(10_000, 10_000, 10) + + run_test_case(1_000_000, 1_000, 1_000)