简体   繁体   中英

Pythonic way to find a minimum dictionary key with value in a list?

I am coding an implementation of the Astar algorithm in Python and at some point I want to select the element in open_set that has the lowest f_score value :

# current is the node in open_set having the lowest f_score value
    current = min_f_score(open_set, f_score)

open_set is a list of tuples (x, y) that represent the coordinates of a point in 2D and f_score is a dictionary {(x, y): score} that assigns to each tuple (x, y) its f_score value.

My first implementation of min_f_score was the following:

def min_f_score(open_set, f_score):
    # First we initialize min_score_element and min_score
    min_score_element = open_set[0]
    min_score = f_score[open_set[0]]

    # Then we look for the element from f_score keys with the lowest value
    for element in open_set:
        if element in f_score.keys() and f_score[element] < min_score:
            min_score = f_score[element]
            min_score_element = element

    return min_score_element

It works fine but I was wondering if I could come up with some leaner, more pythonic code. After some research I came up with these two other implementations:

def min_f_score(open_set, f_score):   
    # We filter the elements from open_set and then find the min f_score value
    return min(filter(lambda k: k in open_set, f_score), key=(lambda k: f_score[k]))

and:

def min_f_score(open_set, f_score):   
    # We look for the min while assigning inf value to elements not in open_set
    return min(f_score, key=(lambda k: f_score[k] if k in open_set else float("inf")))

Both seem to work and give me the same results but are significantly slower than the first implementation.

I was wondering out of curiosity if there was any better way to implement min_f_score ?

Edit: As suggested by @azro (thank you), I am adding an example of code to execute:

open_set = [(1, 2), (1, 3), (2, 1), (2, 2), (3, 1), (1, 4), (4, 1), (1, 5), (5, 0), (5, 1), (0, 6), (1, 6)]
f_score = {(0, 0): 486.0, (0, 1): 308.0, (1, 0): 308.0, (1, 1): 265.0, (0, 2): 265.0, (1, 2): 338.0, (0, 3): 284.0, (1, 3): 450.0, (2, 0): 265.0, (2, 1): 338.0, (2, 2): 629.0, (3, 0): 284.0, (3, 1): 450.0, (0, 4): 310.0, (1, 4): 550.0, (4, 0): 310.0, (4, 1): 564.0, (0, 5): 316.0, (1, 5): 588.0, (5, 0): 316.0, (5, 1): 606.0, (0, 6): 298.0, (1, 6): 534.0}
min_f_score(open_set, f_score)

Output: (0, 6)

V1

A version with dict.get() is much faster than your 2 min() versions (that were more than 10 times slower), but keeps being about 2 times slower

def min_f_score_d(open_set, f_score):
    inf = float("inf")
    return min(open_set, key=lambda k: f_score.get(k, inf))

V2

Suggested by @stefan-pochmann , that one is sligthly slower than the classic iteration

def min_f_score(open_set, f_score):
    return min(f_score.keys() & open_set, key=f_score.get)

Note

if element in f_score        and f_score[element] < min_score:
# if faster than 
if element in f_score.keys() and f_score[element] < min_score:

Bench code

from timeit import timeit

A = """
def min_f_score(open_set, f_score):
    min_score_element, min_score = open_set[0], f_score[open_set[0]]
    for element in open_set:
        if element in f_score and f_score[element] < min_score:
            min_score = f_score[element]
            min_score_element = element
    return min_score_element
"""

B = """
def min_f_score(open_set, f_score):
    inf = float("inf")
    return min(open_set, key=lambda k: f_score.get(k, inf))
"""

C = """
def min_f_score(open_set, f_score):
    return min(f_score.keys() & open_set, key=f_score.get)
"""

SETUP = """
from random import randrange
open_set = [(randrange(1000), randrange(1000)) for _ in range(1000)]
f_score = {pair: randrange(1000) for pair in open_set[:len(open_set) // 2]}
"""

NB = 20_000
print(timeit(setup=SETUP + A, stmt="min_f_score(open_set, f_score)", number=NB))  # ~2.7
print(timeit(setup=SETUP + B, stmt="min_f_score(open_set, f_score)", number=NB))  # ~4.8
print(timeit(setup=SETUP + C, stmt="min_f_score(open_set, f_score)", number=NB))  # ~3.1

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM