我正在尝试将两个已排序的数组合并为一个已排序的数组。我知道如何一般地这样做(所以问题本身不是关于合并列表),但出于学习目的,我想用列表理解来编写它。理想情况下,我写这个:
i=0; j=0
[a[i++] if j >= len(b) or a[i] < b[j] else b[j++]
for tmp in range (len(a)+len(b))]
(我知道有一个更大的最后一个元素,所以上面的内容不会超出范围)。 但是,python没有++运算符。我试着自己写一下:
def inc (x): x += 1; return x-1
但是这不起作用,因为python没有通过引用传递。我会使用迭代器,如果我能看到顶部元素而不推进它,但我不能。
那么,有没有办法在一个语句中优雅地编写它,而不是像这样:
i=0; j=0
while i<len(a) or j<len(b):
if j >= len(b) or a[i]<b[j]: res.append(a[i]); i += 1
else res.append(b[j]); j += 1
答案 0 :(得分:1)
我认为使列表理解能够合并两个列表的唯一模糊Pythonic方法是编写一个自定义迭代器类型,让您可以查看将要生成的下一个元素。这是我拼凑的快速版本:
class PeekIterator(object):
def __init__(self, iterable, sentinel=None):
self.iterator = iter(iterable)
self.sentinel = sentinel
self.next_val = self.sentinel
def peek(self):
if self.next_val is self.sentinel:
try:
self.next_val = next(self.iterator)
except StopIteration:
pass # the sentinel will be returned if there's nothing left in the iterator
return self.next_val
def __iter__(self):
return self
def __next__(self):
if self.next_val is self.sentinel:
return next(self.iterator) # StopIteration is deliberately allowed to bubble out!
val = self.next_val
self.next_val = self.sentinel
return val
next = __next__ # Python 2 compatibility
现在列表理解变为:
a_peek = PeekIterator(a)
b_peek = PeekIterator(b)
merged = [next(a_peek) if a_peek.peek() is not None and
(b_peek.peek() is None or a_peek.peek() <= b_peek.peek())
else next(b_peek)
for _ in range(len(a) + len(b))]
如果您知道列表中值的上限(例如,对于数字可能是float("inf")
),则可以通过创建迭代器来避免额外的peek
调用和短路逻辑。绑定为他们的哨兵值:
upper_bound = float("inf") # any value that compares larger than all the values in both of your lists
peek_a = PeekIterator(a, upper_bound)
peek_b = PeekIterator(b, upper_bound)
merged = [next(peek_a) if peek_a.peek() <= peek_b.peek() else next(peek_b)
for _ in range(len(a)+len(b))]
我认为非理解版本会更容易理解。
答案 1 :(得分:0)
检查这个答案 Python passing an integer by reference
[a[i++] if j >= len(b) or a[i] < b[j] else b[j++]
假设i和j是一个包含一个元素的维度列表,如在 链接上面。
def increment(var):
var[0] += 1
return var[0]
使用上述函数可以将代码转换为
[a[increment(i)] if j >= len(b) or a[i] < b[j] else b[increment(j)]
<强>更新强>
>>> def increment(number):
... number[0] += 1
... return number[0]
...
>>> num = [0]
>>> print [increment(num) for dummy in range(10)]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
答案 2 :(得分:0)
这就是我所说的优雅:
a = [0,1,2]
b = [1,2,3]
i, j = 0, 0
result = []
for temp in range(len(a)+len(b)):
if j>=len(b) or a[i]<b[j]:
result.append(a[i])
i += 1
else:
result.append(b[j])
j += 1
print(i,j,result)
print(result)
特别是因为它提供了诊断打印
0011:~/mypy$ python stack36052998.py
(1, 0, [0])
(1, 1, [0, 1])
(2, 1, [0, 1, 1])
(2, 2, [0, 1, 1, 2])
(3, 2, [0, 1, 1, 2, 2])
Traceback (most recent call last):
File "stack36052998.py", line 31, in <module>
if j>=len(b) or a[i]<b[j]:
IndexError: list index out of range
试图将列表理解转化为不适合的东西并不优雅。
列表pop
是++
索引递增的替代方法:
def foo(a,b):
result = []
while a and b:
x = a.pop(0) if a[0]<b[0] else b.pop(0)
result.append(x)
return result
a = [0,1,3,5,6]
b = [1.1,2.1,4.1,9.1]
print(foo(a[:],b[:]))
产生
[0, 1, 1.1, 2.1, 3, 4.1, 5, 6]
它与我之前遇到的问题相同,它不会从b
收集尾随值。
如果我确切知道需要多少次迭代,我也可以在列表解析中运行它。
result = [a.pop(0) if a[0]<b[0] else b.pop(0) for _ in range(8)]
通过引用传递值需要可变值。整数不可变,列表是。而不是使用伪装成列表的索引,只需使用列表本身。
正确循环:
def foo(a,b):
result = []
while a or b:
if not a:
result.extend(b)
break
if not b:
result.extend(a)
break
x = a.pop(0) if a[0]<b[0] else b.pop(0)
result.append(x)
return result
答案 3 :(得分:0)
我知道这是一个非常老的问题,您可能已经过了自己的生活;)但是我遇到了同样的问题,并通过列表理解+闭包来解决了这个问题。
def merge(left, right):
merge.lhs=0
merge.rhs = 0
def combine(left_side):
if left_side:
el = left[merge.lhs]
merge.lhs += 1
else:
el = right[merge.rhs]
merge.rhs += 1
return el
result = [combine(True) if merge.rhs >= len(right) or (merge.lhs < len(left) and left[merge.lhs] < right[merge.rhs]) else combine(False) for i in range(len(left) + len(right))]
return result
l = [1,3,5]
r = [2,4]
merge(l, r)
您可以说这很丑陋,我同意,但是,我不确定LC是否真的可以合并两个排序的列表!
答案 4 :(得分:-2)
i + 1的
J + 1
如果你需要做的就是增加,那是你唯一的问题