【问题标题】:In Python, efficiently determine if two lists are shifted copies of one another在 Python 中,有效地确定两个列表是否是彼此移动的副本
【发布时间】:2013-03-14 16:43:58
【问题描述】:

检查两个相对较短(大约 3-8 个元素)列表是否相互移动副本的最有效(及时)方法是什么?如果是,确定并返回偏移量?

这是我想要的示例代码和输出:

>>> def is_shifted_copy(list_one, list_two):
>>>     # TODO
>>>
>>> is_shifted_copy([1, 2, 3], [1, 2, 3])
0
>>> is_shifted_copy([1, 2, 3], [3, 1, 2])
1
>>> is_shifted_copy([1, 2, 3], [2, 3, 1])
2
>>> is_shifted_copy([1, 2, 3], [3, 2, 1])
None
>>> is_shifted_copy([1, 2, 3], [1])
None
>>> is_shifted_copy([1, 1, 2], [2, 1, 1])
1

列表可能有重复的条目。如果多个偏移量有效,则返回任何偏移量。

【问题讨论】:

  • 如果列表很短,为什么要关心时间效率!
  • 是的,如果速度那么重要,您应该使用另一种数据结构。

标签: python list compare


【解决方案1】:

这是一个简单的迭代器版本,它在 2n 次迭代中完成这项工作(n 是列表的长度)

import itertools

def is_shifted_copy(list1, list2):

    if len(list1) != len(list2):
        return False

    iterator = iter(list2)

    for i, item in enumerate(itertools.chain(list1, list1)):
        try:
            if item == iterator.next():
                continue
            else:
                iterator = iter(list2)
        except StopIteration:
            return i - len(list2)

    else:
        return False


print is_shifted_copy([1, 2, 3], [1, 2, 3]) #0
print is_shifted_copy([1, 2, 3], [3, 1, 2]) #2
print is_shifted_copy([1, 2, 3], [3, 2, 1]) #False
print is_shifted_copy([1, 2, 3], [2, 3, 1]) #1
print is_shifted_copy([1, 1, 2], [2, 1, 1]) #2
print is_shifted_copy([1, 2, 3], [1]) #False
print is_shifted_copy([1, 2, 1], [2, 1, 1]) #1
print is_shifted_copy([1, 1, 1], [1, 1, 1]) #0

根据您的规范,

不应该is_shifted_copy([1, 1, 2], [2, 1, 1])返回2吗?

【讨论】:

  • 我认为规范是一致的。但这是一个细节。我真的不在乎你返回 1 还是 2,只要它是一致的。
【解决方案2】:

搜索第一个列表的两个副本可以避免执行过多的连接:

def is_shifted_copy(l1, l2):
    l1l1 = l1 * 2
    n = len(l1)
    return next((i for i in range(n) if l1l1[i:i + n] == l2), None)

【讨论】:

  • 3 行,没有 try 语句,性能非常接近其他解决方案中最好的。谢谢!
  • 注意:这是O(n**2)时间,O(n)空间。如果n 很大; @thkang solution 是 O(n) 时间,O(1) 空间可能更可取。虽然对于n ~ 3..8 来说没关系。
【解决方案3】:

这是一个基于索引和切片的解决方案:

>>> def is_shifted_copy(l1, l2):
    try:
        return [l1[-i:] + l1[:-i] for i in range(len(l1))].index(l2)
    except ValueError:
        return None

结果如预期:

>>> is_shifted_copy([1, 2, 3], [1, 2, 3])
0
>>> is_shifted_copy([1, 2, 3], [3, 1, 2])
1
>>> is_shifted_copy([1, 2, 3], [2, 3, 1])
2
>>> is_shifted_copy([1, 2, 3], [2, 1, 3])
None

【讨论】:

    【解决方案4】:

    以下是 NPE 解决方案的修改版本,它检查所有可能的匹配位置。它与 thkang(和 ecatmur)的巧妙迭代器解决方案相比毫不逊色:

    import itertools as IT
    
    def is_shifted_copy_thkang(list1, list2):
        N = len(list1)
        if N != len(list2):
            return None
        elif N == 0:
            return 0
        next_item = iter(list2).next
        for i, item in enumerate(IT.chain(list1, list1)):
            try:
                if item == next_item():
                    continue
                else:
                    next_item = iter(list2).next
            except StopIteration:
                return -i % N
        else:
            return None
    
    def is_shifted_copy_NPE(list1, list2):
        N = len(list1)
        if N != len(list2):
            return None
        elif N == 0:
            return 0
    
        pos = -1
        first = list1[0]
        while pos < N:
            try:
                pos = list2.index(first, pos+1)
            except ValueError:
                break
            if (list2 + list2)[pos:pos+N] == list1:
                return pos
        return None
    
    def is_shifted_copy_ecatmur(l1, l2):
        l1l1 = l1 * 2
        n = len(l1)
        return next((-i % n for i in range(n) if l1l1[i:i + n] == l2), None)
    
    
    tests = [
        # ([], [], 0),    
        ([1, 2, 3], [1, 2, 3], 0),
        ([1, 2, 3], [3, 1, 2], 1),
        ([1, 2, 3], [2, 3, 1], 2),
        ([1, 2, 3], [3, 2, 1], None),
        ([1, 2, 3], [1], None),
        ([1, 1, 2], [2, 1, 1], 1),
        ([1,2,3,1,3,2], [1,3,2,1,2,3], 3)
        ]
    
    for list1, list2, answer in tests:
        print(list1, list2, answer)
        assert is_shifted_copy_thkang(list1, list2) == answer
        assert is_shifted_copy_NPE(list1, list2) == answer    
        assert is_shifted_copy_ecatmur(list1, list2) == answer        
    

    In [378]: %timeit is_shifted_copy_thkang([1, 2, 3], [3, 1, 2])
    100000 loops, best of 3: 3.5 us per loop
    
    In [377]: %timeit is_shifted_copy_ecatmur([1, 2, 3], [3, 1, 2])
    100000 loops, best of 3: 2.37 us per loop
    
    In [379]: %timeit is_shifted_copy_NPE([1, 2, 3], [3, 1, 2])
    1000000 loops, best of 3: 1.13 us per loop
    

    注意:我更改了is_shifted_copy_thkangis_shifted_copy_ecatmur 中的返回值,以便所有三个版本都能通过原始帖子中的测试。

    我对有变化和没有变化的函数进行了基准测试,发现它不会显着影响函数的性能。

    例如,return i:

    In [384]: %timeit is_shifted_copy_thkang([1, 2, 3], [3, 1, 2])
    100000 loops, best of 3: 3.38 us per loop
    

    return -i % N:

    In [378]: %timeit is_shifted_copy_thkang([1, 2, 3], [3, 1, 2])
    100000 loops, best of 3: 3.5 us per loop
    

    【讨论】:

    • 如果i in range(n),为什么要i % n?仅仅i 就足够了。
    • @J.F.Sebastian:感谢您发现错误。我原本打算将其设为-i % n for i in range(n)...(因此所有版本都将通过相同的测试),但不知怎的打错了。
    • 你可以在@thkang 的版本中使用next_item = iter(list2).next。它不应该在else 子句中返回0(空列表大小写)吗?
    • @J.F.Sebastian:我将 thkang 改为使用 next_item = iter(list2).next,因为这样可以稍微提高速度。当输入两个空列表时,我修复了 thkang 和 NPE 版本以返回 0,但我没有看到一个漂亮的方法来更改 ecatmur 的代码,它是如此简洁,我不想碰它。时间大致相同。
    猜你喜欢
    • 1970-01-01
    • 2010-09-28
    • 2018-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-23
    相关资源
    最近更新 更多