#!/usr/bin/env PYTHONHASHSEED=1234 python3 # Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Reproduce book environment import random random.seed(1234) import logging from pprint import pprint from sys import stdout as STDOUT # Write all output to a temporary directory import atexit import gc import io import os import tempfile TEST_DIR = tempfile.TemporaryDirectory() atexit.register(TEST_DIR.cleanup) # Make sure Windows processes exit cleanly OLD_CWD = os.getcwd() atexit.register(lambda: os.chdir(OLD_CWD)) os.chdir(TEST_DIR.name) def close_open_files(): everything = gc.get_objects() for obj in everything: if isinstance(obj, io.IOBase): obj.close() atexit.register(close_open_files) # Example 1 class Book: def __init__(self, title, due_date): self.title = title self.due_date = due_date # Example 2 def add_book(queue, book): queue.append(book) queue.sort(key=lambda x: x.due_date, reverse=True) queue = [] add_book(queue, Book('Don Quixote', '2019-06-07')) add_book(queue, Book('Frankenstein', '2019-06-05')) add_book(queue, Book('Les Misérables', '2019-06-08')) add_book(queue, Book('War and Peace', '2019-06-03')) # Example 3 class NoOverdueBooks(Exception): pass def next_overdue_book(queue, now): if queue: book = queue[-1] if book.due_date < now: queue.pop() return book raise NoOverdueBooks # Example 4 now = '2019-06-10' found = next_overdue_book(queue, now) print(found.title) found = next_overdue_book(queue, now) print(found.title) # Example 5 def return_book(queue, book): queue.remove(book) queue = [] book = Book('Treasure Island', '2019-06-04') add_book(queue, book) print('Before return:', [x.title for x in queue]) return_book(queue, book) print('After return: ', [x.title for x in queue]) # Example 6 try: next_overdue_book(queue, now) except NoOverdueBooks: pass # Expected else: assert False # Doesn't happen # Example 7 import random import timeit def print_results(count, tests): avg_iteration = sum(tests) / len(tests) print(f'Count {count:>5,} takes {avg_iteration:.6f}s') return count, avg_iteration def print_delta(before, after): before_count, before_time = before after_count, after_time = after growth = 1 + (after_count - before_count) / before_count slowdown = 1 + (after_time - before_time) / before_time print(f'{growth:>4.1f}x data size, {slowdown:>4.1f}x time') def list_overdue_benchmark(count): def prepare(): to_add = list(range(count)) random.shuffle(to_add) return [], to_add def run(queue, to_add): for i in to_add: queue.append(i) queue.sort(reverse=True) while queue: queue.pop() tests = timeit.repeat( setup='queue, to_add = prepare()', stmt=f'run(queue, to_add)', globals=locals(), repeat=100, number=1) return print_results(count, tests) # Example 8 baseline = list_overdue_benchmark(500) for count in (1_000, 1_500, 2_000): print() comparison = list_overdue_benchmark(count) print_delta(baseline, comparison) # Example 9 def list_return_benchmark(count): def prepare(): queue = list(range(count)) random.shuffle(queue) to_return = list(range(count)) random.shuffle(to_return) return queue, to_return def run(queue, to_return): for i in to_return: queue.remove(i) tests = timeit.repeat( setup='queue, to_return = prepare()', stmt=f'run(queue, to_return)', globals=locals(), repeat=100, number=1) return print_results(count, tests) # Example 10 baseline = list_return_benchmark(500) for count in (1_000, 1_500, 2_000): print() comparison = list_return_benchmark(count) print_delta(baseline, comparison) # Example 11 from heapq import heappush def add_book(queue, book): heappush(queue, book) # Example 12 try: queue = [] add_book(queue, Book('Little Women', '2019-06-05')) add_book(queue, Book('The Time Machine', '2019-05-30')) except: logging.exception('Expected') else: assert False # Example 13 import functools @functools.total_ordering class Book: def __init__(self, title, due_date): self.title = title self.due_date = due_date def __lt__(self, other): return self.due_date < other.due_date # Example 14 queue = [] add_book(queue, Book('Pride and Prejudice', '2019-06-01')) add_book(queue, Book('The Time Machine', '2019-05-30')) add_book(queue, Book('Crime and Punishment', '2019-06-06')) add_book(queue, Book('Wuthering Heights', '2019-06-12')) print([b.title for b in queue]) # Example 15 queue = [ Book('Pride and Prejudice', '2019-06-01'), Book('The Time Machine', '2019-05-30'), Book('Crime and Punishment', '2019-06-06'), Book('Wuthering Heights', '2019-06-12'), ] queue.sort() print([b.title for b in queue]) # Example 16 from heapq import heapify queue = [ Book('Pride and Prejudice', '2019-06-01'), Book('The Time Machine', '2019-05-30'), Book('Crime and Punishment', '2019-06-06'), Book('Wuthering Heights', '2019-06-12'), ] heapify(queue) print([b.title for b in queue]) # Example 17 from heapq import heappop def next_overdue_book(queue, now): if queue: book = queue[0] # Most overdue first if book.due_date < now: heappop(queue) # Remove the overdue book return book raise NoOverdueBooks # Example 18 now = '2019-06-02' book = next_overdue_book(queue, now) print(book.title) book = next_overdue_book(queue, now) print(book.title) try: next_overdue_book(queue, now) except NoOverdueBooks: pass # Expected else: assert False # Doesn't happen # Example 19 def heap_overdue_benchmark(count): def prepare(): to_add = list(range(count)) random.shuffle(to_add) return [], to_add def run(queue, to_add): for i in to_add: heappush(queue, i) while queue: heappop(queue) tests = timeit.repeat( setup='queue, to_add = prepare()', stmt=f'run(queue, to_add)', globals=locals(), repeat=100, number=1) return print_results(count, tests) # Example 20 baseline = heap_overdue_benchmark(500) for count in (1_000, 1_500, 2_000): print() comparison = heap_overdue_benchmark(count) print_delta(baseline, comparison) # Example 21 @functools.total_ordering class Book: def __init__(self, title, due_date): self.title = title self.due_date = due_date self.returned = False # New field def __lt__(self, other): return self.due_date < other.due_date # Example 22 def next_overdue_book(queue, now): while queue: book = queue[0] if book.returned: heappop(queue) continue if book.due_date < now: heappop(queue) return book break raise NoOverdueBooks queue = [] book = Book('Pride and Prejudice', '2019-06-01') add_book(queue, book) book = Book('The Time Machine', '2019-05-30') add_book(queue, book) book.returned = True book = Book('Crime and Punishment', '2019-06-06') add_book(queue, book) book.returned = True book = Book('Wuthering Heights', '2019-06-12') add_book(queue, book) now = '2019-06-11' book = next_overdue_book(queue, now) assert book.title == 'Pride and Prejudice' try: next_overdue_book(queue, now) except NoOverdueBooks: pass # Expected else: assert False # Doesn't happen # Example 23 def return_book(queue, book): book.returned = True assert not book.returned return_book(queue, book) assert book.returned