23

我目前正在尝试使用 Python 编写一个简单的多线程程序。但是我遇到了一个我认为我错过的错误。我正在尝试简单地编写一个使用蛮力方法解决以下问题的程序:

骑士必须如何移动...

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

我的方法是简单地尝试每一种可能的方式,其中每一种可能的方式都是一个新线程。如果在线程结束时没有可能的移动,如果等于 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...任何帮助将是最受欢迎的...

谢谢

4

8 回答 8

30

您所谓的Quest of the Knights Who Say Ni问题虽然是对 Python 问题的巧妙改写,但更广为人知的是Knights Tour数学问题。鉴于这一点以及您是数学老师的事实,我怀疑您的问题可能是愚蠢的差事(又名狙击狩猎),并且您完全了解以下事实:

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

5.1 蛮力算法

蛮力搜索骑士之旅是不切实际的,除了
最小的棋盘;例如,在一个 8x8 棋盘上,大约有
4x10 51 个可能的移动序列*
,而 现代计算机(或计算机网络)
在如此大的集合上执行操作远远超出了能力。

根据脚注链接,其中有 3,926,356,053,343,005,839,641,342,729,308,535,057,127,083,875,101,072 个

于 2014-02-17T15:44:33.867 回答
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清单。您的线程都在使用对同一个列表实例的引用,并且当它们改变它(通过调用appendin runner)时,它们将相互混淆它的值。也就是说,在第一个线程运行并启动它的八个子线程之后,它们每个都会看到相同的情况,所有八个点都已经访问过。您可以通过复制列表来解决这个问题,而不是改变它(例如previously_seen + [processing])。

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

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

第四个问题是即使找到许多解决方案,您的输出代码也只会让您看到数据文件中的最后一个结果。这是因为您使用模式打开输出文件,该"w"模式会删除文件的先前内容。您可能想要使用"a"(append) 或"r+"(read and write, without truncating)。

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

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

于 2014-02-11T14:04:01.070 回答
3

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

1)您正在使用变量 i 计算线程数。但是 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 个可能的动作,你想为第一个启动线程将有当前先前的动作加上一个新的附加(这是你想要的)。但是,第二个将具有先前的动作+您启动线程的第一个新动作+您启动线程的第二个新动作。因此,它先前移动的历史现在已被破坏(它有第一种可能是另一个线程正在尝试以及它打算尝试的那个)。第三个是有 2 个额外的可能性,依此类推。这可以通过执行(未经测试)来纠正:

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 数组的锁定需求(在您执行此操作的同时在所有不同的线程中对其进行修改)

于 2014-02-11T14:07:34.367 回答
2

I tried to do a very similar thing (exploring a large combinatorial search tree) in Python using MultiProcessing. I implemented some kind of work stealing algorithm You can find the result of my experiment which are aiming to go into Sagemath at this patch. However, I finally realized that Python is a very bad language to do that. I strongly suggest trying the Cilk++ langage which is a superset of C++. It particularly fits those kind of problems. You can for example find a solution of the 8-queens problem. I'm sorry it is only a link answer but I lost a lot of time trying to do that in Python before realizing that this is not the right way.

于 2014-02-16T08:37:56.437 回答
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 !")
于 2014-02-18T18:58:42.043 回答
1

这是我想出的一个解决方案,因为我发现这很有趣(在一分钟内没有解决它......所以可能在某个地方有点偏离......使用了深度优先搜索,但可以轻松更改):

#!/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 与 Thread

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

于 2014-02-11T13:54:09.137 回答
1

请查看代码,它解决了特定类型的 Knight Sequence Tour 问题。它不使用多线程方法,但如果您正在寻找速度(并且它不使用线程),它会高度优化(算法上)来模拟骑士序列巡回赛问题。我相信你可以根据你的用例调整它。只需修改 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())))
于 2014-02-20T09:25:25.373 回答
0

我是 John Roach 的实习生,他给了我这个作为家庭作业,我无法解决它。我用他的账号问了这个问题。以下是我的回答;我通过使用称为Warnsdorff's rule的启发式方法找到了它的解决方案。但是我在网上找到的代码有这样的输出:

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 算法创建的库一样。

是的......我被派去狙击狩猎...... :(

于 2014-02-20T14:45:28.200 回答