Подтвердить что ты не робот

Моделирование маршрута рыцарской последовательности

В настоящее время я пытаюсь написать простую многопоточную программу с использованием Python. Однако я столкнулся с ошибкой, я думаю, что мне не хватает. Я пытаюсь просто написать программу, использующую грубую силу, для решения проблемы ниже:

How the knight must move...

Как видно из изображения, есть шахматная доска, где рыцарь перемещает все соответствующие квадраты.

Мой подход - это просто попробовать каждый возможный путь, где каждый возможный путь - это новый поток. Если в конце потока нет возможных шагов, подсчитайте, сколько посещений было посещено, если оно равно 63 write solution в простом текстовом файле...

Код выглядит следующим образом:

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... Любая помощь будет наиболее приветствоваться...

Спасибо

4b9b3361

Ответ 1

Я стажер Джон Роуч, и он дал мне это как домашнюю работу, я не смог ее решить. Я использовал его аккаунт, чтобы задать вопрос. Ниже мой ответ; Я нашел его решение, используя heuristic, известный как правило Warnsdorff. Но код, который я нашел онлайн, имеет такой вывод:

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 - это кортеж. Я создал список кортежей с именами ходов и вернул его.

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.

И да... Меня отправили на охоту на бекасов...: (

Ответ 2

Ваш так называемый Quest Проблема Knights Who Say Ni, в то время как умная перефразировка для запроса вопроса Python, более широко известна как Задачи Knights Tour. Учитывая тот факт, что вы являетесь учителем математики, я подозреваю, что ваш вопрос, вероятно, является безумным поручением (aka hipe hunt) и что вы полностью осознаете следующий факт:

В соответствии с статьи Википедии о проблеме Knights Tour:

5.1 Алгоритмы грубой силы

Поиск грубой силы для рыцарского тура нецелесообразен для всех, кроме
наименьшие доски; например, на плате 8x8 есть примерно
4x10 51 возможных последовательностей перемещения & lowast; и это значительно превышает возможности
современных компьютеров (или сетей компьютеров) для выполнения операций
на таком большом множестве.

& lowast; Точно 3,926,356,053,343,005,839,641,342,729,308,535,057,127,083,875,101,072 из них в соответствии с footnote.

Ответ 3

В вашем текущем коде есть несколько проблем.

Первая проблема, которую я вижу, заключается в том, что ваш контролер никогда не определяет, какой потенциальный ход будет недействительным. У вас есть ошибка в условном выражении в этом блоке:

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

В цикле выполняется цикл, если to_be_removed пуст. Поскольку цикл над пустым списком немедленно прекращается, он ничего не делает. Я думаю, вы хотите, чтобы if был if 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" (append) или "r+" (чтение и запись без усечения).

Мое последнее предложение не является конкретной ошибкой в ​​коде, но более общей точкой. Использование потоков в этой программе, похоже, не принесло вам ничего. Нитевидный код Python не может работать одновременно, даже если у вас несколько ядер в вашем процессоре из-за Global Interpreter Lock. Threading может иметь смысл для ограниченного кода IO, но для кода с ограниченным процессором, такого как ваш, он собирается добавить сложную нагрузку и отлаживать трудности без каких-либо выгод.

Более базовое решение, которое просто рекурсирует в одном потоке или использует какую-либо другую стратегию, например backtracking, чтобы просмотреть все поисковые запросы пространство почти наверняка будет лучше.

Ответ 4

есть две проблемы, которые я вижу здесь:

1) Вы подсчитываете свои потоки, используя переменную i. Но я передается всем дочерним потокам из вызывающего потока. Таким образом, первый поток будет проходить 1,2,3 для первых 3 дочерних потоков. Но дочерний поток, помеченный 1 wil, затем передаёт 2,3,4 своим трем детям (потоки внуков оригинальной нити). Или, другими словами, вы дублируете номера потоков в разных потоках, что является одной из причин, по которым вы не считаете 12. Вы можете обойти это несколькими способами - проще всего, возможно, использовать переменную, объявленную за пределами области бегун и используйте блокировку, чтобы убедиться, что два потока не изменяют ее одновременно:

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) Вторая проблема заключается в том, что вы выполняете функцию runner:

    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 (который был изменен во всех ваших разных потоках одновременно, как вы это делали)

Ответ 5

Я попытался сделать очень похожее (исследуя большое комбинаторное дерево поиска) в Python, используя MultiProcessing. Я реализовал какой-то алгоритм обработки работы. Вы можете найти результат моего эксперимента, который нацелен на переход в Sagemath на этот патч. Тем не менее, я наконец понял, что Python - очень плохой язык для этого. Я настоятельно рекомендую попробовать Cilk ++ langage, который является надмножеством С++. Это особенно подходит для таких проблем. Вы можете, например, найти решение проблемы 8-queens. Мне жаль, что это всего лишь ответ на ссылку, но я потерял много времени, пытаясь сделать это на Python, прежде чем осознать, что это неправильный путь.

Ответ 6

Обратите внимание, что запись в файл не является потокобезопасной.

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 !")

Ответ 7

Вот решение, которое я придумал, потому что я нашел это интересным (не решил его в течение минуты... так что может быть немного где-то... Используется Поиск глубин-первых, но его можно легко изменить):

#!/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 использует общий доступ к памяти из того, что я читал.

Ответ 8

Пожалуйста, посмотрите этот код, который решает какой-то конкретный тип проблемы Tour Knight Sequence Tour. Он не использует многопоточный подход, но он оптимизирован (алгоритмически) для имитации проблемы маршрута 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())))