繁体   English   中英

在保留其结构的同时递归清空嵌套列表

[英]Recursively emptying a nested list while preserving its structure

我试图编写一个函数来读取包含列表的列表,并返回相同的列表结构但没有元素。

def remove_from_list(l):
    for e in l:
        if isinstance(e, list):
            remove_from_list(e)
        else:
            l.remove(e)
    return l

所以对于这样的输入:

[1, 2, [], [3,[4]], 5]

我应该得到这样的东西:

[[], [[]]]

我尝试了这些输入:

[1, 2, [], [3,[4]], 5]
[1, 2, [], [2,[3]], 2]

得到这些输出:

[2, [], [[4]]]
[[], [[3]], 2]

这是非常令人困惑的,因为两个列表的结构是相同的; 只有元素不同。 因此,我不仅会犯错误,而且会得到另一个错误。 帮助我的错误的功能和解释将非常感激。

这里的关键问题是for循环有一定数量的迭代,但是当你删除循环中的列表时,你就会缩小它们。 因此,当列表变小时,循环指针保持固定。 有一次,循环没有机会完成迭代。

选项1
作为一个简单的修复,您可以在函数内创建一个新列表。 这应该更简单,并且不会改变您的原始列表。

def remove_from_list(l):
    r = []
    for e in l:
        if isinstance(e, list):
            r.append(remove_from_list(e))
    return r

>>> remove_from_list([1, 2, [], [3,[4]], 5])
[[], [[]]]

此外,由于您只是附加空结构,因此这应该比创建数据副本和后续删除调用更快。


选项2
基于wim的想法 ,反向迭代并使用del如果你想改变列表的位置

def remove_from_list(l):
    r = []
    for i in range(len(l) - 1, -1, -1):
        if isinstance(l[i], list):
            remove_from_list(l[i])
        else:
            del l[i]

>>> l = [1, 2, [], [3,[4]], 5]
>>> remove_from_list(l)
>>> l
[[], [[]]]

从良好实践的角度来看,我建议要么返回副本,要么在没有返回的情况下进行修改,但不能同时修改。


您可以执行时间比较,以确定哪种方法可以更快地处理数据。

一,设置 -

def remove_from_list(l):
    r = []
    for e in l:
        if isinstance(e, list):
            r.append(remove_from_list(e))
    return r

def remove_from_list_reverse_del(l):
    r = []
    for i in range(len(l) - 1, -1, -1):
        if isinstance(l[i], list):
            remove_from_list(l[i])
        else:
            del l[i]


def remove_from_list_copy(l):
    for e in l[:]: # make a copy by slicing
        if isinstance(e, list):
            remove_from_list_copy(e)
        else:
            l.remove(e)
    return l

y = [1, 2, [], [3,[4]], 5]
z = copy.deepcopy(y  * 10000)

接下来,时间 -

%timeit remove_from_list(z)
19.3 ms ± 334 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit
z2 = copy.deepcopy(z)    # copying because this function mutates the original
remove_from_list_reverse_del(z2)

78.6 ms ± 157 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

虽然创造z2浪费了大量时间。

好老式的递归怎么了? (顺便说一下,这个答案还处理包含对自己的引用的列表。)

def f(l, i, ids):
  if i >= len(l):
    return l
  if isinstance(l[i], list):
    if not id(l[i]) in ids:
      ids.add(id(l[i]))
      f(l[i], 0, ids)
    return f(l, i + 1, ids)
  else:
    del l[i]
    return f(l, i, ids)

a = [1, 2, [], [3,[4]], 5]
a.append(a)
a[3].append(a[3])

print a # [1, 2, [], [3, [4], [...]], 5, [...]]
print f(a, 0, set([id(a)])) # [[], [[], [...]], [...]]

(至于你的误解 - 正如cᴏʟᴅsᴘᴇᴇᴅ所提到的,在for循环期间删除列表的部分可能会导致意外的结果,因为迭代的范围在它开始之前设置,但是列表会在中途被修改。)

这是使用递归的另一种方法

def remove_from_list (l):
  if not l:
    return []
  elif isinstance (l[0], list):
    return [remove_from_list (l[0])] + remove_from_list (l[1:])
  else:
    return remove_from_list (l[1:])

print (remove_from_list ([1, 2, [], [3, [4]], 5]))
# [[], [[]]]

如果您觉得以这种方式思考问题很好,您会发现一些通用函数可以使事情变得更好

def is_empty (l):
  return not l

def is_list (l):
  return isinstance (l, list)

def head (l):
  return l[0]

def tail (l):
  return l[1:]

def remove_from_list (l):
  if is_empty (l):
    return []
  elif is_list (head (l)):
    return [remove_from_list (head (l))] + remove_from_list (tail (l))
  else:
    return remove_from_list (tail (l))

print (remove_from_list ([1, 2, [], [3, [4]], 5]))
# [[], [[]]]

它不会改变输入

data = [1, 2, [], [3, [4]], 5]

print (remove_from_list (data))
# [[], [[]]]

print (data)
# [1, 2, [], [3, [4]], 5]

还有一个尾递归版本,可以实现堆栈安全粗体更改)

def identity (x): return x

def remove_from_list (l, k = identity):
  if is_empty (l):
    return k ([])
  elif is_list (head (l)):
    return remove_from_list (head (l), lambda x:
      remove_from_list (tail (l), lambda y:
        k ([x] + y)))
  else:
    return remove_from_list (tail (l), k)

print (remove_from_list (data))
# [[], [[]]]

简单的递归

def remove_from_list(l):
  if l == []:
    return []
  elif not isinstance(l[0], list):
    return remove_from_list(l[1:])
  else:
    return [remove_from_list(l[0])] + remove_from_list(l[1:])

有点复杂

def remove_from_list(l):
  def remove_inner(l_in,l_out):
    if l_in == []:
      return l_out
    elif not isinstance(l_in[0], list) or l[0] == []:
      return remove_inner(l_in[1:],l_out)
    else:
      return remove_inner(l_in[1:], l_out + [remove_from_list(l_in[0])])
  return remove_inner(l,[])

print(remove_from_list([1, 2, [], [3,[4]], 5]))

问题是您在迭代同一列表时从列表中删除元素。 一种解决方案是制作副本并对其进行迭代,如下所示:

def remove_from_list(l):
    for e in l[:]: # make a copy by slicing
        if isinstance(e, list):
            remove_from_list(e)
        else:
            l.remove(e)
    return l

结果:

>>> remove_from_list([1, 2, [], [3,[4]], 5])
[[], [[]]]

Python文档中对此行为的解释:

当循环修饰序列时有一个微妙的变化(这只能发生在可变序列,即列表中)。 内部计数器用于跟踪下一个使用的项目,并在每次迭代时递增。 当该计数器达到序列的长度时,循环终止。 这意味着如果套件从序列中删除当前(或前一个)项目,则将跳过下一个项目(因为它获取已经处理的当前项目的索引)。 同样,如果套件在当前项目之前的序列中插入项目,则下次循环时将再次处理当前项目。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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