使用Python的Langford数字配对

时间:2017-11-12 08:26:18

标签: python python-3.x performance

我正在尝试使用Python生成Langford数字。我已经编写了以下代码,该代码适用于获得第四个Langford数字。这是我的代码:

import itertools

n=0
for l in set(itertools.permutations(["1", "1", "2", "2", "3", "3", "4", "4"])):
    t1, t2, t3, t4 = [i for i, j in enumerate(l) if j == "1"], [i for i, j in enumerate(l) if j == "2"], [i for i, j in enumerate(l) if j == "3"], [i for i, j in enumerate(l) if j == "4"]
    if abs(t1[1]-t1[0]) == 2 and abs(t2[1]-t2[0]) == 3 and abs(t3[1]-t3[0]) == 4 and abs(t4[1]-t4[0]) == 5:
        print("".join(l))
        n+=1
    else:
    pass

print(n)

我有两个问题:

  • 首先,是否有技术可以更快地生成此代码(目前它在0.1秒内找到结果)
  • 第二,你能给我一些关于如何调整代码以获得任何第n个Langford数字的提示

我想知道,here是Langford数字的维基百科页面。

如果你花时间回答我,非常感谢你!

2 个答案:

答案 0 :(得分:3)

是的,有一些更快的方法来制作Langford序列。

首先,这是一个相当简单的方法。我们不是测试包含从1到n的数字对的所有排列,而是生成从1到n的数字排列,然后尝试通过将每对数字放入下一个可用的插槽对来从这些排列构建Langford序列。如果没有可用的插槽,我们放弃那个插入并转到下一个插槽。

构建序列比仅仅测试2n项的完全排列是否有效要慢一些,但这意味着当n很大时,我们需要测试 lot 更少的排列。例如,如果n = 7则有7个! = 5040排列,但如果我们测试7对的排列,那就是14! = 87178291200排列!

我们可以减少这个数字,因为它包含很多重复项。对于7对,唯一排列的数量是14! / (2**7) = 681080400,因为交换7对中的任何一对中的2个项目产生重复的排列。遗憾的是,itertools.permutations并不关心重复项,但my answer here具有不会产生重复排列的排列生成器的代码。但是,仍然有6.81亿个排列很多,测试它们需要很长时间。如果我们能够避免这样做,那就更好了。

import sys
from itertools import permutations

def place(t):
    slen = 2 * len(t)
    seq = [0] * slen
    for u in t:
        # Find next vacant slot
        for i, v in enumerate(seq):
            if v == 0:
                break
        else:
            # No vacant slots
            return
        j = i + u + 1
        if j >= slen or seq[j]:
            return
        seq[i] = seq[j] = u
    return tuple(seq)

def langford(n):
    count = 0
    for t in permutations(range(1, n+1)):
        seq = place(t)
        #if seq and seq < seq[::-1]:
        if seq:
            count += 1
            print(seq, count)
    return count // 2

def main():
    n = int(sys.argv[1]) if len(sys.argv) > 1 else 4
    count = langford(n)
    print(count)

if __name__ == '__main__':
    main()

输出n = 7

(1, 4, 1, 5, 6, 7, 4, 2, 3, 5, 2, 6, 3, 7) 1
(1, 4, 1, 6, 7, 3, 4, 5, 2, 3, 6, 2, 7, 5) 2
(1, 5, 1, 4, 6, 7, 3, 5, 4, 2, 3, 6, 2, 7) 3
(1, 5, 1, 6, 3, 7, 4, 5, 3, 2, 6, 4, 2, 7) 4
(1, 5, 1, 6, 7, 2, 4, 5, 2, 3, 6, 4, 7, 3) 5
(1, 5, 1, 7, 3, 4, 6, 5, 3, 2, 4, 7, 2, 6) 6
(1, 6, 1, 3, 5, 7, 4, 3, 6, 2, 5, 4, 2, 7) 7
(1, 6, 1, 7, 2, 4, 5, 2, 6, 3, 4, 7, 5, 3) 8
(1, 7, 1, 2, 5, 6, 2, 3, 4, 7, 5, 3, 6, 4) 9
(1, 7, 1, 2, 6, 4, 2, 5, 3, 7, 4, 6, 3, 5) 10
(2, 3, 6, 2, 7, 3, 4, 5, 1, 6, 1, 4, 7, 5) 11
(2, 3, 7, 2, 6, 3, 5, 1, 4, 1, 7, 6, 5, 4) 12
(2, 4, 7, 2, 3, 6, 4, 5, 3, 1, 7, 1, 6, 5) 13
(2, 5, 6, 2, 3, 7, 4, 5, 3, 6, 1, 4, 1, 7) 14
(2, 6, 3, 2, 5, 7, 3, 4, 6, 1, 5, 1, 4, 7) 15
(2, 6, 3, 2, 7, 4, 3, 5, 6, 1, 4, 1, 7, 5) 16
(2, 6, 7, 2, 1, 5, 1, 4, 6, 3, 7, 5, 4, 3) 17
(2, 7, 4, 2, 3, 5, 6, 4, 3, 7, 1, 5, 1, 6) 18
(3, 4, 5, 7, 3, 6, 4, 1, 5, 1, 2, 7, 6, 2) 19
(3, 4, 6, 7, 3, 2, 4, 5, 2, 6, 1, 7, 1, 5) 20
(3, 5, 7, 2, 3, 6, 2, 5, 4, 1, 7, 1, 6, 4) 21
(3, 5, 7, 4, 3, 6, 2, 5, 4, 2, 7, 1, 6, 1) 22
(3, 6, 7, 1, 3, 1, 4, 5, 6, 2, 7, 4, 2, 5) 23
(3, 7, 4, 6, 3, 2, 5, 4, 2, 7, 6, 1, 5, 1) 24
(4, 1, 6, 1, 7, 4, 3, 5, 2, 6, 3, 2, 7, 5) 25
(4, 1, 7, 1, 6, 4, 2, 5, 3, 2, 7, 6, 3, 5) 26
(4, 5, 6, 7, 1, 4, 1, 5, 3, 6, 2, 7, 3, 2) 27
(4, 6, 1, 7, 1, 4, 3, 5, 6, 2, 3, 7, 2, 5) 28
(4, 6, 1, 7, 1, 4, 5, 2, 6, 3, 2, 7, 5, 3) 29
(4, 6, 3, 5, 7, 4, 3, 2, 6, 5, 2, 1, 7, 1) 30
(5, 1, 7, 1, 6, 2, 5, 4, 2, 3, 7, 6, 4, 3) 31
(5, 2, 4, 6, 2, 7, 5, 4, 3, 1, 6, 1, 3, 7) 32
(5, 2, 4, 7, 2, 6, 5, 4, 1, 3, 1, 7, 6, 3) 33
(5, 2, 6, 4, 2, 7, 5, 3, 4, 6, 1, 3, 1, 7) 34
(5, 2, 7, 3, 2, 6, 5, 3, 4, 1, 7, 1, 6, 4) 35
(5, 3, 6, 4, 7, 3, 5, 2, 4, 6, 2, 1, 7, 1) 36
(5, 3, 6, 7, 2, 3, 5, 2, 4, 6, 1, 7, 1, 4) 37
(5, 6, 1, 7, 1, 3, 5, 4, 6, 3, 2, 7, 4, 2) 38
(5, 7, 1, 4, 1, 6, 5, 3, 4, 7, 2, 3, 6, 2) 39
(5, 7, 2, 3, 6, 2, 5, 3, 4, 7, 1, 6, 1, 4) 40
(5, 7, 2, 6, 3, 2, 5, 4, 3, 7, 6, 1, 4, 1) 41
(5, 7, 4, 1, 6, 1, 5, 4, 3, 7, 2, 6, 3, 2) 42
(6, 1, 5, 1, 7, 3, 4, 6, 5, 3, 2, 4, 7, 2) 43
(6, 2, 7, 4, 2, 3, 5, 6, 4, 3, 7, 1, 5, 1) 44
(7, 1, 3, 1, 6, 4, 3, 5, 7, 2, 4, 6, 2, 5) 45
(7, 1, 4, 1, 6, 3, 5, 4, 7, 3, 2, 6, 5, 2) 46
(7, 2, 4, 5, 2, 6, 3, 4, 7, 5, 3, 1, 6, 1) 47
(7, 2, 4, 6, 2, 3, 5, 4, 7, 3, 6, 1, 5, 1) 48
(7, 2, 6, 3, 2, 4, 5, 3, 7, 6, 4, 1, 5, 1) 49
(7, 3, 1, 6, 1, 3, 4, 5, 7, 2, 6, 4, 2, 5) 50
(7, 3, 6, 2, 5, 3, 2, 4, 7, 6, 5, 1, 4, 1) 51
(7, 4, 1, 5, 1, 6, 4, 3, 7, 5, 2, 3, 6, 2) 52
26

我的旧2GHz机器需要大约0.2秒。

传统上,如果一个是另一个的逆转,则认为2个Langford序列是相同的。解决这个问题的一种方法是将序列与其反转版本进行比较,只有在反转版本小于反转版本时才打印。您可以通过注释掉

来更改上述代码
if seq:
langford函数中

并取消注释以下行:

#if seq and seq < seq[::-1]:

以上代码是一项改进,但我们可以做得更好。我的下一个解决方案使用了一种称为递归backtracking的技术。通过使用递归生成器函数,可以在Python中优雅地实现此技术。

我们从一系列零开始。从最高的数字开始,我们尝试在每个合法的一对插槽中放置一对数字,如果我们成功,我们会递归下一对数字,如果没有剩下的数字,我们会#39我们找到了一个解决方案,我们可以放弃它。

import sys

def langford(n, seq):
    ''' Generate Langford sequences by recursive backtracking '''
    # The next n
    n1 = n - 1
    # Test each valid pair of positions for this n
    for i in range(0, len(seq) - n - 1):
        j = i + n + 1
        if not (seq[i] or seq[j]):
            # Insert this n into the sequence
            seq[i] = seq[j] = n
            if n1:
                # Recurse to add the next n
                yield from langford(n1, seq)
            else:
                # Nothing left to insert
                yield seq
            # Remove this n from the sequence in preparation
            # for trying the next position
            seq[i] = seq[j] = 0

def main():
    n = int(sys.argv[1]) if len(sys.argv) > 1 else 4
    for i, t in enumerate(langford(n, [0] * 2 * n), 1):
        print(t, i)
        if i % 1000 == 0:
            print(' ', i, end='\r', flush=True)
    print('\n', i // 2)

if __name__ == '__main__':
    main()

if i % 1000 == 0:内容可让您查看n较大时的进度。如果您注释掉print(t, i)行,这很方便。

此代码可以在25秒内在我的机器上为n = 11生成35584 = 2 * 17792序列。

如果您想将langford产生的序列收集到列表中,而不是仅仅打印它们,您可以这样做:

n = 7
results = list(langford(n, [0] * 2 * n))

但是,如果您想这样做,必须langford功能稍作更改。在哪里说

yield seq

将其更改为

yield seq[:]

这样它就会生成seq的副本,而不是原始的seq列表。

如果您只想获得序列计数(不计算逆转),您可以这样做:

n = 7
count = sum(1 for _ in langford(n, [0] * 2 * n)) // 2
print(count)

这对yield seq即可。

对于较大的n值,上述代码会很慢。使用相当高级的数学,有更快的技术来计算Langford序列的数量,但是没有已知的简单公式。 OEIS在A014552处有一个Langford序列号列表。

答案 1 :(得分:-1)

你使事情复杂化:所需要的只是生成所有排列,并消除那些不是Langford序列的排列。

1-不要使用set(itertools...),itertools已经返回了唯一的元素 对于每个排列,你必须检查它是否是一个Langfort序列 3-如果没有,请打破并检查下一个
4-如果是,检查其逆是否尚未整理,并将其保存在一组唯一元素中 5-返回得到的独特langfort序列

此代码对n = 4很快,可以找到任意n的序列;然而,时间复杂度是大规模指数;过去n=6,需要相当多的时间才能完成。

import itertools

def langfort(n):
    seq = [_ for _ in range(1, n+1)] * 2
    lang = set()
    for s in itertools.permutations(seq):
        for elt in seq:
            first = s.index(elt)
            if s[first+1:].index(elt) == elt:
                continue
            else:
                break
        else:
            if s[::-1] not in lang:
                lang.add(s)
    return lang

langfort(4)

输出:

{(4, 1, 3, 1, 2, 4, 3, 2)}

性能:

在2011年的mac书上播出:

%timeit langfort(4)
10 loops, best of 3: 53.4 ms per loop

更多输出:

langfort(5)
set()          # there are no langfort(5) sequences
langfort(6)
set()          # there are no langfort(6) sequences