Skip to content

Commit 6dbc755

Browse files
committed
sync from Atlas repo
1 parent 661b68b commit 6dbc755

20 files changed

+2281
-31
lines changed

classes/bingoaddable.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
======================
3+
AddableBingoCage tests
4+
======================
5+
6+
7+
Tests for __add__ and __iadd__:
8+
9+
>>> vowels = 'AEIOU'
10+
>>> globe = AddableBingoCage(vowels)
11+
>>> len(globe)
12+
5
13+
>>> globe.pop() in vowels
14+
True
15+
>>> len(globe)
16+
4
17+
>>> globe2 = AddableBingoCage('XYZ')
18+
>>> globe3 = globe + globe2
19+
>>> len(globe3)
20+
7
21+
>>> void = globe + [10, 20]
22+
Traceback (most recent call last):
23+
...
24+
TypeError: unsupported operand type(s) for +: 'AddableBingoCage' and 'list'
25+
26+
27+
Tests for __add__ and __iadd__:
28+
29+
>>> globe_orig = globe
30+
>>> len(globe)
31+
4
32+
>>> globe += globe2
33+
>>> len(globe)
34+
7
35+
>>> globe += [10, 20]
36+
>>> len(globe)
37+
9
38+
>>> globe is globe_orig
39+
True
40+
41+
"""
42+
43+
# BEGIN ADDABLE_BINGO
44+
import itertools # <1>
45+
from bingobase import BingoCage
46+
47+
48+
class AddableBingoCage(BingoCage): # <2>
49+
50+
def __add__(self, other):
51+
if isinstance(other, AddableBingoCage): # <3>
52+
return AddableBingoCage(itertools.chain(self, other)) # <4>
53+
else:
54+
return NotImplemented
55+
56+
def __iadd__(self, other):
57+
self.load(other) # <5>
58+
return self # <6>
59+
# END ADDABLE_BINGO

classes/bingobase.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""
2+
===============
3+
BingoCage tests
4+
===============
5+
6+
7+
Create and load instance from iterable::
8+
9+
>>> balls = list(range(3))
10+
>>> globe = BingoCage(balls)
11+
>>> len(globe)
12+
3
13+
14+
15+
Pop and collect balls::
16+
17+
>>> picks = []
18+
>>> picks.append(globe.pop())
19+
>>> picks.append(globe.pop())
20+
>>> picks.append(globe.pop())
21+
22+
23+
Check state and results::
24+
25+
>>> len(globe)
26+
0
27+
>>> sorted(picks) == balls
28+
True
29+
30+
31+
Reload::
32+
33+
>>> globe.load(balls)
34+
>>> len(globe)
35+
3
36+
>>> picks = [globe.pop() for i in balls]
37+
>>> len(globe)
38+
0
39+
40+
41+
Load and pop 20 balls to verify that the order has changed::
42+
43+
>>> balls = list(range(20))
44+
>>> globe = BingoCage(balls)
45+
>>> picks = []
46+
>>> while globe:
47+
... picks.append(globe.pop())
48+
>>> len(picks) == len(balls)
49+
True
50+
>>> picks != balls
51+
True
52+
53+
54+
Also check that the order is not simply reversed either::
55+
56+
>>> picks[::-1] != balls
57+
True
58+
59+
Note: last 2 tests above each have 1 chance in 20! (factorial) of
60+
failing even if the implementation is OK. 1/20!, or approximately
61+
4.11e-19, is the probability of the 20 balls coming out, by chance,
62+
in the exact order the were loaded.
63+
64+
Check that `LookupError` (or a subclass) is the exception thrown
65+
when the device is empty::
66+
67+
>>> globe = BingoCage([])
68+
>>> try:
69+
... globe.pop()
70+
... except LookupError as exc:
71+
... print('OK')
72+
OK
73+
74+
"""
75+
76+
import random
77+
78+
79+
class BingoCage():
80+
81+
def __init__(self, iterable):
82+
self._balls = []
83+
self.load(iterable)
84+
85+
def load(self, iterable):
86+
self._balls.extend(iterable)
87+
random.shuffle(self._balls)
88+
89+
def __len__(self):
90+
return len(self._balls)
91+
92+
def pop(self):
93+
return self._balls.pop()
94+
95+
def __iter__(self):
96+
return reversed(self._balls)

0 commit comments

Comments
 (0)