init
This commit is contained in:
13
Pipfile
Normal file
13
Pipfile
Normal 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
101
Pipfile.lock
generated
Normal 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
9
src/base.py
Normal 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
41
src/explicit_storage.py
Normal 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
44
src/range_based.py
Normal 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
61
src/segment_tree.py
Normal 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
44
src/segment_tree_array.py
Normal 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
100
tests/test_all.py
Normal 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)
|
Reference in New Issue
Block a user