mirror of
https://github.com/jlengrand/project_euler.git
synced 2026-03-10 08:41:20 +00:00
166 lines
5.1 KiB
Python
166 lines
5.1 KiB
Python
import sys
|
|
import heapq
|
|
|
|
class TriangleGraph(object):
|
|
"""
|
|
Problem: Determine the maximum sum of numbers in a path from the
|
|
top of the triangle to the bottom. You have to start at the top
|
|
and move to adjacent numbers on the line below.
|
|
|
|
Example triangle (samples/triangle2.txt):
|
|
|
|
5*
|
|
/ \
|
|
/ \
|
|
9* 6
|
|
/ \ / \
|
|
/ \ / \
|
|
4 6* 8
|
|
/ \ / \ / \
|
|
/ \ / \ / \
|
|
0 7* 1 5
|
|
|
|
Answer to the triangle above is 5+9+6+7 = 27
|
|
"""
|
|
|
|
def __init__(self, triangle_lines=[]):
|
|
"""
|
|
triangle_lines should be a list of space-delimited strings of numbers:
|
|
Example: ["5", "9 6", "4 6 8", "0 7 1 5"] represents the following:
|
|
5
|
|
9 6
|
|
4 6 8
|
|
0 7 1 5
|
|
"""
|
|
self.lines = triangle_lines
|
|
|
|
self.graph = []
|
|
self.numNodes = 0
|
|
|
|
self.values = []
|
|
self.distances = []
|
|
self.predecessors = []
|
|
|
|
self.build_adjacency_lists()
|
|
|
|
def build_adjacency_lists(self):
|
|
"""
|
|
Generates the adjacency lists from the data.
|
|
"""
|
|
# account for root node first because we need to track the other
|
|
# node's indexes as they're added to the graph.
|
|
self.numNodes = 1
|
|
|
|
# add root node to values first because the loop below only
|
|
# adds next lines.
|
|
self.values.append(int(self.lines[0]))
|
|
|
|
for i in xrange(len(self.lines)-1):
|
|
nextline = self.lines[i+1].split()
|
|
|
|
# add next line of values to self.values (this is why self.values
|
|
# is initialized with root value above)
|
|
self.values.extend([int(n) for n in nextline])
|
|
|
|
for j in xrange(len(nextline[:-1])):
|
|
# add adjacent numbers on the next line to the graph
|
|
# for the current vertex.
|
|
self.graph.append({self.numNodes: int(nextline[j]),
|
|
self.numNodes+1: int(nextline[j+1])})
|
|
|
|
# last adjacent vertex added to current vertex's adjacency
|
|
# list will be the _first_ vertex added to the next vertex's
|
|
# adjacency list, so increment numNodes by only 1.
|
|
self.numNodes += 1
|
|
|
|
# A new line is about to be added to the graph, so make sure
|
|
# the first adjacent vertex added to the current vertex is indexed
|
|
# as one more than the last adjacent vertex added to the previous
|
|
# vertex. Otherwise, the last vertex on this line will be adjacent
|
|
# to the first vertex on this line.
|
|
self.numNodes += 1
|
|
|
|
# add last line's adjacency list (which are just empty dicts)
|
|
last = self.lines[-1].split()
|
|
self.graph.extend([{} for i in xrange(len(last))])
|
|
|
|
# uncomment below to see the adjacency lists
|
|
#for i, g in enumerate(self.graph):
|
|
# print i, "->", g
|
|
|
|
def path(self, s, v):
|
|
"""
|
|
Returns the total cost of traveling from s to v.
|
|
"""
|
|
if s == v:
|
|
return self.values[s]
|
|
elif self.predecessors[v] == -1:
|
|
return 0
|
|
else:
|
|
return self.path(s, self.predecessors[v]) + self.values[v]
|
|
|
|
def relax(self, u, v, w):
|
|
"""
|
|
Note: shortest-path relax function checks if d[v] > d[u] + w
|
|
Since I'm trying to find the longest path, I do the opposite.
|
|
"""
|
|
if self.distances[v] < self.distances[u] + w:
|
|
self.distances[v] = self.distances[u] + w
|
|
self.predecessors[v] = u
|
|
|
|
def weight(self, u, v):
|
|
return self.values[u] + self.values[v]
|
|
|
|
def dijkstra(self, s):
|
|
"""
|
|
Dijkstra's algorithm finds the single-source shortest-path in a
|
|
weighted directed graph. I want to find the single-source
|
|
longest-path, so rather than initialize distances to infinity,
|
|
they're initialized to -1.
|
|
"""
|
|
for i in xrange(self.numNodes):
|
|
self.distances.append(-1)
|
|
self.predecessors.append(-1)
|
|
self.distances[s] = 0
|
|
|
|
S = set()
|
|
Q = [i for i in xrange(self.numNodes)]
|
|
heapq.heapify(Q)
|
|
|
|
while Q:
|
|
u = heapq.heappop(Q)
|
|
S.add(u)
|
|
for v in self.graph[u].keys():
|
|
w = self.weight(u, v)
|
|
self.relax(u, v, w)
|
|
|
|
def longest_path(self):
|
|
total = 0
|
|
for i in xrange(self.numNodes-len(self.lines), self.numNodes):
|
|
# iterate over the last line's numbers from start to end to
|
|
# get valid end points.
|
|
t = self.path(0, i)
|
|
if t > total:
|
|
total = t
|
|
return total
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print "Usage: %s <triangle_file>" % sys.argv[0]
|
|
return
|
|
|
|
try:
|
|
lines = open(sys.argv[1]).readlines()
|
|
except IOError:
|
|
print "Can't open file '%s'" % sys.argv[1]
|
|
return
|
|
|
|
lines = [line.strip() for line in lines]
|
|
|
|
G = TriangleGraph(lines)
|
|
G.dijkstra(0)
|
|
print G.longest_path()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|