Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/11.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 在dict中查找整数最近邻_Python_Algorithm_Dictionary_Nearest Neighbor - Fatal编程技术网

Python 在dict中查找整数最近邻

Python 在dict中查找整数最近邻,python,algorithm,dictionary,nearest-neighbor,Python,Algorithm,Dictionary,Nearest Neighbor,我有一个dict,它接受整数键: a = {} a[1] = 100 a[55] = 101 a[127] = 102 在询问以下问题时,我希望能够选择最近的邻居: a[20] # should return a[1] = 100 a[58] # should return a[55] = 101 a[167] # should return a[127] = 102 有没有一种类似于python的方法可以做到这一点?(我想这可以通过在所有dict上循环来实现,但这可能不是最优雅的解决方案?

我有一个
dict
,它接受整数键:

a = {}
a[1] = 100
a[55] = 101
a[127] = 102
在询问以下问题时,我希望能够选择最近的邻居:

a[20] # should return a[1] = 100
a[58] # should return a[55] = 101
a[167] # should return a[127] = 102
有没有一种类似于python的方法可以做到这一点?(我想这可以通过在所有dict上循环来实现,但这可能不是最优雅的解决方案?)


带双索引的相同问题(也包括整数):


我希望能够得到
b[73,40]=b[70,45]=41
,即二维平面中的最近邻点。

类似于此:

class CustomDict(dict):
    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            closest_key = min(self.keys(), key=lambda x: abs(x - key))
            return dict.__getitem__(self, closest_key)
或者这个:

class CustomDict(dict):
    def __getitem__(self, key):
        if key in self:
            return dict.__getitem__(self, key)
        else:
            closest_key = min(self.keys(), key=lambda x: abs(x - key))
            return dict.__getitem__(self, closest_key)
两者都给出了这样的结果:

a = CustomDict()
a[1] = 100
a[55] = 101
a[127] = 102

print a[20] # prints 100
print a[58] # prints 101
print a[167] # prints 102
对于双索引版本:

class CustomDoubleDict(dict):
    def __getitem__(self, key):
        if key in self:
            return dict.__getitem__(self, key)
        else:
            closest_key = min(self.keys(), key=lambda c: (c[0] - key[0]) ** 2 + (c[1] - key[1]) ** 2)
            return dict.__getitem__(self, closest_key)


b = CustomDoubleDict()
b[90, 1] = 100
b[90, 55] = 101
b[90, 127] = 102
b[70, 1] = 40
b[70, 45] = 41
b[70, 107] = 42

print b[73, 40]  # prints 41
print b[70, 45]  # prints 41
一维情况下的未测试O(对数n)示例:

import collections
import bisect


class ProximityDict(collections.MutableMapping):
    def __init__(self, *args, **kwargs):
        self.store = dict()
        self.ordkeys = []
        self.update(dict(*args, **kwargs))

    def __getitem__(self, key):
        try: return self.store[key]
        except KeyError:
            cand = bisect.bisect_left(self.ordkeys, key)
            if cand == 0: return self.store[self.ordkeys[0]]

            return self.store[
                min(self.ordkeys[cand], self.ordkeys[cand-1],
                    key=lambda k: abs(k - key))
            ]

    def __setitem__(self, key, value):
        if not key in self.store: bisect.insort_left(self.ordkeys, key)
        self.store[key] = value

    def __delitem__(self, key):
        del self.store[key]
        del self.keys[bisect.bisect_left(self.ordkeys, key)]

    def __iter__(self):
        return iter(self.store)

    def __len__(self):
        return len(self.store)
二维的情况明显更烦人(您需要存储按四叉树排序的键),但也可以采用类似的方式

我没有将删除编码为也具有“接近”行为,但您也可以这样做。

选项1:

维护单独且有序的密钥列表(或使用
OrderedDict
)。使用二进制搜索查找最近的密钥。这应该是O(对数n)

选项2:(如果数据不是非常大和稀疏)

因为您提到dict是静态的,所以通过dict一次填充所有缺少的值。保留最大值和最小值键,并覆盖
\uuu getitem\uuu
,以便高于最大值或低于最小值的键返回正确的值。这应该是O(1)

备选案文3:


每次只需在按键上使用循环,它将O(n)。在您的应用程序中尝试一下,您可能会发现简单的解决方案非常快速且足够。

使用带有适当按键功能的
min
怎么样:

>>> b ={(90, 55): 101, (90, 127): 102, (90, 1): 100}
>>> def nearest(x,y):
...   m=min(((i,j) for i,j in b ),key= lambda v:abs(v[0]-x)+abs(v[1]-y))
...   return b[m]
... 
>>> nearest(40,100)
102
>>> nearest(90,100)
102
>>> b
{(90, 55): 101, (90, 127): 102, (90, 1): 100}
>>> nearest(90,10)
100
前面的答案是我建议的一个pythonic答案,但是如果您想寻找一种快速的方法,您可以使用
scipy.spatial.KDTree

用于快速最近邻查找的kd树

此类提供了一组k维点的索引,可用于快速查找任何点的最近邻

也看看


更新

更新:在对本答案中的两种方法进行基准测试后,第二种方法明显更好,几乎应该严格推荐


以下方法相同地处理n维:

class NearestDict(dict):
    def __init__(self, ndims):
        super(NearestDict, self).__init__()
        self.ndims = ndims

    # Enforce dimensionality
    def __setitem__(self, key, val):
        if not isinstance(key, tuple): key = (key,)
        if len(key) != self.ndims: raise KeyError("key must be %d dimensions" % self.ndims)
        super(NearestDict, self).__setitem__(key, val)

    @staticmethod
    def __dist(ka, kb):
        assert len(ka) == len(kb)
        return sum((ea-eb)**2 for (ea, eb) in zip(ka, kb))

    # Helper method and might be of use
    def nearest_key(self, key):
        if not isinstance(key, tuple): key = (key,)
        nk = min((k for k in self), key=lambda k: NearestDict.__dist(key, k))
        return nk

    def __missing__(self, key):
        if not isinstance(key, tuple): key = (key,)
        if len(key) != self.ndims: raise KeyError("key must be %d dimensions" % self.ndims)
        return self[self.nearest_key(key)]
演示:

请注意,如果键存在,则查找不会比标准字典查找慢。如果关键点不存在,则计算每个现有关键点之间的距离。没有缓存任何东西,不过我想你可以把它加在上面

编辑:

根据以下方法建议的方法,使用以下方法实现与上述相同的类:

请注意,还有一个可选参数,
regenOnAdd
,它允许您在完成(大部分)插入之前推迟(重新)构建KDTree:

from scipy.spatial import cKDTree

class KDDict(dict):
    def __init__(self, ndims, regenOnAdd=False):
        super(KDDict, self).__init__()
        self.ndims = ndims
        self.regenOnAdd = regenOnAdd
        self.__keys = []
        self.__tree = None
        self.__stale = False

    # Enforce dimensionality
    def __setitem__(self, key, val):
        if not isinstance(key, tuple): key = (key,)
        if len(key) != self.ndims: raise KeyError("key must be %d dimensions" % self.ndims)
        self.__keys.append(key)
        self.__stale = True
        if self.regenOnAdd: self.regenTree()
        super(KDDict, self).__setitem__(key, val)

    def regenTree(self):
        self.__tree = cKDTree(self.__keys)
        self.__stale = False

    # Helper method and might be of use
    def nearest_key(self, key):
        if not isinstance(key, tuple): key = (key,)
        if self.__stale: self.regenTree()
        _, idx = self.__tree.query(key, 1)
        return self.__keys[idx]

    def __missing__(self, key):
        if not isinstance(key, tuple): key = (key,)
        if len(key) != self.ndims: raise KeyError("key must be %d dimensions" % self.ndims)
        return self[self.nearest_key(key)]
输出与上述方法相同

基准结果

为了理解这三种方法(
NearestDict
KDDict(True)
(插入时重新生成)和
KDDict(False)
(延迟重新生成))的性能,我对它们进行了简单的基准测试

我做了3个不同的测试。试验期间保持不变的参数为:

  • 测试迭代次数:每次测试我都做了5次,而且时间最短。(注意
    timeit.repeat
    默认值为3)

  • 点边界:我生成了0范围内的整数关键点这里有一个主要依赖于映射/过滤操作的python解决方案

    class NeirestSearchDictionnary1D(dict):
        """ An extended dictionnary that returns the value that is the nearest to 
        the requested key. As it's key distance is defined for simple number 
        values, trying to add other keys will throw error. """
        def __init__(self):
            """ Constructor of the dictionnary.
            It only allow to initialze empty dict """
            dict.__init__(self)
    
        def keyDistance(self, key1, key2):
            """ returns a distance between 2 dic keys """
            return abs(key1-key2)
    
        def __setitem__(self, key, value):
            """ override  the addition of a couple in the dict."""
            #type checking
            if not (isinstance(key, int) or isinstance(key, float)):
                raise TypeError("The key of such a "+ type(self) + "must be a simple numerical value")
            else:
                dict.__setitem__(self, key, value)
    
        def __getitem__(self, key):
            """ Override the getting item operation """
            #compute the minial distance
            minimalDistance = min(map(lambda x : self.keyDistance(key, x), self.keys()))
            #get the list of key that minimize the distance
            resultSetKeys = filter(lambda x : self.keyDistance(key, x) <= minimalDistance, self.keys())
            #return the values binded to the keys minimizing the distances.
            return list(map(lambda x : dict.__getitem__(self, x), resultSetKeys))
    
    if __name__ == "__main__":
    
        dic = NeirestSearchDictionnary1D()
        dic[1] = 100
        dic[55] = 101
        dic[57] = 102
        dic[127] = 103
        print("the entire dict :", dic)
        print("dic of '20'", dic[20])
        print("dic of '56'", dic[56])
    
    class NeirestSearchDictionnary1D(dict):
    “”“一个扩展的词汇表,返回最接近的值。”
    请求的键。因为它的键距离是为简单数字定义的
    值,尝试添加其他键将抛出错误。”“”
    定义初始化(自):
    字典的构造函数。
    它只允许初始化空dict“”
    口述(自我)
    def钥匙距离(自身、钥匙1、钥匙2):
    “”“返回两个dic键之间的距离”“”
    返回abs(键1-键2)
    定义设置项(自身、键、值):
    “”“覆盖在dict中添加一对。”
    #类型检查
    如果不是(isinstance(key,int)或isinstance(key,float)):
    raise TypeError(“此类“+类型(自身)+”的键必须是简单的数值”)
    其他:
    dict.\uuuuu setitem\uuuuuuuu(自身、键、值)
    def _u获取项目(自身,密钥):
    “”“重写获取项目操作”“”
    #计算最小距离
    最小距离=最小(映射(lambda x:self.keydestance(key,x),self.keys())
    #获取使距离最小化的关键点列表
    
    resultSetKeys=过滤器(λx:self.keydestance(键,x)如果距离打成平局,你想要什么?你的意思是你想要两个?或者两个都想要?注意,对于二维的情况,可以有四个方向tie@wim不,如果允许使用实关键点,则可以有无限数量的联系。@wim不是无限的,但圆上的所有离散点都将是联系。如果您希望有效地使用,numpy/scipy可能是最佳选择最佳方式,例如,谢谢你的解决方案。选民们能解释为什么这不是一个好的解决方案吗?(我还不清楚为什么它会是好的或坏的…)是的,如果选民们能详细解释为什么他们认为它可能不正确,那就太好了。在这里试着学习。:)我投了否决票,因为每次没有精确匹配时,它都会在所有键上循环。请参阅我的答案以获得O(logn)解决方案。很难选择一个可接受的答案。这是一个简单、快速的问题解决方案(但非最优,因为循环覆盖所有关键点)。jedwards是一个用于N维泛化的。还有wim对我的问题的应用程序的答案。@Basj这是一个信号
    from scipy.spatial import cKDTree
    
    class KDDict(dict):
        def __init__(self, ndims, regenOnAdd=False):
            super(KDDict, self).__init__()
            self.ndims = ndims
            self.regenOnAdd = regenOnAdd
            self.__keys = []
            self.__tree = None
            self.__stale = False
    
        # Enforce dimensionality
        def __setitem__(self, key, val):
            if not isinstance(key, tuple): key = (key,)
            if len(key) != self.ndims: raise KeyError("key must be %d dimensions" % self.ndims)
            self.__keys.append(key)
            self.__stale = True
            if self.regenOnAdd: self.regenTree()
            super(KDDict, self).__setitem__(key, val)
    
        def regenTree(self):
            self.__tree = cKDTree(self.__keys)
            self.__stale = False
    
        # Helper method and might be of use
        def nearest_key(self, key):
            if not isinstance(key, tuple): key = (key,)
            if self.__stale: self.regenTree()
            _, idx = self.__tree.query(key, 1)
            return self.__keys[idx]
    
        def __missing__(self, key):
            if not isinstance(key, tuple): key = (key,)
            if len(key) != self.ndims: raise KeyError("key must be %d dimensions" % self.ndims)
            return self[self.nearest_key(key)]
    
    {'NDIMS': 4, 'NITER': 5, 'NELEMS': 1000, 'NFINDS': 10000, 'DIM_LB': 0, 'DIM_UB': 1000, 'SCORE_MUL': 100} insert::NearestDict 0.125 insert::KDDict(regen) 35.957 insert::KDDict(defer) 0.174 search::NearestDict 2636.965 search::KDDict(regen) 49.965 search::KDDict(defer) 51.880 {'NDIMS': 4, 'NITER': 5, 'NELEMS': 100, 'NFINDS': 10000, 'DIM_LB': 0, 'DIM_UB': 1000, 'SCORE_MUL': 100} insert::NearestDict 0.013 insert::KDDict(regen) 0.629 insert::KDDict(defer) 0.018 search::NearestDict 247.920 search::KDDict(regen) 44.523 search::KDDict(defer) 44.718 {'NDIMS': 12, 'NITER': 5, 'NELEMS': 100, 'NFINDS': 10000, 'DIM_LB': 0, 'DIM_UB': 1000, 'SCORE_MUL': 100} insert::NearestDict 0.013 insert::KDDict(regen) 0.722 insert::KDDict(defer) 0.017 search::NearestDict 405.092 search::KDDict(regen) 49.046 search::KDDict(defer) 50.601
    class NeirestSearchDictionnary1D(dict):
        """ An extended dictionnary that returns the value that is the nearest to 
        the requested key. As it's key distance is defined for simple number 
        values, trying to add other keys will throw error. """
        def __init__(self):
            """ Constructor of the dictionnary.
            It only allow to initialze empty dict """
            dict.__init__(self)
    
        def keyDistance(self, key1, key2):
            """ returns a distance between 2 dic keys """
            return abs(key1-key2)
    
        def __setitem__(self, key, value):
            """ override  the addition of a couple in the dict."""
            #type checking
            if not (isinstance(key, int) or isinstance(key, float)):
                raise TypeError("The key of such a "+ type(self) + "must be a simple numerical value")
            else:
                dict.__setitem__(self, key, value)
    
        def __getitem__(self, key):
            """ Override the getting item operation """
            #compute the minial distance
            minimalDistance = min(map(lambda x : self.keyDistance(key, x), self.keys()))
            #get the list of key that minimize the distance
            resultSetKeys = filter(lambda x : self.keyDistance(key, x) <= minimalDistance, self.keys())
            #return the values binded to the keys minimizing the distances.
            return list(map(lambda x : dict.__getitem__(self, x), resultSetKeys))
    
    if __name__ == "__main__":
    
        dic = NeirestSearchDictionnary1D()
        dic[1] = 100
        dic[55] = 101
        dic[57] = 102
        dic[127] = 103
        print("the entire dict :", dic)
        print("dic of '20'", dic[20])
        print("dic of '56'", dic[56])