在列表中查找具有值的最小字典键的 Pythonic 方法?

Posted

技术标签:

【中文标题】在列表中查找具有值的最小字典键的 Pythonic 方法?【英文标题】:Pythonic way to find a minimum dictionary key with value in a list? 【发布时间】:2021-10-17 21:17:49 【问题描述】:

我正在用 Python 编写 Astar 算法的实现,有时我想选择 open_set 中具有最低 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 是元组 (x, y) 的列表,表示 2D 中点的坐标,f_score 是字典 (x, y): score,它为每个元组 (x, y) 分配其 f_score 值。

我对@9​​87654330@ 的第一个实现如下:

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

它工作得很好,但我想知道我是否可以想出一些更精简、更 Python 的代码。经过一番研究,我想出了另外两个实现:

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]))

和:

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")))

两者似乎都可以工作并给我相同的结果,但比第一个实现慢得多。

出于好奇,我想知道是否有更好的方法来实现min_f_score

编辑:正如@a​​zro 所建议的(谢谢),我正在添加一个要执行的代码示例:

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)

输出:(0, 6)

【问题讨论】:

如果能提供一个可重现的示例,我们可以粘贴并运行代码,那就太好了,谢谢 您的第一个实现的优点是可读性强且易于理解 嗯,为什么open_set不是集合? @StefanPochmann 这是个好问题。至少这是一个很好的例子,为什么在名称中包含变量的类型是不好的。 在您的示例数据中,open_setf_score 的子集,大约是其一半。它是否始终是一个子集,并且是您一般情况的一半大小? 【参考方案1】:

V1

带有dict.get() 的版本比您的 2 个min() 版本快得多(慢了 10 倍以上),但仍然慢了大约 2 倍

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

@stefan-pochmann 建议,比经典迭代慢一点

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

注意

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:

基准代码

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

【讨论】:

谢谢。令人失望的是它仍然比原来的慢。不过,不确定测试数据是否合适。在 OP 的示例数据中,open_setf_score 的一个子集,大约是它的一半,而在您的测试数据中则相反。 @StefanPochmann 似乎它并不是一个真正的子集,因为 OP 添加了检查它是否在其中 @StefanPochmann 感谢您的建议,这些真的很有趣! @azro 它应该是一个子集,我添加了检查以避免意外情况/行为 @azro 也可以在问题下查看我们的新 cmets。

以上是关于在列表中查找具有值的最小字典键的 Pythonic 方法?的主要内容,如果未能解决你的问题,请参考以下文章

获取字典最小值的键,而该键在数组中

如何在字典列表中找到公共键的最小/最大值?

在包含“无”/“假”值的字典上获取具有最小值的键

Pandas:在多列中查找具有匹配值的行的 Pythonic 方法(分层条件)

搜索特定值的嵌套字典列表[重复]

如何使用映射或过滤器而不是列表推导过滤特定值的嵌套字典(pythonic 方式)?