Skip to content

Commit 8259c4b

Browse files
authored
Miguel 3.6 - Animal Shelter [Python] (#87)
1 parent 03a61d3 commit 8259c4b

File tree

1 file changed

+276
-0
lines changed

1 file changed

+276
-0
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
"""3.6 - Animal Shelter:
2+
An animal shelter, which holds only dogs and cats, operates on a strictly
3+
"first in, first out" basis. People must adopt either the "oldest" (based on arrival time)
4+
animal of all animals at the shelter, or they can select whether they would prefer
5+
a dog or a cat (and will receive the oldest animal of that type). They cannot
6+
select which specific animal they would like. Create the data structures to maintain this system
7+
and implement operations such as enqueue, dequeueAny, dequeueDog, and
8+
dequeueCat. You may use the built-in LinkedList data structure.
9+
"""
10+
import unittest
11+
12+
from collections import deque
13+
from dataclasses import dataclass
14+
from enum import Enum
15+
from typing import Optional
16+
17+
18+
class AnimalTypeEnum(Enum):
19+
DOG = 'dog'
20+
CAT = 'cat'
21+
22+
23+
@dataclass
24+
class Animal:
25+
name: str
26+
species: AnimalTypeEnum
27+
28+
@dataclass
29+
class QueuedAnimal:
30+
animal: Animal
31+
timestamp: int
32+
33+
class AnimalShelter:
34+
"""We will have each animal instance
35+
maintain a timestamp. This timestamp will
36+
be used to determine the globally "oldest"
37+
(based on arrival time) animal.
38+
"""
39+
def __init__(self):
40+
# FIFO, a queue of QueuedAnimals
41+
self.dogs: Deque[QueuedAnimal] = deque()
42+
self.cats: Deque[QueuedAnimal] = deque()
43+
self._species_map = {
44+
AnimalTypeEnum.DOG.value: self.dogs,
45+
AnimalTypeEnum.CAT.value: self.cats
46+
}
47+
self._global_timestamp = 0
48+
self._size = 0
49+
50+
def _enqueue(self, animal: Animal) -> None:
51+
# list will grow right [<animal_1>, <animal_2>, ..., <animal_n>]
52+
# Animal will be encapsulated in QueuedAnimal, so hide the timestamp
53+
# usage from the caller
54+
queued_animal = QueuedAnimal(animal, self._global_timestamp)
55+
self._global_timestamp += 1
56+
self._species_map[animal.species].append(queued_animal)
57+
58+
def enqueue(self, *animals: Animal) -> None:
59+
for animal in animals:
60+
self._enqueue(animal)
61+
62+
def dequeueAny(self) -> Animal:
63+
if (len(self.dogs) + len(self.cats)) == 0:
64+
raise IndexError('Animal Shelter is empty.')
65+
# oldest animals will be stored in the left-most index of
66+
# their corresponding queues.
67+
# we will return the oldest animal of the two queues.
68+
if len(self.dogs) >= 1 and len(self.cats) == 0:
69+
return self.dequeueDog()
70+
elif len(self.dogs) == 0 and len(self.cats) >= 1:
71+
return self.dequeueCat()
72+
# otherwise, pick oldest from overall
73+
oldest_dog: QueuedAnimal = self.dogs[0]
74+
oldest_cat: QueuedAnimal = self.cats[0]
75+
if oldest_dog.timestamp < oldest_cat.timestamp:
76+
return self.dequeueDog()
77+
else:
78+
return self.dequeueCat()
79+
80+
def dequeueDog(self) -> Animal:
81+
if len(self.dogs) == 0:
82+
raise IndexError('No dogs available to adopt.')
83+
return self.dogs.popleft().animal
84+
85+
def dequeueCat(self) -> Animal:
86+
if len(self.cats) == 0:
87+
raise IndexError('No cats available to adopt.')
88+
return self.cats.popleft().animal
89+
90+
def __len__(self) -> int:
91+
return len(self.dogs) + len(self.cats)
92+
93+
94+
class AnimalShelterTest(unittest.TestCase):
95+
def test_enqueue(self):
96+
# create animal shelter
97+
a = AnimalShelter()
98+
self.assertEqual(len(a), 0)
99+
# add a few dogs...
100+
d1 = Animal('Geralt', 'dog')
101+
d2 = Animal('Taco', 'dog')
102+
d3 = Animal('Lucky', 'dog')
103+
d4 = Animal('Nova', 'dog')
104+
a.enqueue(d1, d2, d3, d4)
105+
self.assertEqual(len(a), 4)
106+
self.assertEqual(len(a.dogs), 4)
107+
self.assertEqual(len(a.cats), 0)
108+
self.assertEqual(a.dogs[0].timestamp, 0)
109+
self.assertEqual(a.dogs[1].timestamp, 1)
110+
self.assertEqual(a.dogs[2].timestamp, 2)
111+
self.assertEqual(a.dogs[3].timestamp, 3)
112+
# now, add a few cats...
113+
c1 = Animal('Curio', 'cat')
114+
c2 = Animal('Chai', 'cat')
115+
c3 = Animal('Alma', 'cat')
116+
c4 = Animal('Blanco', 'cat')
117+
a.enqueue(c1, c2, c3, c4)
118+
self.assertEqual(len(a), 8)
119+
self.assertEqual(len(a.dogs), 4)
120+
self.assertEqual(len(a.cats), 4)
121+
self.assertEqual(a.cats[0].timestamp, 4)
122+
self.assertEqual(a.cats[1].timestamp, 5)
123+
self.assertEqual(a.cats[2].timestamp, 6)
124+
self.assertEqual(a.cats[3].timestamp, 7)
125+
126+
def test_dequeue_dog_and_cat_both_empty(self):
127+
a = AnimalShelter()
128+
self.assertRaises(IndexError, lambda: a.dequeueDog())
129+
self.assertRaises(IndexError, lambda: a.dequeueCat())
130+
131+
def test_dequeue_dog_and_cat_queue(self):
132+
a = AnimalShelter()
133+
d1 = Animal('Lucky', 'dog')
134+
d2 = Animal('Nova', 'dog')
135+
d3 = Animal('Geralt', 'dog')
136+
a.enqueue(d1, d2, d3)
137+
self.assertEqual(len(a), 3)
138+
self.assertEqual(len(a.dogs), 3)
139+
self.assertEqual(len(a.cats), 0)
140+
d = a.dequeueDog()
141+
self.assertEqual(len(a), 2)
142+
self.assertEqual(len(a.dogs), 2)
143+
self.assertEqual(len(a.cats), 0)
144+
self.assertEqual(d1, d)
145+
d = a.dequeueDog()
146+
self.assertEqual(len(a), 1)
147+
self.assertEqual(len(a.dogs), 1)
148+
self.assertEqual(len(a.cats), 0)
149+
self.assertEqual(d2, d)
150+
d = a.dequeueDog()
151+
self.assertEqual(len(a), 0)
152+
self.assertEqual(len(a.dogs), 0)
153+
self.assertEqual(len(a.cats), 0)
154+
self.assertEqual(d3, d)
155+
self.assertRaises(IndexError, lambda: a.dequeueDog())
156+
157+
def test_dequeue_cat(self):
158+
a = AnimalShelter()
159+
c1 = Animal('Curio', 'cat')
160+
c2 = Animal('Chai', 'cat')
161+
c3 = Animal('Alma', 'cat')
162+
a.enqueue(c1, c2, c3)
163+
self.assertEqual(len(a), 3)
164+
self.assertEqual(len(a.dogs), 0)
165+
self.assertEqual(len(a.cats), 3)
166+
c = a.dequeueCat()
167+
self.assertEqual(len(a), 2)
168+
self.assertEqual(len(a.dogs), 0)
169+
self.assertEqual(len(a.cats), 2)
170+
self.assertEqual(c1, c)
171+
c = a.dequeueCat()
172+
self.assertEqual(len(a), 1)
173+
self.assertEqual(len(a.dogs), 0)
174+
self.assertEqual(len(a.cats), 1)
175+
self.assertEqual(c2, c)
176+
c = a.dequeueCat()
177+
self.assertEqual(len(a), 0)
178+
self.assertEqual(len(a.dogs), 0)
179+
self.assertEqual(len(a.cats), 0)
180+
self.assertEqual(c3, c)
181+
self.assertRaises(IndexError, lambda: a.dequeueCat())
182+
183+
def test_index_err_raised_on_empty_dequeue(self):
184+
a = AnimalShelter()
185+
self.assertRaises(IndexError, lambda: a.dequeueAny())
186+
187+
def test_dequeue_any_only_dogs(self):
188+
a = AnimalShelter()
189+
# scenario 1: Only dogs
190+
d1 = Animal('Lucky', 'dog')
191+
d2 = Animal('Nova', 'dog')
192+
d3 = Animal('Geralt', 'dog')
193+
a.enqueue(d1, d2, d3)
194+
self.assertEqual(len(a), 3)
195+
self.assertEqual(len(a.dogs), 3)
196+
self.assertEqual(len(a.cats), 0)
197+
d = a.dequeueAny()
198+
self.assertEqual(len(a), 2)
199+
self.assertEqual(len(a.dogs), 2)
200+
self.assertEqual(len(a.cats), 0)
201+
self.assertEqual(d1, d)
202+
d = a.dequeueAny()
203+
self.assertEqual(len(a), 1)
204+
self.assertEqual(len(a.dogs), 1)
205+
self.assertEqual(len(a.cats), 0)
206+
self.assertEqual(d2, d)
207+
d = a.dequeueAny()
208+
self.assertEqual(len(a), 0)
209+
self.assertEqual(len(a.dogs), 0)
210+
self.assertEqual(len(a.cats), 0)
211+
self.assertEqual(d3, d)
212+
self.assertRaises(IndexError, lambda: a.dequeueAny())
213+
214+
def test_dequeue_any_only_cats(self):
215+
a = AnimalShelter()
216+
# scenario 2: Only cats
217+
c1 = Animal('Curio', 'cat')
218+
c2 = Animal('Chai', 'cat')
219+
c3 = Animal('Alma', 'cat')
220+
a.enqueue(c1, c2, c3)
221+
self.assertEqual(len(a), 3)
222+
self.assertEqual(len(a.dogs), 0)
223+
self.assertEqual(len(a.cats), 3)
224+
c = a.dequeueAny()
225+
self.assertEqual(len(a), 2)
226+
self.assertEqual(len(a.dogs), 0)
227+
self.assertEqual(len(a.cats), 2)
228+
self.assertEqual(c1, c)
229+
c = a.dequeueAny()
230+
self.assertEqual(len(a), 1)
231+
self.assertEqual(len(a.dogs), 0)
232+
self.assertEqual(len(a.cats), 1)
233+
self.assertEqual(c2, c)
234+
c = a.dequeueAny()
235+
self.assertEqual(len(a), 0)
236+
self.assertEqual(len(a.dogs), 0)
237+
self.assertEqual(len(a.cats), 0)
238+
self.assertEqual(c3, c)
239+
self.assertRaises(IndexError, lambda: a.dequeueAny())
240+
241+
def test_dequeue_any_cats_and_dogs_oldest_is_dog(self):
242+
a = AnimalShelter()
243+
# scenario 3: both cats and dogs, but global oldest is dog
244+
d1 = Animal('Lucky', 'dog')
245+
d2 = Animal('Nova', 'dog')
246+
d3 = Animal('Geralt', 'dog')
247+
c1 = Animal('Curio', 'cat')
248+
c2 = Animal('Chai', 'cat')
249+
c3 = Animal('Alma', 'cat')
250+
a.enqueue(d1, c1, d2, c2, d3, c3)
251+
self.assertEqual(len(a), 6)
252+
oldest = a.dequeueAny()
253+
self.assertEqual(d1, oldest)
254+
self.assertEqual(len(a), 5)
255+
self.assertEqual(len(a.dogs), 2)
256+
self.assertEqual(len(a.cats), 3)
257+
258+
def test_dequeue_any_cats_and_dogs_oldest_is_cat(self):
259+
a = AnimalShelter()
260+
# scenario 4: both cats and dogs, but global oldest is cat
261+
d1 = Animal('Lucky', 'dog')
262+
d2 = Animal('Nova', 'dog')
263+
d3 = Animal('Geralt', 'dog')
264+
c1 = Animal('Curio', 'cat')
265+
c2 = Animal('Chai', 'cat')
266+
c3 = Animal('Alma', 'cat')
267+
a.enqueue(c1, d1, d2, c2, d3, c3)
268+
oldest = a.dequeueAny()
269+
self.assertEqual(c1, oldest)
270+
self.assertEqual(len(a), 5)
271+
self.assertEqual(len(a.dogs), 3)
272+
self.assertEqual(len(a.cats), 2)
273+
274+
275+
if __name__ == '__main__':
276+
unittest.main()

0 commit comments

Comments
 (0)