Mercurial > public > think_complexity
changeset 22:84e183b40c63
Added exercises 1 and 2 from chapter 4.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Mon, 31 Dec 2012 18:58:21 -0600 |
parents | 55880b29b4d4 |
children | 74c9d126bd05 |
files | Graph.py ch3ex6.py ch4ex1.py ch4ex2.py |
diffstat | 4 files changed, 257 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/Graph.py Sat Dec 29 22:16:11 2012 -0600 +++ b/Graph.py Mon Dec 31 18:58:21 2012 -0600 @@ -219,7 +219,7 @@ visit_func(v) # Add the adjacent neigbors to the node to the queue - queue.extend(self.out_vertices(v)) + queue.extend(c for c in self.out_vertices(v) if c not in visited) return visited
--- a/ch3ex6.py Sat Dec 29 22:16:11 2012 -0600 +++ b/ch3ex6.py Mon Dec 31 18:58:21 2012 -0600 @@ -16,7 +16,6 @@ behavior, but small enough to run quickly." """ -import os import random import timeit
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ch4ex1.py Mon Dec 31 18:58:21 2012 -0600 @@ -0,0 +1,82 @@ +"""Section 4.2, exercise 1 of Allen Downey's Think Complexity book. + +"Write an implementation of a FIFO using either a doubly-linked list or +a circular buffer." + +""" + +class Node(object): + + def __init__(self, data): + self.prev = self.next = None + self.data = data + +class Fifo(object): + + def __init__(self): + self.first = None + self.last = None + + def append(self, val): + """Append a value onto the back of the queue.""" + + node = Node(val) + + if self.last: + self.last.next = node + node.prev = self.last + + self.last = node + + if not self.first: + self.first = node + + def pop(self): + """Remove and return the value at the front of the queue. + + Raises an IndexError if the queue is empty. + """ + if not self.first: + raise IndexError + + node = self.first + self.first = node.next + node.prev = node.next = None + + if self.last is node: + self.last = None + + return node.data + + def empty(self): + """Returns True if the queue is empty and False otherwise.""" + return self.first is None and self.last is None + + +if __name__ == '__main__': + import random + values = range(100) + random.shuffle(values) + + fifo = Fifo() + assert fifo.empty() + + for v in values: + fifo.append(v) + + got = [] + while not fifo.empty(): + got.append(fifo.pop()) + + assert got == values + + fifo.append(42) + assert fifo.pop() == 42 + assert fifo.empty() + + try: + fifo.pop() + except IndexError: + pass + else: + assert False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ch4ex2.py Mon Dec 31 18:58:21 2012 -0600 @@ -0,0 +1,174 @@ +"""Section 4.2, exercise 2. + +"The following implementation of a BFS contains two performance errors. What are +they? What is the actual order of growth for this algorithm? + +def bfs(top_node, visit): + 'Breadth-first search on a graph, starting at top_node.' + visited = set() + queue = [top_node] + while len(queue): + curr_node = queue.pop(0) # Dequeue + visit(curr_node) # Visit the node + visited.add(curr_node) + + # Enqueue non-visited and non-enqueued children + queue.extend(c for c in curr_node.children + if c not in visited and c not in queue) + +Test this code with a range of graph sizes and check your analysis. Then use +a FIFO implementation to fix the errors and confirm that your algorithm is +linear." + +---- + +I think the 2 performance errors are: + 1) Using a list as the queue, and popping element 0. + 2) Doing a linear search to see if the child node is already in the queue. + +To characterize this bfs, I'll inherit from my Graph class and add +a flawed_bfs() method which implements the above. I'll then plot both the +flawed_bfs and the bfs method I wrote earlier. + +This program tests complete graphs. The flawed_bfs is plotted along with a curve +of slope 3, which fits well, and shows that this search is O(|V|^3). Next, the bfs +method is plotted with a curve of slope 2, which fits well. This shows that bfs +is quadratic, or O(|V|^2), on a complete graph, which is what we expect. + +""" +import timeit +import matplotlib.pyplot as pyplot + +from Graph import Graph, Vertex + + +class FlawedGraph(Graph): + + def flawed_bfs(self, start, visit=None): + """A breadth-first search with performance problems.""" + visited = set() + queue = [start] + while len(queue): + curr_node = queue.pop(0) + if visit: + visit(curr_node) + visited.add(curr_node) + + queue.extend(c for c in self.out_vertices(curr_node) + if c not in visited and c not in queue) + +GRAPH = None +METHOD = None +V = None + + +def test_graph_search(): + + if METHOD == 'flawed_bfs': + GRAPH.flawed_bfs(V) + else: + GRAPH.bfs(V) + + +def test_func(): + + elapsed = timeit.timeit('test_graph_search()', + setup='from __main__ import test_graph_search', number=1) + return elapsed + + +def test(method): + """Tests the given function with a range of values for n. + + Returns a list of ns and a list of run times. + """ + global GRAPH, METHOD, V + + METHOD = method + factor = 10 + + # test (f) over a range of values for (n) + ns = [] + ts = [] + for i in range(10, 50): + n = factor * i + + GRAPH = FlawedGraph() + V = None + # Add n vertices and connect them + for i in range(n): + v = Vertex() + if not V: + V = v + GRAPH.add_vertex(v) + + GRAPH.add_all_edges() # make g a complete graph + + t = test_func() + + print n, t + ns.append(n) + ts.append(t) + + return ns, ts + + +def plot(ns, ts, label, color='blue', exp=1.0): + """Plots data and a fitted curve. + + ns: sequence of n (problem size) + ts: sequence of t (run time) + label: string label for the data curve + color: string color for the data curve + exp: exponent (slope) for the fitted curve + """ + tfit = fit(ns, ts, exp) + pyplot.plot(ns, tfit, color='0.7', linewidth=2, linestyle='dashed') + pyplot.plot(ns, ts, label=label, color=color, linewidth=3) + + +def fit(ns, ts, exp=1.0, index=-1): + """Fits a curve with the given exponent. + + Use the given index as a reference point, and scale all other + points accordingly. + """ + nref = ns[index] + tref = ts[index] + + tfit = [] + for n in ns: + ratio = float(n) / nref + t = ratio**exp * tref + tfit.append(t) + + return tfit + + +def make_fig(funcs): + pyplot.clf() + pyplot.xscale('log') + pyplot.yscale('log') + pyplot.title('') + pyplot.xlabel('n') + pyplot.ylabel('run time (s)') + + colors = ['red', 'green'] + + for func, color in zip(funcs, colors): + data = test(func) + exp = 3.0 if func == 'flawed_bfs' else 2.0 + plot(*data, label=func, color=color, exp=exp) + + pyplot.legend(loc=4) + + pyplot.show() + + +def main(script): + make_fig(['flawed_bfs', 'bfs']) + + +if __name__ == '__main__': + import sys + main(*sys.argv)