-
Notifications
You must be signed in to change notification settings - Fork 95
/
Copy pathmiguelHx.py
192 lines (164 loc) · 6.06 KB
/
miguelHx.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
"""Python Version 3.9.2
4.7 - Build Order:
You are given a list of projects and a list of dependencies
(which is a list of pairs of projects, where the second project is dependent on the first project).
All of a project's dependencies must be built before the project is.
Find a build order that will allow the projects to be built. If there is no
valid build order, return an error.
EXAMPLE
Input:
projects: a, b, c, d, e, f
dependencies: (a, d), (f, b), (b, d), (f, a), (d, c)
Output:
f, e, a, b, d, c
"""
import unittest
from dataclasses import dataclass
from typing import List, Set, Tuple, Dict
@dataclass
class Node:
id: int
children: 'List[Node]'
def add_child(self, *nodes: 'Node'):
for node in nodes:
self.children.append(node)
def children_as_str(self) -> str:
return ', '.join(str(child.id) for child in self.children)
def print_children(self):
print('Adjacency list for node {}: {}'.format(self.id, self.children_as_str()))
def __str__(self):
return f'Node ({self.id}), children: {self.children_as_str()}'
@dataclass
class DependencyNode:
id: str
children: 'List[DependencyNode]'
def add_child(self, *nodes: 'DependencyNode'):
for node in nodes:
self.children.append(node)
def children_as_str(self) -> str:
return ', '.join(str(child.id) for child in self.children)
def print_children(self):
print('Adjacency list for node {}: {}'.format(self.id, self.children_as_str()))
def __str__(self):
return f'Node ({self.id}), children: {self.children_as_str()}'
@dataclass
class DependencyGraph:
nodes: 'List[DependencyNode]'
def print_graph(self):
for node in self.nodes:
node.print_children()
def build_order(projects: List[str], dependencies: List[Tuple[str, str]]) -> List[str]:
"""Given a list of projects and dependencies,
this function will find a build order that will
allow the projects to be build given the dependencies.
If there is no valid build order, an error will be raised.
All of a project's dependencies must be built before the project
is.
EXAMPLE
Input:
projects: a, b, c, d, e, f
dependencies: (a, d), (f, b), (b, d), (f, a), (d, c)
Output:
f, e, a, b, d, c
Args:
projects (List[str]): a list of projects
dependencies (List[Tuple[str, str]]):
a list of pairs of dependencies (2nd project is dependent on 1st)
Returns:
List[str]: a valid build order
"""
# 0. define output
output: List[str] = []
# 1. build a dependency graph
project_to_node_map: Dict[str, DependencyNode] = {}
for p in projects:
project_to_node_map[p] = DependencyNode(p, [])
for d in dependencies:
p1 = d[0]
p2 = d[1]
project_to_node_map[p2].add_child(project_to_node_map[p1])
nodes = []
for node in project_to_node_map.values():
nodes.append(node)
# g = DependencyGraph(nodes)
# print("BUILT GRAPH, HERE IS RESULT:")
# g.print_graph()
# 2. define set to keep track of what we already built
projects_built: Set[str] = set()
# 3. for each project node, perform a depth-first search
for project_node in project_to_node_map.values():
# 4. perform a depth first search
visited = set()
stack = []
stack.append(project_node)
not_built = []
while stack:
node = stack.pop()
if node.id not in visited:
visited.add(node.id)
# get adjacent vertices of popped node.
# if it has not been visited, push it to stack
for n in node.children:
if n.id not in visited and n.id not in projects_built:
stack.append(n)
# check if all children are built.
all_adjacent_built = True
for n in node.children:
if n.id not in projects_built:
all_adjacent_built = False
# if all adjacent nodes are built,
# then this node can be safely built.
# otherwise, add to not_built list to check
# if children are built after traversal
if all_adjacent_built and node.id not in projects_built:
projects_built.add(node.id)
output.append(node.id)
else:
not_built.append(node)
# after traversing, we may have built all children.
# check nodes that haven't been built from this traversal.
for node in not_built:
all_adjacent_built = True
for n in node.children:
if n.id not in projects_built:
all_adjacent_built = False
if all_adjacent_built and node.id not in projects_built:
projects_built.add(node.id)
output.append(node.id)
return output
class TestBuildOrder(unittest.TestCase):
def test_build_order_ctci_example(self) -> None:
projects = ['a', 'b', 'c', 'd', 'e', 'f']
dependencies = [
('a', 'd'),
('f', 'b'),
('b', 'd'),
('f', 'a'),
('d', 'c')
]
result = build_order(projects, dependencies)
# this is the textbook answer, but it is possible to get a
# different valid build order (depends on algorithm)
# self.assertEqual(result, ['f', 'e', 'a', 'b', 'd', 'c'])
self.assertEqual(result, ['f', 'a', 'b', 'd', 'c', 'e'])
class TestMyDependencyGraphSearch(unittest.TestCase):
def test_basic_graph_creation(self):
n0 = Node(0, [])
n1 = Node(1, [])
n2 = Node(2, [])
n3 = Node(3, [])
n4 = Node(4, [])
n5 = Node(5, [])
n6 = Node(6, [])
n0.add_child(n1)
n1.add_child(n2)
n2.add_child(n0, n3)
n3.add_child(n2)
n4.add_child(n6)
n5.add_child(n4)
n6.add_child(n5)
nodes = [n0, n1, n2, n3, n4, n5, n6]
g = DependencyGraph(nodes)
# g.print_graph()
if __name__ == '__main__':
unittest.main()