Python 查找其值最接近某个值的k dict项

Python 查找其值最接近某个值的k dict项,python,sorting,dictionary,nearest-neighbor,Python,Sorting,Dictionary,Nearest Neighbor,假设我们想找到两个值最接近10的项目: A = {'abc': 12.3, 'def': 17.3, 'dsfsf': 18, 'ppp': 3.2, "jlkljkjlk": 9.23} 它与: def nearest(D, centre, k=10): return sorted([[d, D[d], abs(D[d] - centre)] for d in D], key=lambda e: e[2])[:k] print(nearest(A, centre=10, k=2))

假设我们想找到两个值最接近10的项目:

A = {'abc': 12.3, 'def': 17.3, 'dsfsf': 18, 'ppp': 3.2, "jlkljkjlk": 9.23}
它与:

def nearest(D, centre, k=10):
    return sorted([[d, D[d], abs(D[d] - centre)] for d in D], key=lambda e: e[2])[:k]

print(nearest(A, centre=10, k=2))
[jlkljkjlk',9.23,0.769999996],[abc',12.3,2.30000000007]]


但是,当dict的大小大得多(数十万项)时,是否有Python内置的方法来实现这一点和/或更优化的版本?如果您不介意使用Pandas:

import pandas as pd
closest = (pd.Series(A) - 10).abs().sort_values()[:2]
#jlkljkjlk    0.77
#abc          2.30
closest.to_dict()
#{'jlkljkjlk': 0.7699999999999996, 'abc': 2.3000000000000007}

如果您不介意使用熊猫:

import pandas as pd
closest = (pd.Series(A) - 10).abs().sort_values()[:2]
#jlkljkjlk    0.77
#abc          2.30
closest.to_dict()
#{'jlkljkjlk': 0.7699999999999996, 'abc': 2.3000000000000007}
您可以使用:

就时间复杂度而言,它在
O(n log(k))
时间内运行,而不是基于字典排序的解决方案的
O(n log(n))

您可以使用:


就时间复杂度而言,它在
O(n log(k))
时间内运行,而不是基于字典排序的解决方案的
O(n log(n))

鉴于您需要经常执行查找,我们可以通过首先将数据存储在已排序的列表中,使其成为O(log n)算法:

然后,对于每个项目,我们可以使用点来确定插入点。然后我们可以检查两个周围的值,以检查最小值,并返回相应的键。也有可能

from bisect import bisect_left
from operator import itemgetter

def closests(v):
    idx = bisect_left(vs, v)
    i, j = max(0, idx-1), min(idx+2, len(ks))
    part = ks[i:j]
    return sorted([[*pi, abs(pi[-1]-v)] for pi in part], key=itemgetter(-1))[:2]
上述情况看起来可能不是一种改进,但在这里,我们将始终在
排序(..)
中最多计算三个元素,并且
左对分
将计算对数数量的元素

例如:

>>> closests(1)
[['ppp', 3.2, 2.2], ['jlkljkjlk', 9.23, 8.23]]
>>> closests(3.2)
[['ppp', 3.2, 0.0], ['jlkljkjlk', 9.23, 6.03]]
>>> closests(5)
[['ppp', 3.2, 1.7999999999999998], ['jlkljkjlk', 9.23, 4.23]]
>>> closests(9.22)
[['jlkljkjlk', 9.23, 0.009999999999999787], ['abc', 12.3, 3.08]]
>>> closests(9.24)
[['jlkljkjlk', 9.23, 0.009999999999999787], ['abc', 12.3, 3.0600000000000005]]

因此,“加载”阶段取O(n logn)(n为元素数)。然后,如果我们推广上述方法来获取k个元素(通过增加切片),则需要O(logn+klogk)来执行查找。

鉴于您需要经常执行查找,我们可以通过首先将数据存储在一个排序列表中,使其成为O(logn)算法:

然后,对于每个项目,我们可以使用点来确定插入点。然后我们可以检查两个周围的值,以检查最小值,并返回相应的键。也有可能

from bisect import bisect_left
from operator import itemgetter

def closests(v):
    idx = bisect_left(vs, v)
    i, j = max(0, idx-1), min(idx+2, len(ks))
    part = ks[i:j]
    return sorted([[*pi, abs(pi[-1]-v)] for pi in part], key=itemgetter(-1))[:2]
上述情况看起来可能不是一种改进,但在这里,我们将始终在
排序(..)
中最多计算三个元素,并且
左对分
将计算对数数量的元素

例如:

>>> closests(1)
[['ppp', 3.2, 2.2], ['jlkljkjlk', 9.23, 8.23]]
>>> closests(3.2)
[['ppp', 3.2, 0.0], ['jlkljkjlk', 9.23, 6.03]]
>>> closests(5)
[['ppp', 3.2, 1.7999999999999998], ['jlkljkjlk', 9.23, 4.23]]
>>> closests(9.22)
[['jlkljkjlk', 9.23, 0.009999999999999787], ['abc', 12.3, 3.08]]
>>> closests(9.24)
[['jlkljkjlk', 9.23, 0.009999999999999787], ['abc', 12.3, 3.0600000000000005]]


因此,“加载”阶段取O(n logn)(n为元素数)。然后,如果我们将上述方法推广到获取k个元素(通过增加切片),则需要O(logn+klogk)来执行查找。

您可以使用分区numpy@MadPhysicist这需要手动将dict拆分为字符串列表和值numpy数组,然后手动执行所有操作,或者你的意思是有一个现成的函数来完成这个任务?这看起来更像是一个二叉树或kde树的任务(如果数据是多维的)。@Basj
np.argpartition(np.abs(np.subtractlist(d.values())),2)
将使您非常接近,因为
键和
值的顺序是相同的。如果操作到位,您可能会获得更好的里程数。我想不出一条线的解决方案,如果你问的是这个问题的话。@MadPhysicator,你能用这个发布一个anwser吗(这对未来的ref来说会很有趣)?在使用
abs
之前,您还需要将dict值减去
center
numpy@MadPhysicist这需要手动将dict拆分为字符串列表和值numpy数组,然后手动执行所有操作,或者你的意思是有一个现成的函数来完成这个任务?这看起来更像是一个二叉树或kde树的任务(如果数据是多维的)。@Basj
np.argpartition(np.abs(np.subtractlist(d.values())),2)
将使您非常接近,因为
键和
值的顺序是相同的。如果操作到位,您可能会获得更好的里程数。我想不出一条线的解决方案,如果你问的是这个问题的话。@MadPhysicator,你能用这个发布一个anwser吗(这对未来的ref来说会很有趣)?在使用
abs
之前,您还需要将dict值减去
center
。很好!numpy中是否有类似的东西(以避免另一种依赖性)?numpy不适合处理异构数组,在您的情况下,数组是异构的(字符串和浮点)。很好!numpy中是否有类似的东西(以避免另一种依赖性)?numpy不适合处理异构数组,在您的情况下,数组是异构的(字符串和浮点)。哦,我们已经有了一个
nsmallest
:D我将删除我的答案。这里值得注意的是,这将是O(n log(k))而不是O(n log(n))。哦,我们已经有了一个
nsmalest
:D我将删除我的答案。这里值得注意的是,这将是O(n log(k))而不是O(n log(n))。@slider:不,这将是O(k log k+log n):),事实上,这里的时间复杂性是查找的时间复杂性,而不是“加载”的时间复杂性。哦,没错<代码>左对分
仅为一次。但为什么是k log k?它应该是O(k+log(n))@slider:因为我们在这里也使用了
排序(…)
,这需要O(m log m)(m是要切片的列表),因为m=O(k),因此这会产生O(k log k+log n),尽管如果从
idx径向向外,从任意一侧选择最小差分元素,我认为您可以构建按O(k)时间差排序的k个元素的列表。@slider:您确实可以实现某种“之字形”函数来枚举这些元素。然而,这有点棘手:如果有一个元素正好具有该值,那么我们当然会产生该值,但是我们需要同时查找左侧和右侧的元素。此外,如果该值小于最小值或大于最大值,则没有下一个值,因此检查边界会使其更加复杂。但如果我找到时间,我会尝试正确地实现它:)@slider:no那将是O(k log k+log n):)和in