模拟骑士序列之旅

时间:2014-02-11 12:26:34

标签: python multithreading

我目前正在尝试使用Python编写一个简单的多线程程序。但是我已经遇到了一个我认为我失踪的错误。我试图简单地编写一个使用蛮力的程序来解决下面的问题:

How the knight must move...

从图像中可以看出,有一个国际象棋棋盘,骑士在各个方块中移动。

我的方法是尝试每种可能的方式,其中每种可能的方式都是新线程。如果在线程的末尾没有可能的移动计数,如果它在一个简单的文本文件上等于63写入解决方案,则访问了多少个方格......

代码如下:

from thread import start_new_thread
import sys

i=1

coor_x = raw_input("Please enter x[0-7]: ")
coor_y = raw_input("Please enter y[0-7]: ")

coordinate = int(coor_x), int(coor_y)



def checker(coordinates, previous_moves):

    possible_moves = [(coordinates[0]+1, coordinates[1]+2), (coordinates[0]+1, coordinates[1]-2),
                      (coordinates[0]-1, coordinates[1]+2), (coordinates[0]-1, coordinates[1]-2),
                      (coordinates[0]+2, coordinates[1]+1), (coordinates[0]+2, coordinates[1]-1),
                      (coordinates[0]-2, coordinates[1]+1), (coordinates[0]-2, coordinates[1]-1)]

    to_be_removed = []

    for index in possible_moves:
        (index_x, index_y) = index
        if index_x < 0 or index_x > 7 or index_y < 0 or index_y > 7:
            to_be_removed.append(index)

    for index in previous_moves:
        if index in possible_moves:
            to_be_removed.append(index)



    if not to_be_removed:
        for index in to_be_removed:
            possible_moves.remove(index)


    if len(possible_moves) == 0:
        if not end_checker(previous_moves):
            print "This solution is not correct"
    else:
        return possible_moves

def end_checker(previous_moves):
    if len(previous_moves) == 63:
        writer = open("knightstour.txt", "w")
        writer.write(previous_moves)
        writer.close()
        return True
    else:
        return False


def runner(previous_moves, coordinates, i):
    if not end_checker(previous_moves):
        process_que = checker(coordinates, previous_moves)
        for processing in process_que:
            previous_moves.append(processing)
            i = i+1
            print "Thread number:"+str(i)
            start_new_thread(runner, (previous_moves, processing, i))
    else:
        sys.exit()



previous_move = []
previous_move.append(coordinate)

runner(previous_move, coordinate, i)
c = raw_input("Type something to exit !")

我对所有建议持开放态度...... 我的示例输出如下:

Please enter x[0-7]: 4
Please enter y[0-7]: 0
Thread number:2
Thread number:3
Thread number:4
Thread number:5Thread number:4
Thread number:5

Thread number:6Thread number:3Thread number:6Thread number:5Thread number:6
Thread number:7
Thread number:6Thread number:8

Thread number:7

Thread number:8Thread number:7
 Thread number:8



Thread number:4
Thread number:5
Thread number:6Thread number:9Thread number:7Thread number:9
Thread number:10
Thread number:11
Thread number:7
Thread number:8
Thread number:9
Thread number:10
Thread number:11
Thread number:12
Thread number:5Thread number:5
 Thread number:6
Thread number:7
Thread number:8
Thread number:9

Thread number:6
Thread number:7
Thread number:8
Thread number:9

如果由于某种原因似乎线程数被卡在12 ... 任何帮助都会受到欢迎......

谢谢

8 个答案:

答案 0 :(得分:30)

你所谓的 Knights Who Say Ni 问题的探索,虽然巧妙地重新提问Python问题,但更广为人知的是Knights Tour数学问题。考虑到这一点以及你是math teacher的事实,我怀疑你的问题可能是一个傻瓜的差事(又名snipe hunt),并且你完全了解以下事实:

根据维基百科关于骑士之旅问题的section文章:

5.1蛮力算法

蛮力搜索骑士之旅对除了之外的所有人都是不切实际的 最小的板;例如,在8x8板上大约有 4x10 51 可能的移动序列 * ,它远远超出容量
现代计算机(或计算机网络)执行操作的 在这么大的一套。

* 正好3,926,356,053,343,005,839,641,342,729,308,535,057,127,083,875,101,072 根据{{​​3}}链接。

答案 1 :(得分:11)

您当前的代码存在多个问题。

我看到的第一个问题是你的检查员永远不会确定任何可能的无效行动。你在这个区块的条件中有一个错误:

if not to_be_removed:
    for index in to_be_removed:
        possible_moves.remove(index)

如果to_be_removed为空,则循环仅运行循环。由于在空列表上循环立即终止,因此它什么都不做。我想你希望ifif to_be_removed,它会测试包含其中某些内容的列表。但是那个测试没有必要。您可以始终运行循环,如果列表为空,则不执行任何操作。

或者更好的是,根本不要使用to_be_removed列表,并使用列表理解直接过滤possible_moves

def checker(coordinates, previous_moves):
    possible_moves = [(coordinates[0]+1, coordinates[1]+2),
                      (coordinates[0]+1, coordinates[1]-2),
                      (coordinates[0]-1, coordinates[1]+2),
                      (coordinates[0]-1, coordinates[1]-2),
                      (coordinates[0]+2, coordinates[1]+1),
                      (coordinates[0]+2, coordinates[1]-1),
                      (coordinates[0]-2, coordinates[1]+1),
                      (coordinates[0]-2, coordinates[1]-1)]

    valid_moves = [(x, y) for x, y in possible_moves
                   if 0 <= x <= 7 and 0 <= y <=7 and (x,y) not in previous_moves]

    return valid_moves # always return the list, even if it is empty

我看到的第二个问题是关于您的previously_seen列表。你的线程都使用对同一个列表实例的引用,并且当它们改变它时(通过调用append中的runner),它们将会相互混淆它的值。也就是说,在第一个线程运行并启动其八个子线程之后,它们将看到相同的情况,所有八个点已经访问过。你可以通过复制列表来解决这个问题,而不是改变它(例如previously_seen + [processing])。

第三个问题是您的线程编号系统无法按您希望的方式工作。原因是每个线程将其八个子线程编号为紧跟其自身编号的值。所以线程1产生线程2-9,但线程2产生线程3-10,重用一堆数字。

有多种方法可以提出更好的数字,但它们并非完全无足轻重。您可以使用每次启动新线程时递增的全局变量,但这需要锁定同步以确保两个线程都不会同时尝试递增它。或者您可以使用某种数学方案来使子线程数唯一(例如,线程i的子线程为i*8加上0-8的数字),但这可能需要跳过一些线程数,因为您无法事先知道由于无效移动而不需要哪些线程。

第四个问题是,即使找到了许多解决方案,您的输出代码也只会让您看到数据文件中的最后结果。这是因为您使用"w"模式打开输出文件,该模式会删除文件的先前内容。您可能希望使用"a"(追加)或"r+"(读写,不截断)。

我的最终建议不是代码中的特定错误,而是更一般的观点。在这个程序中使用线程似乎没有任何收获。即使由于Global Interpreter Lock而在CPU中有多个核心,螺纹Python代码也永远不会同时运行。线程对于IO有限的代码是有意义的,但是对于像你这样的CPU有限的代码,它会增加开销和调试难度而无法获得。

一个更基本的解决方案,只需在单个线程中进行递归,或者使用其他策略(如backtracking来检查所有搜索空间)几乎肯定会更好。

答案 2 :(得分:3)

我可以在这里看到两个问题:

1)您使用变量i计算线程数。但我将从调用线程传递给所有子线程。所以第一个线程将1,2,3传递给前3个子线程。但标记为1的子线程然后将2,3,4传递给其3个子节点(原始线程的孙线程)。或者换句话说,你是在不同的线程中复制线程数,这是你不计算12的一个原因。你可以通过几种方式解决这个问题 - 最简单的方法是使用在范围之外声明的变量。 runner函数并使用一个锁来确保两个线程不会同时修改它:

runnerLock = threading.Lock()
i=0
def runner(previous_moves, coordinates):
global i
if not end_checker(previous_moves):
    process_que = checker(coordinates, previous_moves)
    for processing in process_que:
        previous_moves.append(processing)
        runnerLock.acquire()
        i = i+1
        print "Thread number:"+str(i)
        runnerLock.release()
        start_new_thread(runner, (previous_moves, processing))
else:
    sys.exit()

2)第二个问题是你正在做的转轮功能:

    previous_moves.append(processing)

在for循环中,您希望为当前位置的每个可能移动启动新线程。这样做的问题在于,如果你有4个可能的移动,你想要开始第一个移动线程将有当前的前一个移动加上一个新的附加(这是你想要的)。然而,第二个将有先前的动作+你开始一个线程的第一个新动作+你开始线程的第二个新动作。因此,它之前移动的历史现在已经被破坏了(它有第一种可能性,其他线程正在尝试以及它本来要尝试的那个)。第三种是有两种额外的可能性,依此类推。这可以通过执行(未经测试)来纠正:

runnerLock = threading.Lock()
i=0
def runner(previous_moves, coordinates):
global i
if not end_checker(previous_moves):
    process_que = checker(coordinates, previous_moves)
    temp_previous_moves = previous_moves.deepcopy()
    for processing in process_que:
        temp_previous_moves.append(processing)
        runnerLock.acquire()
        i = i+1
        print "Thread number:"+str(i)
        runnerLock.release()
        start_new_thread(runner, (temp_previous_moves, processing))
else:
    sys.exit()

这种方式也避免了对previous_moves数组的锁定(在你所做的同时在所有不同的线程中进行了修改)

答案 3 :(得分:2)

我尝试使用MultiProcessing在Python中做一个非常相似的事情(探索一个大型的组合搜索树)。我实现了某种work stealing algorithm你可以找到我的实验结果,这些结果旨在this patch进入Sagemath。但是,我终于意识到Python是一种非常糟糕的语言。我强烈建议尝试Cilk++ langage这是C ++的超集。它特别适合这类问题。例如,您可以找到solution of the 8-queens problem。我很抱歉这只是一个链接答案,但我在Python中尝试用它做了很多时间才意识到这不是正确的方法。

答案 4 :(得分:2)

请注意,写入文件不是线程安全的。

import thread
import sys

i=1

coor_x = raw_input("Please enter x[0-7]: ")
coor_y = raw_input("Please enter y[0-7]: ")

coordinate = int(coor_x), int(coor_y)



def checker(coordinates, previous_moves):

    possible_moves = [(coordinates[0]+1, coordinates[1]+2), (coordinates[0]+1, coordinates[1]-2), 
                      (coordinates[0]-1, coordinates[1]+2), (coordinates[0]-1, coordinates[1]-2),    
                      (coordinates[0]+2, coordinates[1]+1), (coordinates[0]+2, coordinates[1]-1),    
                      (coordinates[0]-2, coordinates[1]+1), (coordinates[0]-2, coordinates[1]-1)] 

    possible_moves = [(x,y) for x,y in possible_moves if x >= 0 and x < 8 and y >=0 and y < 8]

    possible_moves = [move for move in possible_moves if move not in previous_moves]

    if len(possible_moves) == 0:
        if not end_checker(previous_moves):
            print "This solution is not correct"
    else:
        return possible_moves

def end_checker(previous_moves):
    if len(previous_moves) == 63:
        writer = open("knightstour.txt", "w")
        writer.write(str(previous_moves) + "\n")
        writer.close()
        return True
    else:
        return False


def runner(previous_moves, coordinates, i):
    if not end_checker(previous_moves):
        process_que = checker(coordinates, previous_moves)
        if not process_que:
            thread.exit()
        for processing in process_que:
            previous_moves.append(processing)
            i = i+1
            print "Thread number:"+str(i)
            thread.start_new_thread(runner, (previous_moves, processing, i))
    else:
        sys.exit()



previous_move = []
previous_move.append(coordinate)

runner(previous_move, coordinate, i)
c = raw_input("Type something to exit !")

答案 5 :(得分:1)

这是我提出的一个解决方案,因为我发现这很有意思(没有在一分钟内解决它......所以可能有点偏离某个地方......使用Depth-First search,但很容易被改变了):

#!/usr/bin/env python
# you should probably be using threading... python docs suggest thread is 
from threading import Thread
import itertools
import time


def is_legal(move):
    x = move[0]
    y = move[1]
    return 8 > x >= 0 and 8 > y >= 0


def get_moves(current, previous_moves):
    possibilities = []
    possibilities.extend(itertools.product([1,-1],[2,-2]))
    possibilities.extend(itertools.product([2, -2],[1,-1]))
    for mx, my in possibilities:
        move_dest = [current[0] + mx, current[1] + my]
        if is_legal(move_dest) and not move_dest in previous_moves:
            yield (move_dest)


def solve_problem(current, previous_moves):
    # add location to previous moves...
    previous_moves.append(current)
    threads = []
    for move in get_moves(current, previous_moves):
        # start a thread for every legal move
        t = Thread(target=solve_problem, args=(list(move), list(previous_moves)))
        threads.extend([t])
        t.start()
        # dfs prevent resource overflow...
        # - makes threads redundant as mentioned in comments
        # t.join()
    if len(previous_moves) % 20 == 0:
        #   print "got up to %d moves !\n" % len(previous_moves)
        pass

    if len(previous_moves) == 64:
        print " solved !\n" % len(previous_moves)
        # check to see if we're done
        t = int(time.time())
        with open('res_%d' % t, 'w') as f:
            f.write("solution: %r" % previous_moves)
            f.close()

#    for t in threads:
#        t.join()


if "__main__" == __name__:
    print "starting..."
    coor_x = int(raw_input("Please enter x[0-7]:"))
    coor_y = int(raw_input("Please enter y[0-7]:"))
    start = [coor_x, coor_y]
    print "using start co-ordinations: %r" % start
    solve_problem(start, [])

Threadinv vs Thread

通过快速重新检查代码,可以尝试确保将事实复制到子线程中。 Python默认从我读过的内容中分享内存。

答案 6 :(得分:1)

请查看this代码,该代码解决了特定类型的骑士序列旅游问题。它不使用多线程方法,但它是高度优化的(算法)模拟骑士序列导览问题,如果它是您正在寻找的速度(并且它不使用线程)。我相信你可以根据你的用例进行调整。只需修改build_keypad函数以匹配棋盘拓扑并删除元音约束代码。希望它有所帮助。

__author__ = 'me'
'''
Created on Jun 5, 2012

@author: Iftikhar Khan
'''
REQD_SEQUENCE_LENGTH = 10
VOWEL_LIMIT = 2
VOWELS = [(0, 0), (4, 0), (3, -1), (4, -2)]


def build_keypad():
    """Generates 2-D mesh representation of keypad."""
    keypad = [(x, y) for x in range(5) for y in range(-3, 1)]
    # adjust topology
    keypad.remove((0, -3))
    keypad.remove((4, -3))
    return keypad


def check_position(position):
    """Determines if the transform is valid. That is, not off-keypad."""
    if position == (0, -3) or position == (4, -3):
        return False

    if (-1 < position[0] < 5) and (-4 < position[1] < 1):
        return True
    else:
        return False


def build_map(keypad):
    """Generates a map of all possible Knight moves for all keys."""
    moves = [(1, -2), (1, 2), (2, -1), (2, 1), (-1, -2), (-1, 2), (-2, -1), (-2, 1)]
    keymap = {}
    for key_pos in keypad:
        for move in moves:
            x = key_pos[0] + move[0]
            y = key_pos[1] + move[1]
            if check_position((x, y)):
                keymap.setdefault(key_pos, []).append((x, y))
    return keymap


def build_sequence(k, p, m, v, ks):
    """Generates n-key sequence permutations under m-vowel constraints using
        memoization optimisation technique. A valid permutation is a function
        of a key press, position of a key in a sequence and the current
        vowel count. This memoization data is stored as a 3-tuple, (k,p,v), in
        dictionary m.
    """
    if k in VOWELS:
        v += 1
        if v > VOWEL_LIMIT:
            v -= 1
            return 0

    if p == REQD_SEQUENCE_LENGTH:
        m[(k, p, v)] = 0
        return 1
    else:
        if (k, p, v) in m:
            return m[(k, p, v)]
        else:
            m[(k, p, v)] = 0
            for e in ks[k]:
                m[(k, p, v)] += build_sequence(e, p + 1, m, v, ks)

    return m[(k, p, v)]


def count(keys):
    """Counts all n-key permutations under m-vowel constraints."""
    # initialise counters
    sequence_position = 1
    vowel_count = 0
    memo = {}

    return sum(build_sequence(key, sequence_position, memo, vowel_count, keys)
               for key in keys)


if __name__ == '__main__':
    print(count(build_map(build_keypad())))

答案 7 :(得分:0)

我是John Roach的实习生,他把这作为家庭作业给了我,我无法解决。我用他的账户问了这个问题。以下是我的答案; 我使用称为heuristicWarnsdorff's rule找到了解决方案。 但是我发现online的代码有这样的输出:

boardsize: 5
Start position: c3

19,12,17, 6,21
 2, 7,20,11,16
13,18, 1,22, 5
 8, 3,24,15,10
25,14, 9, 4,23

因为我改变了一点,而不是使用标准输出,我使用P,因为P的格式是元组。我创建了一个名为move的元组列表并将其返回。

def knights_tour(start, boardsize=boardsize, _debug=False):
    board = {(x,y):0 for x in range(boardsize) for y in range(boardsize)}
    move = 1
    P = chess2index(start, boardsize)
    moves.append(P)

    board[P] = move
    move += 1
    if _debug:
        print(boardstring(board, boardsize=boardsize))
    while move <= len(board):
        P = min(accessibility(board, P, boardsize))[1]
        moves.append(P)
        board[P] = move
        move += 1
        if _debug:
            print(boardstring(board, boardsize=boardsize))
            input('\n%2i next: ' % move)
    return moves

现在我有了动作列表,我编写了以下程序来创建一个GIF,动画那些动作。代码如下;

import sys
import pygame
import knightmove
import os


pygame.init()

square_list = []
line_list = []
i = 0
j = 1


def make_gif():
    os.system("convert   -delay 40   -loop 0   Screenshots/screenshot*.png   knights_tour.gif")

def get_moves(start_move):
    return knightmove.knights_tour(start_move, 8)

def scratch(move):
    move_x, move_y = move
    x = int(move_x) * 50
    y = int(move_y) * 50
    line_list.append([x+25, y+25])
    square_list.append([x, y])
    for index in range(len(square_list)):
        screen.blit(square, square_list[index])

def draw_line():
    for index in range(len(line_list)-1):
        pygame.draw.line(screen, black, (line_list[index]), (line_list[index+1]), 2)

def draw_dot():
    return pygame.draw.circle(screen, red, (line_list[i]), 3, 0)

def screenshot():
    if j <= 9:
        c = "0"+str(j)
    else:
        c = j
    pygame.image.save(screen, "/home/renko/PycharmProjects/pygame_tut1/Screenshots/screenshot"+str(c)+".png")


size = width, height = 400, 400
white = 255, 255, 255
black = 0, 0, 0, 0
red = 255, 0, 0

screen = pygame.display.set_mode(size)
square = pygame.image.load("Untitled.png")

start = raw_input("Enter the start position:")
moves = get_moves(start)


while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
    screen.fill(white)
    pygame.draw.line(screen, black, (0, 50), (400, 50), 3)
    pygame.draw.line(screen, black, (0, 100), (400, 100), 3)
    pygame.draw.line(screen, black, (0, 150), (400, 150), 3)
    pygame.draw.line(screen, black, (0, 200), (400, 200), 3)
    pygame.draw.line(screen, black, (0, 250), (400, 250), 3)
    pygame.draw.line(screen, black, (0, 300), (400, 300), 3)
    pygame.draw.line(screen, black, (0, 350), (400, 350), 3)

    pygame.draw.line(screen, black, (50, 0), (50, 400), 3)
    pygame.draw.line(screen, black, (100, 0), (100, 400), 3)
    pygame.draw.line(screen, black, (150, 0), (150, 400), 3)
    pygame.draw.line(screen, black, (200, 0), (200, 400), 3)
    pygame.draw.line(screen, black, (250, 0), (250, 400), 3)
    pygame.draw.line(screen, black, (300, 0), (300, 400), 3)
    pygame.draw.line(screen, black, (350, 0), (350, 400), 3)



    scratch(moves[i])
    draw_line()
    draw_dot()
    screenshot()
    i += 1
    j += 1
    pygame.display.flip()
    if i == 64:
        make_gif()
        print "GIF was created"
        break

您知道导入的骑士移动库是我使用rosettacode.org算法创建的库。

是的......我被发送了狙击...... :(