【问题标题】:Most efficient way to shift and merge the elements of a list in Python (2048)在 Python 中移动和合并列表元素的最有效方法 (2048)
【发布时间】:2014-04-10 11:36:51
【问题描述】:

我有一个函数可以基本上右对齐列表,但也将两个相等的元素合并为一个(列表总是至少有一个元素):

def shift(sequence):
    for i in range(len(sequence)-1):
        current_value = sequence[i]
        next_value = sequence[i+1]
        if next_value == 0:
            sequence[i], sequence[i+1] = 0, current_value
        elif current_value == next_value:
            sequence[i], sequence[i+1] = 0, current_value*2
    return sequence

这是一些输入和输出示例:

>>> shift([0, 0, 1, 0])
[0, 0, 0, 1]
>>> shift([1, 1, 0, 0])
[0, 0, 0, 2]
>>> shift([2, 0, 1, 0])
[0, 0, 2, 1]

最有效的方法是什么?如果对矩阵中的每一行都这样做,有没有比这样做更有效的方法:

matrix = [shift(row) for row in matrix]

另外,如果我要在其他三个方向(除了右边)移动矩阵,有没有比这三个更有效的方法:

#Left
matrix = [shift(row[::-1])[::-1] for row in matrix]

#Down
matrix = map(list, zip(*[shift(row) for row in map(list, zip(*matrix))]))

#Up
matrix = map(list, zip(*[shift(row[::-1])[::-1] for row in map(list, zip(*matrix))]))

如果重复执行这些移位操作(以及每次更改矩阵中的一个值),我应该跟踪什么以提高效率吗?

编辑

我的功能并不总是有效:

>>> shift([1, 1, 1, 1])
[0, 2, 0, 2]

输出应该是:

[0, 0, 2, 2]

更多预期的输入和输出:

[1, 1, 1]             --> [0, 1, 2]
[1, 2, 2, 3, 5, 5, 2] --> [0, 0, 1, 4, 3, 10, 2]

编辑 2

不一定要向右移动,也可以是相反的方向。

【问题讨论】:

  • 这像1024游戏吗?
  • @Cyber​​ 我没听说过,所以没有。
  • 合并递归吗?或者换句话说,[1,1,1,1] 的结果是什么?
  • @NiklasB。 [1, 1, 1, 1] --> [0, 0, 2, 2]。实际上这意味着我的功能不适用于那个......
  • 你应该提到2048,因为这样每个了解游戏的人都可以更容易理解这个问题。此外,您的方法似乎已经渐近最优

标签: python algorithm optimization python-3.x


【解决方案1】:

这是否更高效取决于timeit

def streaming_sum(sequence):
    values = reversed(sequence)
    last = next(values)
    for value in values:
        if value == last:
            yield last + value
            last = 0
        else:
            yield last
            last = value
    yield last


def shift(sequence):
    length = len(sequence)
    reduced = list(reversed(filter(None, streaming_sum(sequence))))
    return [0] * (length - len(reduced)) + reduced


for sequence, expected in [
    ([0, 0, 1, 0], [0, 0, 0, 1]),
    ([1, 1, 0, 0], [0, 0, 0, 2]),
    ([2, 0, 1, 0], [0, 0, 2, 1]),
    ([1, 1, 1, 1], [0, 0, 2, 2]),
    ([1, 1, 1], [0, 1, 2]),
    ([1, 2, 2, 3, 5, 5, 2], [0, 0, 1, 4, 3, 10, 2]),
]:
    actual = shift(sequence)
    assert actual == expected, (actual, expected)

【讨论】:

  • 这会导致错误:TypeError: object of type 'filter' has no len()
  • @Scorpion_God 那是因为filter 在 Python 2 与 Python 3(列表与生成器)中返回不同的对象
  • 这将使[1,1,1] -> [3]。同意这个问题没有详细说明。
  • 这会导致另一个错误:TypeError: argument to reversed() must be a sequence
  • 啊,您使用的是 Python 3。将 filter() 包裹在 list() 中。
【解决方案2】:

这是我目前的解决方案。它比 Kirk Strauser 的解决方案快约 60%。

def shift(self, sequence, right=False):
    if right:
        sequence = sequence[::-1]
    values = []
    empty = 0
    for n in sequence:
        if values and n == values[-1]:
            values[-1] = 2*n
            empty += 1
        elif n:
            values.append(n)
        else:
            empty += 1
    values += [0]*empty
    if right:
        values = values[::-1]
    return values

我的效率更高:

def shift2(length, sequence, right=False):
    if right:
        sequence = sequence[::-1]
    values = [0]*length
    full = 0
    for n in sequence:
        if full and n == values[full]:
            values[full] = 2*n
        elif n:
            values[full] = n
            full += 1
    if right:
        values = values[::-1]
    return values

柯克的解决方案:

def streaming_sum(sequence):
    values = reversed(sequence)
    last = next(values)
    for value in values:
        if value == last:
            yield last + value
            last = 0
        else:
            yield last
            last = value
    yield last

def shift2(sequence):
    length = len(sequence)
    reduced = list(reversed(list(filter(None, streaming_sum(sequence)))))
    return [0] * (length - len(reduced)) + reduced

我对 Kirk 函数的改进(40% 加速):

def shift3(sequence):
    length = len(sequence)
    reduced = [n for n in filter(None, streaming_sum(sequence))][::-1]
    return [0] * (length - len(reduced)) + reduced

时间:

from timeit import Timer

tests = [[1000, 1000, 1000, 1000],
         [1000, 0, 0, 1000],
         [0, 1000],
         [1000, 1000, 0, 500, 0, 500, 1000, 0, 0, 100, 100, 100]]
t1, t2, t3 = 0, 0, 0

for test in tests:
    t1 += Timer(lambda: shift(test)).timeit()
    t2 += Timer(lambda: shift2(test)).timeit()
    t3 += Timer(lambda: shift3(test)).timeit()

>>> print(t1, t2, t3)
10.706327316242147 26.92895738572211 16.65189852514444

在没有右对齐而不是左对齐选项的情况下,我的一个与我更高效的一个与我更高效的一个的时间安排:

10.502816527107633 8.503653343656246 8.15101370397509

【讨论】:

  • 我不认为你的shift 是正确的。它从左到右工作并在右侧添加零。例如,shift([1,1,1,2,2]) => [2, 1, 4, 0, 0] 但它应该是 [0,0,1,2,4]
  • 它对sequence 也有副作用,这可能是不希望的。
  • @TooTone 如果您阅读我的第二次编辑,我说它可以是另一种方式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-14
  • 1970-01-01
  • 2021-10-25
  • 2012-05-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多