This commit is contained in:
2019-03-20 01:11:31 +04:00
commit a8c4573152
8 changed files with 413 additions and 0 deletions

13
Pipfile Normal file
View File

@@ -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"

101
Pipfile.lock generated Normal file
View File

@@ -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": {}
}

9
src/base.py Normal file
View File

@@ -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()

41
src/explicit_storage.py Normal file
View File

@@ -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

44
src/range_based.py Normal file
View File

@@ -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)

61
src/segment_tree.py Normal file
View File

@@ -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)

44
src/segment_tree_array.py Normal file
View File

@@ -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)

100
tests/test_all.py Normal file
View File

@@ -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)