我试图编写一个函数来读取包含列表的列表,并返回相同的列表结构但没有元素。
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]
这是非常令人困惑的,因为两个列表的结构是相同的;只有元素不同。因此,我不仅会犯错误,而且会得到另一个错误。帮助我的错误的功能和解释将非常感激。
答案 0 :(得分:5)
这里的关键问题是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's idea的基础上,如果您想改变列表,则反向迭代并使用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
时浪费了很多时间。
答案 1 :(得分:4)
良好的老式递归发生了什么变化? (顺便说一下,这个答案还处理包含对自己的引用的列表。)
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
循环期间删除部分列表会导致意外结果,因为迭代范围在启动之前设置,但列表会在中途被修改。)
答案 2 :(得分:3)
这是使用递归
的另一种方法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]
还有一个尾递归版本,可以stack-safe(粗体更改)
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))
# [[], [[]]]
答案 3 :(得分:3)
简单递归
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]))
答案 4 :(得分:2)
问题是您在迭代同一个列表时从列表中删除元素。一种解决方案是制作副本并对其进行迭代,如下所示:
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])
[[], [[]]]
对此行为的解释
当循环修饰序列时有一个微妙之处(这只能发生在可变序列,即列表中)。内部计数器用于跟踪下一个使用的项目,并在每次迭代时递增。当该计数器达到序列的长度时,循环终止。这意味着如果套件从序列中删除当前(或前一个)项目,则将跳过下一个项目(因为它获取已经处理的当前项目的索引)。同样,如果套件在当前项目之前的序列中插入一个项目,则下一次循环将再次处理当前项目。