Skip to content

Commit 1af44d0

Browse files
committed
Enhance CircularQueue to support first entry
1 parent 538b865 commit 1af44d0

3 files changed

Lines changed: 94 additions & 11 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "apologies"
3-
version = "0.1.11"
3+
version = "0.1.12"
44
description = "Python code to play a game similar to Sorry"
55
authors = ["Kenneth J. Pronovici <pronovic@ieee.org>"]
66
license = "Apache-2.0"

src/apologies/util.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Utility functionality.
66
"""
77

8-
from typing import Generic, List, TypeVar
8+
from typing import Any, Generic, List, TypeVar
99

1010
import attr
1111
import cattr
@@ -24,27 +24,39 @@ def __init__(self) -> None:
2424
self.register_structure_hook(DateTime, lambda string, _: parse(string) if string else None)
2525

2626

27-
Type = TypeVar("Type")
27+
T = TypeVar("T") # pylint: disable=invalid-name
2828
"""Generic type"""
2929

3030

3131
@attr.s
32-
class CircularQueue(Generic[Type]):
32+
class CircularQueue(Generic[T]):
3333
"""A circular queue that keeps returning the original entries repeatedly, in order."""
3434

35-
entries = attr.ib(type=List[Type])
36-
_working = attr.ib(type=List[Type])
35+
entries = attr.ib(type=List[T])
36+
first = attr.ib(type=Any)
37+
_working = attr.ib(type=List[T])
38+
39+
@first.default
40+
def _default_first(self) -> Any:
41+
return None if not self.entries else self.entries[0]
3742

3843
@_working.default
39-
def _default_working(self) -> List[Type]:
40-
return self.entries[:]
44+
def _default_working(self) -> List[T]:
45+
if self.first not in self.entries:
46+
raise ValueError("First entry not found")
47+
temp = self.entries[:]
48+
popped = temp.pop(0)
49+
while popped != self.first:
50+
popped = temp.pop(0)
51+
temp.insert(0, popped)
52+
return temp
4153

4254
@entries.validator
4355
def _check_entries(self, attribute: str, value: int) -> None:
4456
if not value:
4557
raise ValueError("Entries must not be empty")
4658

47-
def next(self) -> Type:
59+
def next(self) -> T:
4860
"""Get the next entry from the queue."""
4961
if len(self._working) == 0:
5062
self._working.extend(self.entries)

tests/test_util.py

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,83 @@ def test_constructor_single(self):
2222
assert queue.next() == "a"
2323
assert queue.next() == "a"
2424

25+
def test_constructor_single_none(self):
26+
queue = CircularQueue([None])
27+
assert queue.entries == [None]
28+
assert queue.next() is None
29+
assert queue.next() is None
30+
assert queue.next() is None
31+
assert queue.next() is None
32+
assert queue.next() is None
33+
assert queue.next() is None
34+
2535
def test_constructor_multiple(self):
26-
queue = CircularQueue(["a", "b", "c"])
27-
assert queue.entries == ["a", "b", "c"]
36+
queue = CircularQueue(["a", "b", "c", "d", "e", None])
37+
assert queue.entries == ["a", "b", "c", "d", "e", None]
38+
assert queue.next() == "a"
39+
assert queue.next() == "b"
40+
assert queue.next() == "c"
41+
assert queue.next() == "d"
42+
assert queue.next() == "e"
43+
assert queue.next() is None
44+
assert queue.next() == "a"
45+
assert queue.next() == "b"
46+
assert queue.next() == "c"
47+
assert queue.next() == "d"
48+
assert queue.next() == "e"
49+
assert queue.next() is None
50+
assert queue.next() == "a"
51+
52+
def test_constructor_multiple_first(self):
53+
queue = CircularQueue(["a", "b", "c", "d", "e", None], first="c")
54+
assert queue.entries == ["a", "b", "c", "d", "e", None]
55+
assert queue.next() == "c"
56+
assert queue.next() == "d"
57+
assert queue.next() == "e"
58+
assert queue.next() is None
2859
assert queue.next() == "a"
2960
assert queue.next() == "b"
3061
assert queue.next() == "c"
62+
assert queue.next() == "d"
63+
assert queue.next() == "e"
64+
assert queue.next() is None
65+
assert queue.next() == "a"
66+
assert queue.next() == "b"
67+
assert queue.next() == "c"
68+
69+
def test_constructor_multiple_first_none(self):
70+
queue = CircularQueue(["a", "b", "c", "d", "e", None], first=None)
71+
assert queue.entries == ["a", "b", "c", "d", "e", None]
72+
assert queue.next() is None
3173
assert queue.next() == "a"
3274
assert queue.next() == "b"
3375
assert queue.next() == "c"
76+
assert queue.next() == "d"
77+
assert queue.next() == "e"
78+
assert queue.next() is None
79+
assert queue.next() == "a"
80+
assert queue.next() == "b"
81+
assert queue.next() == "c"
82+
assert queue.next() == "d"
83+
assert queue.next() == "e"
84+
assert queue.next() is None
85+
86+
def test_constructor_first_invalid(self):
87+
with pytest.raises(ValueError):
88+
CircularQueue([], first=None)
89+
with pytest.raises(ValueError):
90+
CircularQueue([], first="")
91+
with pytest.raises(ValueError):
92+
CircularQueue([], first="f")
93+
with pytest.raises(ValueError):
94+
CircularQueue(["a"], first=None)
95+
with pytest.raises(ValueError):
96+
CircularQueue(["a"], first="")
97+
with pytest.raises(ValueError):
98+
CircularQueue(["a"], first="f")
99+
with pytest.raises(ValueError):
100+
CircularQueue(["a", "b", "c", "d", "e"], first=None)
101+
with pytest.raises(ValueError):
102+
CircularQueue(["a", "b", "c", "d", "e"], first="")
103+
with pytest.raises(ValueError):
104+
CircularQueue(["a", "b", "c", "d", "e"], first="f")

0 commit comments

Comments
 (0)