【问题标题】:Returning a list of values from a tree recursion从树递归中返回值列表
【发布时间】:2012-08-10 18:23:30
【问题描述】:

我正在尝试自学数据结构,并且我正在用 Python 实现一个 k-d 树。我有一种方法可以在我的 k-d 树类中某个点的某个半径内搜索树中的点:

def within_radius(self, point, radius, result=[]):
    """
    Find all items in the tree within radius of point
    """
    d = self.discriminator

    if in_circle(point, radius, self.data):
        result.append(self.data)

    # Check whether any of the points in the subtrees could be
    # within the circle
    if point[d] - radius < self.data[d] and self.l_child:
        result.append(self.l_child.within_radius(point, radius, result))

    if point[d] + radius > self.data[d] and self.r_child:
        result.append(self.r_child.within_radius(point, radius, result))

    return result

它可以工作,但它返回的列表非常时髦,带有来自result 的递归调用的重复值。将树递归返回的值“累积”到列表中的好方法是什么?我已经考虑了一段时间,但我真的不知道怎么做。

【问题讨论】:

  • 您可以使用本地结果而不是不断传递的结果,但仍将本地值传回...

标签: python recursion kdtree


【解决方案1】:

我不确定这是否是最干净的方法,但是每当我进行这样的递归时,我经常添加一个关键字参数,即要返回的列表。这样,当我修改列表时,我总是修改为相同的列表

def recurse(max, _output=None):
    if _output is None:
        _output = []

    #do some work here, add to your list
    _output.append(max)

    if max <= 0: #Some condition where the recursion stops
        return _output
    else:        #recurse with new arguments so that we'll stop someday...
        return recurse(max-1, _output=_output)

之所以有效,是因为当停止条件为True 时,_output 列表将被返回并一直传递回堆栈。

我使用带下划线的变量名来表示它只能在函数本身中使用。这是对使用下划线前缀变量的正常方式的轻微扩展(在类中表示变量是“私有的”),但我认为它明白了这一点......

请注意,这与您所拥有的并没有太大区别。但是,对于您的版本,result 将在调用之间持续存在,因为 result = [] 在创建函数时被评估 ,而不是当它被调用时。此外,您的版本正在附加返回值(即列表本身)。当您考虑包含对其自身的多个引用的列表时,这会变得非常复杂......

【讨论】:

  • 非常有帮助的答案,谢谢。只是好奇,这有什么不那么“干净”的?
  • @pyroxene -- 不太确定。有一个以下划线为前缀的关键字参数对我来说似乎有点“脏”。不过,如果不使用课程,我想不出更好的方法(显然其他人也想不出)——而且我认为这里的课程会矫枉过正......
【解决方案2】:

我同意 mgilson 的分析。 list 是一个可变类型,list.append 是就地的。这是什么意思:

有两种类型:可变的和不可变的。

可变类型存在于内存中的同一位置,即使您更改它也是如此。例如,lists 和 dicts 是可变类型。这意味着如果您创建一个list 并以某些方式对其进行更改,它仍将位于内存中的同一位置。因此,假设您创建了一个名为“myList”的list。假设这个列表在内存位置 0x9000 中。然后,执行myList.append(0) 不会改变myList 在内存中的位置。即使您使用myList[0] = 'a',该位置也不会改变 - 它仍将位于 0x9000。

当您尝试以任何方式更改不可变类型时,它会“移动”到不同的内存位置。 strs 和 tuples 是不可变的。这就是您收到以下错误的原因:

>>> s = 'as'
>>> s[0] = 'b'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

这意味着即使您定义s = 'as'(假设s 现在位于内存地址0x5000)并将其重新定义为s = 'af's 在内存中的位置也会发生变化。

现在,当您重新分配可变类型时,它在内存中的位置会发生变化。例如,

L = [1,2,3] # 说内存位置 0x4000 L = [5,6,7] # 内存位置不再是 0x4000

这就是list.append 的“就地”属性发挥作用的地方。 “list.append is in-place”表示将新元素添加到列表中而不创建新列表。这就是list.append没有返回值的原因,如下所示:

>>> L = [1,2,3]
>>> ret = L.append(4)
>>> print L
[1, 2, 3, 4]
>>> print ret
None

但是,如果您想创建一个新列表,您可以按如下方式进行:

>>> L = [1,2,3]
>>> ret = L + [4]
>>> print L
[1, 2, 3]
>>> print ret
[1, 2, 3, 4]

所以在您的情况下发生的是,在两个递归调用(左和右)中,point 被附加到每个递归调用的列表中。这就是你得到重复值的原因。

您可以通过执行 mgilson 的建议来规避此问题,或者如果您是 lisp 粉丝(这是一个非常好的 lisp 问题),那么您可以使用 [1,2,3] + [4] 原则并执行此操作(未经测试,但应该可以):

def within_radius(self, point, radius, result=[]):
    """
    Find all items in the tree within radius of point
    """
    d = self.discriminator

    temp = []

    if in_circle(point, radius, self.data):
        temp = [self.data]

    # Check whether any of the points in the subtrees could be
    # within the circle
    if point[d] - radius < self.data[d] and self.l_child:
        temp += self.l_child.within_radius(point, radius, result)

    if point[d] + radius > self.data[d] and self.r_child:
        temp += self.r_child.within_radius(point, radius, result)

    return result+temp

希望对你有帮助

【讨论】:

    【解决方案3】:

    以下是一些想法:

    • 如果您只想返回唯一的结果,您可能应该使用集合并在返回时将其转换为列表。一个问题是self.data 必须是不可变的,例如元组而不是列表。
    • 因为您通过递归对result 进行线程处理并且将递归调用的结果添加到它,所以您明确地将每个命中添加到结果中至少两次。通过递归对结果进行线程化可以防止您创建和丢弃数据结构,因此您或许可以这样做。
    • 正如 mgilson 指出的那样,由于 Python 处理默认参数的方式,在函数声明中将 result 设置为一个空列表不会像您想的那样。每次调用 within_radius 时没有明确传入 result,每次调用都会累积点击次数,而不仅仅是单个调用。 (这有意义吗?见this)。 mgilson 的回答也指出了这一点。

    考虑到所有这些,我可能会做这样的事情:

    def within_radius(self, point, radius, result=None):
        d = self.discriminator
    
        result = set() if result is None else result
    
        if in_circle(point, radius, self.data):
            result.add(self.data)
        if point[d] - radius < self.data[d] and self.l_child:
            self.l_child.within_radius(point, radius, result)
        if point[d] + radius > self.data[d] and self.r_child:
            self.r_child.within_radius(point, radius, result)
    
        return list(result)
    

    【讨论】:

      猜你喜欢
      • 2014-01-18
      • 1970-01-01
      • 2012-08-23
      • 2020-04-04
      • 2020-02-19
      • 1970-01-01
      • 1970-01-01
      • 2021-06-11
      • 2021-05-07
      相关资源
      最近更新 更多