1

I have a simple graph created as such in the below

class Job():
    def __init__(self, name, weight):
        self.name = name
        self.weight = weight
        self.depends = []

    def add_dependent(self, dependent):
        self.depends.append(dependent)


jobA = Job('A', 0)
jobB = Job('B', 4)
jobC = Job('C', 2)
jobD = Job('D', 10)
jobE = Job('E', 3)
jobF = Job('F', 11)

jobA.add_dependent(jobB)
jobA.add_dependent(jobC)
jobB.add_dependent(jobD)
jobC.add_dependent(jobE)
jobD.add_dependent(jobF)
jobE.add_dependent(jobF)

so we have two possible paths

A->B->D->F  0+4+10+11 = 25
A->C->E->F  0+2+3+11 = 16

so the longest paths would be the former

Is there an easy way to gather the longest path, A->B->D->F?

def longest_path(root):
    paths = []
    # some logic here
    return paths

print longest_path(jobA) # should print A->B->D->F
J. P. Petersen
  • 4,871
  • 4
  • 33
  • 33
ealeon
  • 12,074
  • 24
  • 92
  • 173
  • What do you mean by 'an easy way'? Using some third party python library? – Nurjan Oct 18 '16 at 08:32
  • 1
    `item.__sizeof__()` returned byte size, add size parameter to your class and check every new element inserted for saving which is long ! – dsgdfg Oct 18 '16 at 08:45
  • 1
    @dsgdfg: The supported accessor there is `sys.getsizeof` (which uses `__sizeof__` internally). But I have no idea how that relates to the OP's question. And you definitely shouldn't be doing terrible things like defining `__sizeof__` manually in Python level classes (the only place `__sizeof__` needs to be defined explicitly is for C level classes which need to include additional dynamic allocation overhead in their total size). – ShadowRanger Oct 18 '16 at 11:21
  • An afterthought - the way it's implemented, job tree is not protected from cyclic dependency. I suggest you try to solve this problem by adding node to parents connection when adding dependencies. – volcano Oct 19 '16 at 04:06

2 Answers2

2

Not the most efficient solution, but here is one that should work:

import operator

def longest_path(root):
    def _find_longest(job):
        costs = [_find_longest(depend) for depend in job.depends]
        if costs:
            # Find most expensive:
            path, cost = max(costs, key=operator.itemgetter(1))
            return ([job.name] + path, job.weight + cost)
        else:
            return ([job.name], job.weight)
    return "->".join(_find_longest(root)[0])
J. P. Petersen
  • 4,871
  • 4
  • 33
  • 33
1

If you use OO solution, it's easy to provide a way to store only the heaviest path. This is the solution I came up with - using a callable class

In [111]: class Heaviest(object):
     ...:     def __init__(self, job):
     ...:         self.path = ''
     ...:         self.weight = 0
     ...:         self.job = job
     ...:     def _find_heaviest(self, job, path='', weight=0):
     ...:         path += job.name
     ...:         weight += job.weight
     ...:         if not job.depends:
     ...:             if weight > self.weight:
     ...:                 self.weight = weight
     ...:                 self.path = path
     ...:         else:
     ...:             for job in job.depends:
     ...:                 self._find_heaviest(job, path, weight)
     ...:     def __call__(self):
     ...:         self._find_heaviest(self.job)
     ...:         return '->'.join(list(self.path)), self.weight
     ...:                 

In [112]: Heaviest(jobA)()
Out[112]: ('A->B->D->F', 25)

An afterthought:

It occurred to me last night that in case of cyclic dependency (see my comment), the solution above will not yield an answer, stopping with exception when maximum recursion depth is reached. Just adding the line below will blow any tree traversing algorithm - not just this one.

In [226]: jobF.add_dependent(jobA)

In [227]: Heaviest(jobA)()
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-227-94e994624b4e> in <module>()
----> 1 Heaviest(jobA)()

<ipython-input-111-1ff9f69480a9> in __call__(self)
     15                 self._find_heaviest(job, path, weight)
     16     def __call__(self):
---> 17         self._find_heaviest(self.job)
     18         return '->'.join(list(self.path)), self.weight
     19 

<ipython-input-111-1ff9f69480a9> in _find_heaviest(self, job, path, weight)
     13         else:
     14             for job in job.depends:
---> 15                 self._find_heaviest(job, path, weight)
     16     def __call__(self):
     17         self._find_heaviest(self.job)

... last 1 frames repeated, from the frame below ...

<ipython-input-111-1ff9f69480a9> in _find_heaviest(self, job, path, weight)
     13         else:
     14             for job in job.depends:
---> 15                 self._find_heaviest(job, path, weight)
     16     def __call__(self):
     17         self._find_heaviest(self.job)

RuntimeError: maximum recursion depth exceeded

While I leave attempt to mend the implementation to you - if you wish - simple safeguard can fix that

def _find_heaviest(self, job, path='', weight=0):
    if not job.name in path:
        path += job.name
        weight += job.weight
        stop_search = not job.depends
    else:
        stop_search = True
    if stop_search:
        if weight > self.weight:

.....

Problem solved

In [230]: Heaviest(jobA)()
Out[230]: ('A->B->D->F', 25)
volcano
  • 3,578
  • 21
  • 28