Python:使用列表推导进行排序合并

时间:2016-03-17 06:19:02

标签: python

我正在尝试将两个已排序的数组合并为一个已排序的数组。我知道如何一般地这样做(所以问题本身不是关于合并列表),但出于学习目的,我想用列表理解来编写它。理想情况下,我写这个:

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

5 个答案:

答案 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

如果你需要做的就是增加,那是你唯一的问题