我正在尝试从Google的Python类中解决以下练习。
电子。给定按递增顺序排序的两个列表,创建并返回按排序顺序排列的所有元素的合并列表。您可以修改传入的列表。理想情况下,解决方案应该在“线性”时间内工作,只需对两个列表进行一次传递。
我正在使用以下方案方法(我希望我有车,cdr和缺点!)。
def helper(list1, list2, result):
if list1 == None:
return result + list2
elif list2 == None:
return result + list1
elif list1[0] < list2[0]:
return helper(list1[1:], list2, result.insert(0, list1[0]))
else:
return helper(list1, list2[1:], result.insert(0, list2[0]))
def linear_merge(list1, list2):
helper(list1, list2, [])
我得到的错误是,当结果为[]时,我似乎无法在结果中插入元素:
AttributeError: 'NoneType' object has no attribute 'insert'
但这在控制台中运行良好:
>>> b = []
[]
>>> b.insert(0, 4)
>>> b
[4]
我是Python的新手,所以我有两个问题:
谢谢!
答案 0 :(得分:5)
list.insert
返回None,而不是修改后的列表。
这需要将helper
更改为读取
def helper(list1, list2, result):
if not list1:
return result + list2
elif not list2:
return result + list1
elif list1[0] < list2[0]:
return helper(list1[1:], list2, result + [list1[0]])
else:
return helper(list1, list2[1:], result + [list2[0]])
请注意两个基本案例的更改。 None
和空列表[]
不是一回事。测试列表是否为空的pythonic方法是将列表视为布尔值:空列表为False
,其他列表为True
。
正如其他人在我之前注意到的那样,您需要在helper
中明确返回linear_merge
的返回值。
答案 1 :(得分:3)
第一个问题是[]
和None
不相等。
这会导致两个问题。
首先,您对递归基本案例的测试无效。如果您尝试测试列表为空,可以通过多种方式进行测试:
if not list1:
if list1 == []:
if len(list1) == 0:
但将其与None
进行比较不是其中之一。
第一种通常被认为是最具Pythonic的(并且由PEP 8风格指南明确鼓励)。
其次,你显然是用None
显式调用这个函数作为参数,你在代码中没有向我们展示过。不要那样做。如果您的意思是[]
,请拨打[]
。
另一个问题是像list.insert
这样的可变方法不返回变异对象,它们返回None
。所以,而不是:
return helper(list1[1:], list2, result.insert(0, list1[0]))
......你需要这样做:
result.insert(0, list1[0])
return helper(list1[1:], list2, result)
...或者改为使用非变异表达式:
return helper(list1[1:], list2, [list1[0]] + result)
然后,您的linear_merge
没有return
任何内容,因此其值为None
。将其更改为:
return helper(list1, list2, [])
答案 2 :(得分:1)
result.insert
未返回新列表;它会修改现有的result
。因此,最后将None
作为嵌套helper()
调用的第三个参数传递,因为这是result.insert
返回的内容 - None
。
另请注意:
def linear_merge(list1, list2):
helper(list1, list2, [])
由于您return
没有linear_merge
任何内容,因此您总是会得到None
。
答案 3 :(得分:0)
既然你要求Pythonic解决方案,以及如何解决你的尝试,我会把你写成一个单独的答案。
我通过使用迭代器来做到这一点。在每一步,您产生较低的值并拉动下一个值。就像你在Haskell中一样。*这实际上是线性时间,而且它也是懒惰的。使用more-itertools
:
def itermerge(i1, i2):
i1, i2 = iter(i1), more_itertools.peekable(iter(i2))
for v1 in i1:
while i2 and i2.peek() < v1:
yield next(i2)
yield v1
yield from i2
如果您需要Python 2.x或3.2兼容性,只需使用yield from i2
更改for v2 in i2: yield v2
。
如果不清楚这是如何工作的,这里是最明确的版本,以准确显示每一步发生的事情:
def itermerge(i1, i2):
i1, i2 = iter(i1), iter(i2)
sentinel = object()
v1, v2 = sentinel, sentinel
while True:
if v1 is sentinel:
try:
v1 = next(i1)
except StopIteration:
yield from i2
return
if v2 is sentinel:
try:
v2 = next(i2)
except StopIteration:
yield from i1
return
if v1 < v2:
yield v1
v1 = sentinel
else:
yield v2
v2 = sentinel
如果您需要一个返回列表的函数,那很简单:
def linear_merge(l1, l2):
return list(itermerge(l1, l2))
*这有点儿开玩笑。虽然你可以在Haskell中编写这个算法的几乎任何不可变版本,但最喜欢这个解决方案的那个可能不是你写的那个。