This exercise asks us to add support for updates which come out of order, that is, a newer update is followed by an older update. We need to use the timestamp to determine which update is newer and which is older.
The following is a test case for this requirement:
def test_price_is_the_latest_even_if_updates_are_made_out_of_order(self): self.goog.update(datetime(2014, 2, 13), price=8) self.goog.update(datetime(2014, 2, 12), price=10) self.assertEqual(8, self.goog.price)
In the test above, we first give the update for February 13, followed by the update for February 12. We then assert that the price attribute returns the latest price (for February 13). The test fails of course.
In order to make this test pass, we can't simply add the latest update to the end of the price_history
list. We need to check the timestamp and insert it accordingly into the list, keeping it sorted by timestamp.
The bisect
module provided in the Python standard library contains the insort_left
function that inserts into a sorted list. We can use this function as follows (remember to import bisect at the top of the file):
def update(self, timestamp, price): if price < 0: raise ValueError("price should not be negative") bisect.insort_left(self.price_history, (timestamp, price))
In order to have a sorted list, the price_history
list needs to keep a list of tuples, with the timestamp as the first element. This will keep the list sorted by the timestamp. When we make this change, it breaks our other methods that expect the list to contain the price alone. We need to modify them as follows:
@property def price(self): return self.price_history[-1][1] \ if self.price_history else None def is_increasing_trend(self): return self.price_history[-3][1] < \ self.price_history[-2][1] < self.price_history[-1][1]
With the above changes, all our existing tests as well as the new test start passing.
Now that we have the tests passing, we can look at refactoring the code to make it easier to read. Since the price_history
list now contains tuples, we have to refer to the price element by tuple index, leading to statements list price_history[-1][1]
, which are not very clear. We can make this clearer by using a named tuple that allows us to assign names to the tuple values. Our refactored Stock class now looks like the following:
PriceEvent = collections.namedtuple("PriceEvent", ["timestamp", "price"]) class Stock: def __init__(self, symbol): self.symbol = symbol self.price_history = [] @property def price(self): return self.price_history[-1].price \ if self.price_history else None def update(self, timestamp, price): if price < 0: raise ValueError("price should not be negative") bisect.insort_left(self.price_history, PriceEvent(timestamp, price)) def is_increasing_trend(self): return self.price_history[-3].price < \ self.price_history[-2].price < \ self.price_history[-1].price
After the change, we run the tests to ensure that everything still works.