Skip to content

Commit e7134ca

Browse files
heikkitoivonencodex
andcommitted
Update: Add collections complexity tests
Co-Authored-By: Codex <codex@openai.com>
1 parent 314b98e commit e7134ca

1 file changed

Lines changed: 230 additions & 1 deletion

File tree

tests/test_collections_complexity.py

Lines changed: 230 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,17 @@
55
"""
66

77
import time
8-
from collections import deque
8+
from collections import (
9+
ChainMap,
10+
Counter,
11+
OrderedDict,
12+
UserDict,
13+
UserList,
14+
UserString,
15+
defaultdict,
16+
deque,
17+
namedtuple,
18+
)
919
from typing import Any, Callable
1020

1121

@@ -293,3 +303,222 @@ def test_reverse_is_on(self) -> None:
293303
assert is_linear_time(small_time, large_time, self.SIZE_RATIO), (
294304
f"reverse() doesn't appear linear: {small_time:.2e}s vs {large_time:.2e}s"
295305
)
306+
307+
308+
class TestNamedTupleComplexity:
309+
"""Test namedtuple operation complexities."""
310+
311+
SMALL_SIZE = 1_000
312+
LARGE_SIZE = 100_000
313+
SIZE_RATIO = LARGE_SIZE / SMALL_SIZE
314+
315+
def test_attribute_access_is_o1(self) -> None:
316+
"""Field access should be O(1)."""
317+
Point = namedtuple("Point", ["x", "y"])
318+
small_pt = Point(1, 2)
319+
large_pt = Point(1, 2)
320+
321+
small_time = measure_time(lambda: small_pt.x)
322+
large_time = measure_time(lambda: large_pt.x)
323+
324+
assert is_constant_time(small_time, large_time), (
325+
f"namedtuple attribute access appears non-constant: "
326+
f"{small_time:.2e}s vs {large_time:.2e}s"
327+
)
328+
329+
def test_asdict_is_on(self) -> None:
330+
"""_asdict() should be O(n) in number of fields."""
331+
SmallNT = namedtuple("SmallNT", [f"f{i}" for i in range(10)])
332+
LargeNT = namedtuple("LargeNT", [f"f{i}" for i in range(1000)])
333+
small_nt = SmallNT(*range(10))
334+
large_nt = LargeNT(*range(1000))
335+
336+
small_time = measure_time(lambda: small_nt._asdict(), iterations=50)
337+
large_time = measure_time(lambda: large_nt._asdict(), iterations=50)
338+
339+
assert is_linear_time(small_time, large_time, 100), (
340+
f"_asdict() doesn't appear linear: {small_time:.2e}s vs {large_time:.2e}s"
341+
)
342+
343+
344+
class TestChainMapComplexity:
345+
"""Test ChainMap operation complexities."""
346+
347+
SMALL_SIZE = 1_000
348+
LARGE_SIZE = 100_000
349+
350+
def test_lookup_is_o1_avg(self) -> None:
351+
"""Lookup should be O(1) average (first mapping)."""
352+
small_map = ChainMap({i: i for i in range(self.SMALL_SIZE)})
353+
large_map = ChainMap({i: i for i in range(self.LARGE_SIZE)})
354+
355+
small_time = measure_time(lambda: small_map[self.SMALL_SIZE - 1])
356+
large_time = measure_time(lambda: large_map[self.LARGE_SIZE - 1])
357+
358+
assert is_constant_time(small_time, large_time, tolerance=5.0), (
359+
f"ChainMap lookup appears non-constant: "
360+
f"{small_time:.2e}s vs {large_time:.2e}s"
361+
)
362+
363+
364+
class TestCounterComplexity:
365+
"""Test Counter operation complexities."""
366+
367+
SMALL_SIZE = 1_000
368+
LARGE_SIZE = 100_000
369+
SIZE_RATIO = LARGE_SIZE / SMALL_SIZE
370+
371+
def test_update_is_on(self) -> None:
372+
"""update(iterable) should be O(n)."""
373+
small_items = list(range(self.SMALL_SIZE))
374+
large_items = list(range(self.LARGE_SIZE))
375+
376+
small_counter = Counter()
377+
large_counter = Counter()
378+
379+
small_time = measure_time(lambda: small_counter.update(small_items), iterations=20)
380+
large_time = measure_time(lambda: large_counter.update(large_items), iterations=20)
381+
382+
assert is_linear_time(small_time, large_time, self.SIZE_RATIO), (
383+
f"Counter update doesn't appear linear: "
384+
f"{small_time:.2e}s vs {large_time:.2e}s"
385+
)
386+
387+
388+
class TestOrderedDictComplexity:
389+
"""Test OrderedDict operation complexities."""
390+
391+
SMALL_SIZE = 1_000
392+
LARGE_SIZE = 100_000
393+
394+
def test_get_is_o1_avg(self) -> None:
395+
"""Key lookup should be O(1) average."""
396+
small_od = OrderedDict((i, i) for i in range(self.SMALL_SIZE))
397+
large_od = OrderedDict((i, i) for i in range(self.LARGE_SIZE))
398+
399+
small_time = measure_time(lambda: small_od[self.SMALL_SIZE - 1])
400+
large_time = measure_time(lambda: large_od[self.LARGE_SIZE - 1])
401+
402+
assert is_constant_time(small_time, large_time, tolerance=5.0), (
403+
f"OrderedDict lookup appears non-constant: "
404+
f"{small_time:.2e}s vs {large_time:.2e}s"
405+
)
406+
407+
def test_move_to_end_is_o1(self) -> None:
408+
"""move_to_end() should be O(1) average."""
409+
small_od = OrderedDict((i, i) for i in range(self.SMALL_SIZE))
410+
large_od = OrderedDict((i, i) for i in range(self.LARGE_SIZE))
411+
412+
small_time = measure_time(lambda: small_od.move_to_end(self.SMALL_SIZE - 1))
413+
large_time = measure_time(lambda: large_od.move_to_end(self.LARGE_SIZE - 1))
414+
415+
assert is_constant_time(small_time, large_time, tolerance=5.0), (
416+
f"move_to_end() appears non-constant: "
417+
f"{small_time:.2e}s vs {large_time:.2e}s"
418+
)
419+
420+
421+
class TestDefaultDictComplexity:
422+
"""Test defaultdict operation complexities."""
423+
424+
SMALL_SIZE = 1_000
425+
LARGE_SIZE = 100_000
426+
427+
def test_missing_key_is_o1_avg(self) -> None:
428+
"""Missing key access should be O(1) average."""
429+
small_dd: defaultdict[int, int] = defaultdict(int, {i: i for i in range(self.SMALL_SIZE)})
430+
large_dd: defaultdict[int, int] = defaultdict(int, {i: i for i in range(self.LARGE_SIZE)})
431+
432+
small_time = measure_time(lambda: small_dd[self.SMALL_SIZE + 1])
433+
large_time = measure_time(lambda: large_dd[self.LARGE_SIZE + 1])
434+
435+
assert is_constant_time(small_time, large_time, tolerance=5.0), (
436+
f"defaultdict missing-key access appears non-constant: "
437+
f"{small_time:.2e}s vs {large_time:.2e}s"
438+
)
439+
440+
441+
class TestUserDictComplexity:
442+
"""Test UserDict operation complexities."""
443+
444+
SMALL_SIZE = 1_000
445+
LARGE_SIZE = 100_000
446+
447+
def test_get_is_o1_avg(self) -> None:
448+
"""UserDict key lookup should be O(1) average."""
449+
small_ud = UserDict({i: i for i in range(self.SMALL_SIZE)})
450+
large_ud = UserDict({i: i for i in range(self.LARGE_SIZE)})
451+
452+
small_time = measure_time(lambda: small_ud[self.SMALL_SIZE - 1])
453+
large_time = measure_time(lambda: large_ud[self.LARGE_SIZE - 1])
454+
455+
assert is_constant_time(small_time, large_time, tolerance=5.0), (
456+
f"UserDict lookup appears non-constant: "
457+
f"{small_time:.2e}s vs {large_time:.2e}s"
458+
)
459+
460+
461+
class TestUserListComplexity:
462+
"""Test UserList operation complexities."""
463+
464+
SMALL_SIZE = 1_000
465+
LARGE_SIZE = 100_000
466+
SIZE_RATIO = LARGE_SIZE / SMALL_SIZE
467+
468+
def test_len_is_o1(self) -> None:
469+
"""len() should be O(1)."""
470+
small_ul = UserList(range(self.SMALL_SIZE))
471+
large_ul = UserList(range(self.LARGE_SIZE))
472+
473+
small_time = measure_time(lambda: len(small_ul))
474+
large_time = measure_time(lambda: len(large_ul))
475+
476+
assert is_constant_time(small_time, large_time), (
477+
f"UserList len appears non-constant: {small_time:.2e}s vs {large_time:.2e}s"
478+
)
479+
480+
def test_count_is_on(self) -> None:
481+
"""count() should be O(n)."""
482+
small_ul = UserList(range(self.SMALL_SIZE))
483+
large_ul = UserList(range(self.LARGE_SIZE))
484+
485+
small_time = measure_time(lambda: small_ul.count(0), iterations=50)
486+
large_time = measure_time(lambda: large_ul.count(0), iterations=50)
487+
488+
assert is_linear_time(small_time, large_time, self.SIZE_RATIO), (
489+
f"UserList count doesn't appear linear: "
490+
f"{small_time:.2e}s vs {large_time:.2e}s"
491+
)
492+
493+
494+
class TestUserStringComplexity:
495+
"""Test UserString operation complexities."""
496+
497+
SMALL_SIZE = 1_000
498+
LARGE_SIZE = 100_000
499+
SIZE_RATIO = LARGE_SIZE / SMALL_SIZE
500+
501+
def test_len_is_o1(self) -> None:
502+
"""len() should be O(1)."""
503+
small_us = UserString("a" * self.SMALL_SIZE)
504+
large_us = UserString("a" * self.LARGE_SIZE)
505+
506+
small_time = measure_time(lambda: len(small_us))
507+
large_time = measure_time(lambda: len(large_us))
508+
509+
assert is_constant_time(small_time, large_time), (
510+
f"UserString len appears non-constant: {small_time:.2e}s vs {large_time:.2e}s"
511+
)
512+
513+
def test_count_is_on(self) -> None:
514+
"""count() should be O(n)."""
515+
small_us = UserString("a" * self.SMALL_SIZE)
516+
large_us = UserString("a" * self.LARGE_SIZE)
517+
518+
small_time = measure_time(lambda: small_us.count("a"), iterations=50)
519+
large_time = measure_time(lambda: large_us.count("a"), iterations=50)
520+
521+
assert is_linear_time(small_time, large_time, self.SIZE_RATIO), (
522+
f"UserString count doesn't appear linear: "
523+
f"{small_time:.2e}s vs {large_time:.2e}s"
524+
)

0 commit comments

Comments
 (0)