3

I'm trying to make a function to find the highest score in a directed graph. I do have a start node and can't go throught the same node twice. I've tried to use recursive to get the sum of values until I hit one end node. Then I would call back my function to the start node and try other option ultil I hit another. And so on.

My problem is that when i return to a node with more than one path, the score value for this node is the sum of all paths it can take. And I only want the sum of one specific path.

Here is my code so far:


caminho = list()
def maxscore(start, parentals, score):
    global caminho
    parentals += start + '|'

    if len(graph[start]) > 0:
        for i in graph[start]:
            if i not in parentals.split('|'):
                value = graph[start][i]
                if value:
                    score += value

                func = maxscore(i, parentals, score)

            else:
                continue

        if func[0] > score:
            score = func[0]
            caminho = parentals.split('|')

        return score, caminho

    else:
        return score, start

graph = {
    'a': {'b': 2, 'c': 4},
    'b': {'d': 5},
    'c': {'a': 1, 'e': 3},
    'd': {'f': 4},
    'e': {'b': 2, 'f': 3, 'g': 2},
    'f': {},
    'g': {}
}

print(maxscore('a', '', 0))

How could I make it work to return in the end only the best score with the path(caminho) it took.

Sorry if I couldn't make myself clear enough. Feel free to ask any questions.

Artotim
  • 85
  • 5

2 Answers2

2

You probably want to send the score variable by value, but you are sending it by reference, therefore all the possible paths' scores are added to it.

This is my approach:

def maxscore(start, parentals, score):
    newParentals = parentals + start + '|'
    print newParentals, score
    scores = []
    if graph[start]:
        for nextNode in graph[start]:
            if nextNode not in newParentals.split('|'):
                scores.append(maxscore(nextNode, newParentals, score + graph[start][nextNode]))
        return sorted(scores)[-1]
    else:
        return score

graph = {
    'a': {'b': 2, 'c': 4},
    'b': {'d': 5},
    'c': {'a': 1, 'e': 3},
    'd': {'f': 4},
    'e': {'b': 2, 'f': 3, 'g': 2},
    'f': {},
    'g': {}
}

print(maxscore('a', '', 0))

And this is what gets printed:

a| 0
a|c| 4
a|c|e| 7
a|c|e|b| 9
a|c|e|b|d| 14
a|c|e|b|d|f| 18
a|c|e|g| 9
a|c|e|f| 10
a|b| 2
a|b|d| 7
a|b|d|f| 11
18

You can see how it checks all possible paths, and then picks the highest score :D

  • If you just replace `return score` with `return score, newParentals` the function will return both the score as well as the path followed. All the rest, such appending and sorting the scores, will keep working just fine. – JohanC Nov 25 '19 at 15:29
  • I really like your approach, as you stay closer to the code in the question. – JohanC Nov 25 '19 at 15:31
1

Here is an approach:

def maxscore(start, path, score):
    best_score = -1
    best_i = None
    best_path = None

    current_path = path + [start]
    for i in graph[start]:
        if not i in path:
            score_i, path_i = maxscore(i, current_path, score + graph[start][i])
            if score_i > best_score:
                best_score = score_i
                best_i = i
                best_path = path_i
    if best_i is None:
        return score, current_path
    else:
        return best_score, best_path

graph = {
    'a': {'b': 2, 'c': 4},
    'b': {'d': 5},
    'c': {'a': 1, 'e': 3},
    'd': {'f': 4},
    'e': {'b': 2, 'f': 3, 'g': 2},
    'f': {},
    'g': {}
}

print(maxscore('a', [], 0))

Output:

(18, ['a', 'c', 'e', 'b', 'd', 'f'])

Notes:

  • In the code of the question caminho and parentals served the same function. Just having a list is easier to work with than with a string.
  • In the for-loop, we test each of the edges. If the end-node of the edge is not yet in the path, we calculate the maximum score following that edge. We have to compare each of the possible edges and keep the one with the best score. We can only return from maxscore() after testing all possible edges.
  • If no free edge was found, we just return the current score and path
  • If free edges were found, we return the score and path of the best edge

PS: After revising the code, it can be shortened a bit. best_iis not really needed, and the test if best_i is None can be replaced by if best_path is None.

Also, if you need the path in the string form, you can print("|".join(best_path)).

JohanC
  • 71,591
  • 8
  • 33
  • 66