def test_add_edge_contains(): ''' Adding an edge between non-existant edges should cause contains to report they exist. ''' g = DirectedGraph() assert g.add_edge('a', 'b', 42) assert g.contains('a') assert g.contains('b')
def test_add_edge_nodes(): ''' Adding an edge between non-existant edges should cause those nodes to be returned by .nodes(). ''' g = DirectedGraph() assert g.add_edge('a', 'b', 42) assert len(list(g.nodes())) == 2 assert 'a' in g.nodes() assert 'b' in g.nodes()
def test_add_edge_neighbors(): ''' add_edge should connect existing nodes in the graph, causing neighbors to return the connected edges. add_edge should not overwrite existing edges and return False if the user tries. add_edge should create nodes if they do not already exist. ''' g = DirectedGraph() # Edges can connect existing nodes assert g.add_node('a') assert g.add_node('b') assert g.add_edge('a', 'b') # Default weight is 1 assert ('b', 1) in g.neighbors('a') # Adding an edge that already exists should be rejected, and return False assert g.add_edge('a', 'b', 2) == False assert ('b', 1) in g.neighbors('a') # Adding an edge with a non-existing node works just fine assert g.add_edge('b', 'c', 2) assert ('b', 1) in g.neighbors('a') assert ('c', 2) in g.neighbors('b')
def test_single_path_directed(): g = DirectedGraph() g.add_edge('a', 'b') g.add_edge('b', 'c') g.add_edge('b', 'd') g.add_edge('d', 'e') g.add_edge('e', 'f') assert depth_first_search_by_criteria(g, 'a', criterion) == ['a', 'b', 'd', 'e']
def test_single_path_directed(): g = DirectedGraph() g.add_edge('a', 'b') g.add_edge('b', 'c') g.add_edge('b', 'd') g.add_edge('d', 'e') g.add_edge('e', 'f') assert breadth_first_search(g, 'a', 'e') == ['a', 'b', 'd', 'e']
def test_multiple_paths_directed(): g = DirectedGraph() g.add_edge('a', 'b') g.add_edge('b', 'c') g.add_edge('b', 'e') g.add_edge('b', 'd') g.add_edge('d', 'e') g.add_edge('e', 'f') found_path = depth_first_search_by_criteria(g, 'a', criterion) assert found_path == ['a', 'b', 'e'] or found_path == ['a', 'b', 'd', 'e']
def test_no_path_directed_cycles(): ''' Test BFS on a simple directed graph that contains cycles, but no path to the goal. ''' g = DirectedGraph() g.add_edge('a', 'b') g.add_edge('b', 'c') g.add_edge('c', 'a') g.add_edge('b', 'd') g.add_edge('e', 'f') assert breadth_first_search_by_criteria(g, 'a', criterion) is None
def test_start_is_stop(): g = DirectedGraph() g.add_edge('a', 'b') g.add_edge('b', 'c') g.add_edge('b', 'd') g.add_edge('e', 'f') assert breadth_first_search_by_criteria(g, 'e', criterion) == ['e']
def test_start_is_stop(): g = DirectedGraph() g.add_edge('a', 'b') g.add_edge('b', 'c') g.add_edge('b', 'd') g.add_edge('e', 'f') assert breadth_first_search(g, 'a', 'a') == ['a']
def test_add_contains(): ''' Adding a node should cause contains to return true, adding a duplicate should return False, and contains should still report True for that node ''' g = DirectedGraph() assert g.add_node('a') assert g.contains('a') assert g.add_node('a') == False assert g.contains('a')
def test_no_path_directed(): ''' Test BFS on a simple directed graph that contains a no cycles, and no path to the goal. ''' g = DirectedGraph() g.add_edge('a', 'b') g.add_edge('b', 'c') g.add_edge('b', 'd') g.add_edge('e', 'f') assert breadth_first_search(g, 'a', 'e') is None
def test_single_path_with_cycles_directed(): g = DirectedGraph() g.add_edge('a', 'b') g.add_edge('b', 'c') g.add_edge('c', 'h') g.add_edge('h', 'i') g.add_edge('i', 'j') g.add_edge('j', 'e') g.add_edge('b', 'd') g.add_edge('d', 'k') g.add_edge('k', 'f') g.add_edge('f', 'b') assert breadth_first_search_by_criteria( g, 'a', criterion) == ['a', 'b', 'c', 'h', 'i', 'j', 'e']
def test_add_nodes(): ''' Adding a node should cause that node to appear in the return value of .nodes(). ''' g = DirectedGraph() assert g.add_node('a') assert 'a' in g.nodes() assert len(list(g.nodes())) == 1 assert g.add_node('a') == False assert 'a' in g.nodes() assert len(list(g.nodes())) == 1 assert g.add_node('b') assert 'a' in g.nodes() assert 'b' in g.nodes() assert len(list(g.nodes())) == 2
def test_simple_integration(): ''' test a simple use case, retesting assumptions at each step. ''' g = DirectedGraph() assert g.add_node('a') assert g.contains('a') assert len(list(g.nodes())) == 1 assert 'a' in g.nodes() assert len(list(g.neighbors('a'))) == 0 assert g.add_node('b') assert g.contains('b') assert len(list(g.nodes())) == 2 assert 'a' in g.nodes() assert 'b' in g.nodes() assert len(list(g.neighbors('a'))) == 0 assert len(list(g.neighbors('b'))) == 0 assert g.add_node('c') assert g.contains('c') assert len(list(g.nodes())) == 3 assert 'a' in g.nodes() assert 'b' in g.nodes() assert 'c' in g.nodes() assert len(list(g.neighbors('a'))) == 0 assert len(list(g.neighbors('b'))) == 0 assert len(list(g.neighbors('c'))) == 0 g.add_edge('a', 'b') edges = list(g.edges()) assert len(edges) == 1 assert ('a', 'b', 1) in edges assert g.contains('a') assert g.contains('b') assert g.contains('c') assert len(list(g.nodes())) == 3 assert 'a' in g.nodes() assert 'b' in g.nodes() assert 'c' in g.nodes() assert len(list(g.neighbors('a'))) == 1 assert ('b', 1) in g.neighbors('a') assert len(list(g.neighbors('b'))) == 0 assert len(list(g.neighbors('c'))) == 0 g.add_edge('a', 'c') edges = list(g.edges()) assert len(edges) == 2 assert ('a', 'b', 1) in edges assert ('a', 'c', 1) in edges assert g.contains('a') assert g.contains('b') assert g.contains('c') assert len(list(g.nodes())) == 3 assert 'a' in g.nodes() assert 'b' in g.nodes() assert 'c' in g.nodes() assert len(list(g.neighbors('a'))) == 2 assert ('b', 1) in g.neighbors('a') assert ('c', 1) in g.neighbors('a') assert len(list(g.neighbors('b'))) == 0 assert len(list(g.neighbors('c'))) == 0 edges = list(g.edges()) assert ('a', 'b', 1) in edges assert ('a', 'c', 1) in edges
def test_single_path_with_cycles_directed(): g = DirectedGraph() g.add_edge('a', 'b') g.add_edge('b', 'c') g.add_edge('c', 'h') g.add_edge('h', 'i') g.add_edge('i', 'j') g.add_edge('j', 'k') g.add_edge('b', 'd') g.add_edge('d', 'e') g.add_edge('e', 'f') g.add_edge('f', 'b') assert depth_first_search(g, 'a', 'k') == ['a', 'b', 'c', 'h', 'i', 'j', 'k']
def construct_three_jugs_graph(): ''' This function constructs and returns a DirectedGraph representing the three jugs problem. Each node in the graph is represented by a 3-tuple where the three values represent the amount of water currently in each jug, (twelve_liter, eight_liter, five_liter). The starting node is (12, 0, 0) -- 12 in the 12 liter, 0 in the 8 liter, and 0 in the 5 liter. Creating the graph is essentially a depth first search algorithm over the space, but instead of stopping when we find a partiucular node, we explore until the frontier is empty. ''' g = DirectedGraph() frontier = [] explored = set() # The start node frontier.append((12, 0, 0)) while len(frontier) > 0: current_state = frontier.pop() if current_state in explored: continue g.add_node(current_state) # Discover nodes and edges for the neighbors twelve_amount, eight_amount, five_amount = current_state if twelve_amount > 0: # Pouring 12 into 8 if eight_amount < 8: amount_poured = min((8 - eight_amount), twelve_amount) next_state = (twelve_amount - amount_poured, eight_amount + amount_poured, five_amount) g.add_node(next_state) g.add_edge(current_state, next_state) frontier.append(next_state) # Pouring 12 into 5 if five_amount < 5: amount_poured = min((5 - five_amount), twelve_amount) next_state = (twelve_amount - amount_poured, eight_amount, five_amount + amount_poured) g.add_node(next_state) g.add_edge(current_state, next_state) frontier.append(next_state) if eight_amount > 0: # Pouring 8 into 12 if twelve_amount < 12: amount_poured = min((12 - twelve_amount), eight_amount) next_state = (twelve_amount + amount_poured, eight_amount - amount_poured, five_amount) g.add_node(next_state) g.add_edge(current_state, next_state) frontier.append(next_state) # Pouring 8 into 5 if five_amount < 5: amount_poured = min((5 - five_amount), eight_amount) next_state = (twelve_amount, eight_amount - amount_poured, five_amount + amount_poured) g.add_node(next_state) g.add_edge(current_state, next_state) frontier.append(next_state) if five_amount > 0: # Pouring 5 into 12 if twelve_amount < 12: amount_poured = min((12 - twelve_amount), five_amount) next_state = (twelve_amount + amount_poured, eight_amount, five_amount - amount_poured) g.add_node(next_state) g.add_edge(current_state, next_state) frontier.append(next_state) # Pouring 5 into 8 if eight_amount < 8: amount_poured = min((8 - eight_amount), five_amount) next_state = (twelve_amount, eight_amount + amount_poured, five_amount - amount_poured) g.add_node(next_state) g.add_edge(current_state, next_state) frontier.append(next_state) explored.add(current_state) return g