update from Atlas with major reorg
This commit is contained in:
28
13-op-overloading/bingo.py
Normal file
28
13-op-overloading/bingo.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# BEGIN TOMBOLA_BINGO
|
||||
|
||||
import random
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class BingoCage(Tombola): # <1>
|
||||
|
||||
def __init__(self, items):
|
||||
self._randomizer = random.SystemRandom() # <2>
|
||||
self._items = []
|
||||
self.load(items) # <3>
|
||||
|
||||
def load(self, items):
|
||||
self._items.extend(items)
|
||||
self._randomizer.shuffle(self._items) # <4>
|
||||
|
||||
def pick(self): # <5>
|
||||
try:
|
||||
return self._items.pop()
|
||||
except IndexError:
|
||||
raise LookupError('pick from empty BingoCage')
|
||||
|
||||
def __call__(self): # <7>
|
||||
self.pick()
|
||||
|
||||
# END TOMBOLA_BINGO
|
||||
@@ -4,56 +4,83 @@ AddableBingoCage tests
|
||||
======================
|
||||
|
||||
|
||||
Tests for __add__ and __iadd__:
|
||||
Tests for __add__:
|
||||
|
||||
# BEGIN ADDABLE_BINGO_ADD_DEMO
|
||||
|
||||
>>> vowels = 'AEIOU'
|
||||
>>> globe = AddableBingoCage(vowels)
|
||||
>>> len(globe)
|
||||
5
|
||||
>>> globe.pop() in vowels
|
||||
>>> globe = AddableBingoCage(vowels) # <1>
|
||||
>>> globe.inspect()
|
||||
('A', 'E', 'I', 'O', 'U')
|
||||
>>> globe.pick() in vowels # <2>
|
||||
True
|
||||
>>> len(globe)
|
||||
>>> len(globe.inspect()) # <3>
|
||||
4
|
||||
>>> globe2 = AddableBingoCage('XYZ')
|
||||
>>> globe2 = AddableBingoCage('XYZ') # <4>
|
||||
>>> globe3 = globe + globe2
|
||||
>>> len(globe3)
|
||||
>>> len(globe3.inspect()) # <5>
|
||||
7
|
||||
>>> void = globe + [10, 20]
|
||||
>>> void = globe + [10, 20] # <6>
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for +: 'AddableBingoCage' and 'list'
|
||||
|
||||
|
||||
Tests for __add__ and __iadd__:
|
||||
# END ADDABLE_BINGO_ADD_DEMO
|
||||
|
||||
>>> globe_orig = globe
|
||||
>>> len(globe)
|
||||
Tests for __iadd__:
|
||||
|
||||
# BEGIN ADDABLE_BINGO_IADD_DEMO
|
||||
|
||||
>>> globe_orig = globe # <1>
|
||||
>>> len(globe.inspect()) # <2>
|
||||
4
|
||||
>>> globe += globe2
|
||||
>>> len(globe)
|
||||
>>> globe += globe2 # <3>
|
||||
>>> len(globe.inspect())
|
||||
7
|
||||
>>> globe += [10, 20]
|
||||
>>> len(globe)
|
||||
>>> globe += ['M', 'N'] # <4>
|
||||
>>> len(globe.inspect())
|
||||
9
|
||||
>>> globe is globe_orig
|
||||
>>> globe is globe_orig # <5>
|
||||
True
|
||||
>>> globe += 1 # <6>
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: right operand in += must be 'AddableBingoCage' or an iterable
|
||||
|
||||
# END ADDABLE_BINGO_IADD_DEMO
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN ADDABLE_BINGO
|
||||
import itertools # <1>
|
||||
from bingobase import BingoCage
|
||||
|
||||
from tombola import Tombola
|
||||
from bingo import BingoCage
|
||||
|
||||
|
||||
class AddableBingoCage(BingoCage): # <2>
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, AddableBingoCage): # <3>
|
||||
return AddableBingoCage(itertools.chain(self, other)) # <4>
|
||||
if isinstance(other, Tombola): # <3>
|
||||
return AddableBingoCage(self.inspect() + other.inspect()) # <6>
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __iadd__(self, other):
|
||||
self.load(other) # <5>
|
||||
return self # <6>
|
||||
if isinstance(other, Tombola):
|
||||
other_iterable = other.inspect() # <4>
|
||||
else:
|
||||
try:
|
||||
other_iterable = iter(other) # <5>
|
||||
except TypeError: # <6>
|
||||
self_cls = type(self).__name__
|
||||
msg = "right operand in += must be {!r} or an iterable"
|
||||
raise TypeError(msg.format(self_cls))
|
||||
self.load(other_iterable) # <7>
|
||||
return self # <8>
|
||||
|
||||
|
||||
|
||||
|
||||
# END ADDABLE_BINGO
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
"""
|
||||
===============
|
||||
BingoCage tests
|
||||
===============
|
||||
|
||||
|
||||
Create and load instance from iterable::
|
||||
|
||||
>>> balls = list(range(3))
|
||||
>>> globe = BingoCage(balls)
|
||||
>>> len(globe)
|
||||
3
|
||||
|
||||
|
||||
Pop and collect balls::
|
||||
|
||||
>>> picks = []
|
||||
>>> picks.append(globe.pop())
|
||||
>>> picks.append(globe.pop())
|
||||
>>> picks.append(globe.pop())
|
||||
|
||||
|
||||
Check state and results::
|
||||
|
||||
>>> len(globe)
|
||||
0
|
||||
>>> sorted(picks) == balls
|
||||
True
|
||||
|
||||
|
||||
Reload::
|
||||
|
||||
>>> globe.load(balls)
|
||||
>>> len(globe)
|
||||
3
|
||||
>>> picks = [globe.pop() for i in balls]
|
||||
>>> len(globe)
|
||||
0
|
||||
|
||||
|
||||
Load and pop 20 balls to verify that the order has changed::
|
||||
|
||||
>>> balls = list(range(20))
|
||||
>>> globe = BingoCage(balls)
|
||||
>>> picks = []
|
||||
>>> while globe:
|
||||
... picks.append(globe.pop())
|
||||
>>> len(picks) == len(balls)
|
||||
True
|
||||
>>> picks != balls
|
||||
True
|
||||
|
||||
|
||||
Also check that the order is not simply reversed either::
|
||||
|
||||
>>> picks[::-1] != balls
|
||||
True
|
||||
|
||||
Note: last 2 tests above each have 1 chance in 20! (factorial) of
|
||||
failing even if the implementation is OK. 1/20!, or approximately
|
||||
4.11e-19, is the probability of the 20 balls coming out, by chance,
|
||||
in the exact order the were loaded.
|
||||
|
||||
Check that `LookupError` (or a subclass) is the exception thrown
|
||||
when the device is empty::
|
||||
|
||||
>>> globe = BingoCage([])
|
||||
>>> try:
|
||||
... globe.pop()
|
||||
... except LookupError as exc:
|
||||
... print('OK')
|
||||
OK
|
||||
|
||||
"""
|
||||
|
||||
import random
|
||||
|
||||
|
||||
class BingoCage():
|
||||
|
||||
def __init__(self, iterable):
|
||||
self._balls = []
|
||||
self.load(iterable)
|
||||
|
||||
def load(self, iterable):
|
||||
self._balls.extend(iterable)
|
||||
random.shuffle(self._balls)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._balls)
|
||||
|
||||
def pop(self):
|
||||
return self._balls.pop()
|
||||
|
||||
def __iter__(self):
|
||||
return reversed(self._balls)
|
||||
35
13-op-overloading/tombola.py
Normal file
35
13-op-overloading/tombola.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# BEGIN TOMBOLA_ABC
|
||||
|
||||
import abc
|
||||
|
||||
class Tombola(abc.ABC): # <1>
|
||||
|
||||
@abc.abstractmethod
|
||||
def load(self, iterable): # <2>
|
||||
"""Add items from an iterable."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def pick(self): # <3>
|
||||
"""Remove item at random, returning it.
|
||||
|
||||
This method should raise `LookupError` when the instance is empty.
|
||||
"""
|
||||
|
||||
def loaded(self): # <4>
|
||||
"""Return `True` if there's at least 1 item, `False` otherwise."""
|
||||
return bool(self.inspect()) # <5>
|
||||
|
||||
|
||||
def inspect(self):
|
||||
"""Return a sorted tuple with the items currently inside."""
|
||||
items = []
|
||||
while True: # <6>
|
||||
try:
|
||||
items.append(self.pick())
|
||||
except LookupError:
|
||||
break
|
||||
self.load(items) # <7>
|
||||
return tuple(sorted(items))
|
||||
|
||||
|
||||
# END TOMBOLA_ABC
|
||||
Reference in New Issue
Block a user