Python 3, 67 mã thông báo
import sys
import time
class Bunny():
def __init__(self):
self.direction = [0, 1]
self.coords = [-1, -1]
def setCoords(self, x, y):
self.coords = [x, y]
def rotate(self, dir):
directions = [[1, 0], [0, 1], [-1, 0], [0, -1]]
if dir == 'L':
self.direction = directions[(directions.index(self.direction) + 1) % 4]
if dir == 'R':
self.direction = directions[(directions.index(self.direction) - 1) % 4]
def hop(self):
self.coords = self.nextTile()
# Returns where the bunny is about to jump to
def nextTile(self):
return [self.coords[0] + self.direction[0], self.coords[1] + self.direction[1]]
class BoardState():
def __init__(self, map):
self.unvisited = 0
self.map = []
self.bunny = Bunny()
self.hopsLeft = 0
for x, row in enumerate(map):
newRow = []
for y, char in enumerate(row):
if char == '#':
newRow.append(1)
self.unvisited += 1
elif char == 'S':
newRow.append(2)
if -1 in self.bunny.coords:
self.bunny.setCoords(x, y)
else:
print("Multiple starting points found", file=sys.stderr)
sys.exit(1)
elif char == ' ':
newRow.append(0)
elif char == 'O':
newRow.append(2)
else:
print("Invalid char in input", file=sys.stderr)
sys.exit(1)
self.map.append(newRow)
if -1 in self.bunny.coords:
print("No starting point defined", file=sys.stderr)
sys.exit(1)
def finished(self):
return self.unvisited == 0
def validCoords(self, x, y):
return -1 < x < len(self.map) and -1 < y < len(self.map[0])
def runCom(self, com):
if self.finished():
return
if self.hopsLeft < self.unvisited:
return
if com == 'F':
x, y = self.bunny.nextTile()
if self.validCoords(x, y) and self.map[x][y] != 0:
self.bunny.hop()
self.hopsLeft -= 1
if (self.map[x][y] == 1):
self.unvisited -= 1
self.map[x][y] = 2
else:
self.bunny.rotate(com)
class loop():
def __init__(self, loops, commands):
self.loops = loops
self.commands = [*commands]
def __str__(self):
return "loop({}, {})".format(self.loops, list(self.commands))
def __repr__(self):
return str(self)
def rejectRedundantCode(code):
if isSnippetRedundant(code):
return False
if type(code[-1]) is str:
if code[-1] in "LR":
return False
else:
if len(code[-1].commands) == 1:
print(code)
if code[-1].commands[-1] in "LR":
return False
return True
def isSnippetRedundant(code):
joined = "".join(str(com) for com in code)
if any(redCode in joined for redCode in ["FFF", "RL", "LR", "RRR", "LLL"]):
return True
for com in code:
if type(com) is not str:
if len(com.commands) == 1:
if com.loops == 2:
return True
if type(com.commands[0]) is not str:
return True
if com.commands[0] in "LR":
return True
if len(com.commands) > 1 and len(set(com.commands)) == 1:
return True
if isSnippetRedundant(com.commands):
return True
for i in range(len(code)):
if type(code[i]) is not str and len(code[i].commands) == 1:
if i > 0 and code[i].commands[0] == code[i-1]:
return True
if i < len(code) - 1 and code[i].commands[0] == code[i+1]:
return True
if type(code[i]) is not str:
if i > 0 and type(code[i-1]) is not str and code[i].commands == code[i-1].commands:
return True
if i < len(code) - 1 and type(code[i+1]) is not str and code[i].commands == code[i+1].commands:
return True
if len(code[i].commands) > 3 and all(type(com) is str for com in code[i].commands):
return True
return False
def flatten(code):
flat = ""
for com in code:
if type(com) is str:
flat += com
else:
flat += flatten(com.commands) * com.loops
return flat
def newGen(n, topLevel = True):
maxLoops = 9
minLoops = 2
if n < 1:
yield []
if n == 1:
yield from [["F"], ["L"], ["R"]]
elif n == 2:
yield from [["F", "F"], ["F", "L"], ["F", "R"], ["L", "F"], ["R", "F"]]
elif n == 3:
for innerCode in newGen(n - 1, False):
for loops in range(minLoops, maxLoops):
if len(innerCode) != 1 and 0 < innerCode.count('F') < 2:
yield [loop(loops, innerCode)]
for com in "FLR":
for suffix in newGen(n - 2, False):
for loops in range(minLoops, maxLoops):
if com not in suffix:
yield [loop(loops, [com])] + suffix
else:
for innerCode in newGen(n - 1, False):
if topLevel:
yield [loop(17, innerCode)]
else:
for loops in range(minLoops, maxLoops):
if len(innerCode) > 1:
yield [loop(loops, innerCode)]
for com in "FLR":
for innerCode in newGen(n - 2, False):
for loops in range(minLoops, maxLoops):
yield [loop(loops, innerCode)] + [com]
yield [com] + [loop(loops, innerCode)]
def codeLen(code):
l = 0
for com in code:
l += 1
if type(com) is not str:
l += codeLen(com.commands)
return l
def test(code, board):
state = BoardState(board)
state.hopsLeft = flatten(code).count('F')
for com in code:
state.runCom(com)
return state.finished()
def testAll():
score = 0
for i, board in enumerate(boards):
print("\n\nTesting board {}:".format(i + 1))
#print('\n'.join(board),'\n')
start = time.time()
found = False
tested = set()
for maxLen in range(1, 12):
lenCount = 0
for code in filter(rejectRedundantCode, newGen(maxLen)):
testCode = flatten(code)
if testCode in tested:
continue
tested.add(testCode)
lenCount += 1
if test(testCode, board):
found = True
stop = time.time()
print("{} token solution found in {} seconds".format(maxLen, stop - start))
print(code)
score += maxLen
break
if found:
break
print("Final Score: {}".format(score))
def testOne(board):
start = time.time()
found = False
tested = set()
dupes = 0
for maxLen in range(1, 12):
lenCount = 0
for code in filter(rejectRedundantCode, newGen(maxLen)):
testCode = flatten(code)
if testCode in tested:
dupes += 1
continue
tested.add(testCode)
lenCount += 1
if test(testCode, board):
found = True
print(code)
print("{} dupes found".format(dupes))
break
if found:
break
print("Length:\t{}\t\tCombinations:\t{}".format(maxLen, lenCount))
stop = time.time()
print(stop - start)
#testAll()
testOne(input().split('\n'))
Chương trình này sẽ kiểm tra một bảng đầu vào duy nhất, nhưng tôi thấy trình điều khiển thử nghiệm này hữu ích hơn . Nó sẽ kiểm tra từng bảng một cùng một lúc và in ra mất bao lâu để tìm ra giải pháp đó. Khi tôi chạy mã đó trên máy của mình (CPU lõi tứ Intel i7-7700K @ 4,20 GHz, RAM 16,0 GB), tôi nhận được đầu ra sau:
Testing board 1:
2 token solution found in 0.0 seconds
['F', 'F']
Testing board 2:
4 token solution found in 0.0025103092193603516 seconds
[loop(17, [loop(3, ['F']), 'R'])]
Testing board 3:
4 token solution found in 0.0010025501251220703 seconds
[loop(17, [loop(3, ['F']), 'L'])]
Testing board 4:
5 token solution found in 0.012532949447631836 seconds
[loop(17, ['F', loop(7, ['F', 'L'])])]
Testing board 5:
5 token solution found in 0.011022329330444336 seconds
[loop(17, ['F', loop(5, ['F', 'L'])])]
Testing board 6:
4 token solution found in 0.0015044212341308594 seconds
[loop(17, [loop(3, ['F']), 'L'])]
Testing board 7:
8 token solution found in 29.32585096359253 seconds
[loop(17, [loop(4, [loop(5, [loop(6, ['F']), 'L']), 'L']), 'F'])]
Testing board 8:
8 token solution found in 17.202533721923828 seconds
[loop(17, ['F', loop(7, [loop(5, [loop(4, ['F']), 'L']), 'F'])])]
Testing board 9:
6 token solution found in 0.10585856437683105 seconds
[loop(17, [loop(7, [loop(4, ['F']), 'L']), 'F'])]
Testing board 10:
6 token solution found in 0.12129759788513184 seconds
[loop(17, [loop(7, [loop(5, ['F']), 'L']), 'F'])]
Testing board 11:
7 token solution found in 4.331984758377075 seconds
[loop(17, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]
Testing board 12:
8 token solution found in 58.620323181152344 seconds
[loop(17, [loop(3, ['F', loop(4, [loop(3, ['F']), 'R'])]), 'L'])]
Final Score: 67
Thử nghiệm cuối cùng này chỉ vừa mới rít lên trong giới hạn phút.
Lý lịch
Đây là một trong những thử thách thú vị nhất mà tôi từng trả lời! Tôi đã có một mô hình vụ nổ săn bắn và tìm kiếm heuristic để cắt giảm mọi thứ.
Nói chung, ở đây trên PPCG tôi có xu hướng trả lời các câu hỏi tương đối dễ. Tôi đặc biệt thích thẻ chuỗi vì nó thường khá phù hợp với ngôn ngữ của tôi. Một ngày, khoảng hai tuần trước, tôi đã xem qua các huy hiệu của mình và tôi nhận ra rằng tôi chưa bao giờ nhận được huy hiệu hồi sinh . Vì vậy, tôi nhìn qua chưa được trả lờitab để xem có gì lọt vào mắt tôi không, và tôi đã tìm thấy câu hỏi này. Tôi quyết định rằng tôi sẽ trả lời nó bất kể chi phí. Cuối cùng, nó khó hơn một chút so với tôi nghĩ, nhưng cuối cùng tôi cũng nhận được câu trả lời tàn bạo mà tôi có thể nói là tôi tự hào. Nhưng thử thách này hoàn toàn không phù hợp với tôi vì tôi thường không dành hơn một giờ cho một câu trả lời. Câu trả lời này đã khiến tôi mất hơn 2 tuần và ít nhất 10+ công việc để cuối cùng đến giai đoạn này, mặc dù tôi đã không theo dõi cẩn thận.
Lặp lại đầu tiên là một giải pháp vũ lực thuần túy. Tôi đã sử dụng đoạn mã sau để tạo tất cả các đoạn có độ dài N :
def generateCodeLenN(n, maxLoopComs, maxLoops, allowRedundant = False):
if n < 1:
return []
if n == 1:
return [["F"], ["L"], ["R"]]
results = []
if 1:
for com in "FLR":
for suffix in generateCodeLenN(n - 1, maxLoopComs, maxLoops, allowRedundant):
if allowRedundant or not isSnippetRedundant([com] + suffix):
results.append([com] + suffix)
for loopCount in range(2, maxLoopComs):
for loopComs in range(1, n):
for innerCode in generateCodeLenN(loopComs, maxLoopComs, maxLoops - 1, allowRedundant):
if not allowRedundant and isSnippetRedundant([loop(loopCount, innerCode)]):
continue
for suffix in generateCodeLenN(n - loopComs - 1, maxLoopComs, maxLoops - 1, allowRedundant):
if not allowRedundant and isSnippetRedundant([loop(loopCount, innerCode)] + suffix):
continue
results.append([loop(loopCount, innerCode)] + suffix)
if loopComs == n - 1:
results.append([loop(loopCount, innerCode)])
return results
Tại thời điểm này, tôi chắc chắn rằng việc kiểm tra mọi câu trả lời có thể sẽ quá chậm, vì vậy tôi đã sử dụng isSnippetRedundant
để lọc các đoạn có thể được viết bằng một đoạn ngắn hơn. Ví dụ: tôi sẽ từ chối cung cấp đoạn trích ["F", "F", "F"]
vì có thể đạt được các hiệu ứng tương tự chính xác [Loop(3, ["F"])
, vì vậy nếu chúng tôi đạt đến điểm mà chúng tôi kiểm tra đoạn mã dài 3, chúng tôi biết rằng không có đoạn mã dài 3 nào có thể giải được bảng hiện tại. Điều này đã sử dụng rất nhiều khả năng ghi nhớ tốt, nhưng cuối cùng là waaaayquá chậm. Testcase 12 chỉ mất hơn 3.000 giây bằng cách sử dụng phương pháp này. Điều này rõ ràng là quá chậm. Nhưng bằng cách sử dụng thông tin này và một loạt các chu kỳ máy tính để đưa ra các giải pháp ngắn cho mọi bảng, tôi có thể tìm thấy một mẫu mới. Tôi nhận thấy rằng hầu hết mọi giải pháp được tìm thấy thường trông giống như sau:
[<com> loop(n, []) <com>]
lồng nhiều lớp sâu, với các coms ở mỗi bên là tùy chọn. Điều này có nghĩa là các giải pháp như:
["F", "F", "R", "F", "F", "L", "R", "F", "L"]
sẽ không bao giờ xuất hiện. Trên thực tế, không bao giờ có một chuỗi gồm hơn 3 mã thông báo không lặp. Một cách để sử dụng điều này sẽ là lọc ra tất cả những thứ này và không bận tâm để kiểm tra chúng. Nhưng việc tạo ra chúng vẫn mất một lượng thời gian không đáng kể và việc lọc qua hàng triệu đoạn như thế này sẽ giúp giảm thời gian. Thay vào đó, tôi viết lại mạnh mẽ trình tạo mã để chỉ tạo các đoạn mã theo mẫu này. Trong mã giả, trình tạo mới tuân theo mẫu chung này:
def codeGen(n):
if n == 1:
yield each [<com>]
if n == 2:
yield each [<com>, <com>]
if n == 3:
yield each [loop(n, <com length 2>]
yield each [loop(n, <com>), <com>]
else:
yield each [loop(n, <com length n-1>)]
yield each [loop(n, <com length n-2>), <com>]
yield each [<com>, loop(n, <com length n-2>)]
# Removed later
# yield each [<com>, loop(n, <com length n-3>), <com>]
# yield each [<com>, <com>, loop(n, <com length n-3>)]
# yield each [loop(n, <com length n-3>), <com>, <com>]
Điều này cắt trường hợp thử nghiệm dài nhất xuống còn 140 giây, đó là một cải tiến vô lý. Nhưng từ đây, vẫn còn một số điều tôi cần cải thiện. Tôi bắt đầu mạnh mẽ hơn để lọc mã dự phòng / vô giá trị và kiểm tra xem mã đã được kiểm tra trước đó chưa. Điều này cắt giảm hơn nữa, nhưng nó không đủ. Cuối cùng, mảnh cuối cùng bị thiếu là bộ đếm vòng lặp. Thông qua thuật toán rất tiên tiến của tôi (đọc: thử nghiệm ngẫu nhiên và lỗi ) Tôi xác định rằng phạm vi tối ưu để cho phép các vòng lặp chạy trong là [3-8]. Nhưng có một cải tiến rất lớn ở đó: Nếu chúng ta biết rằng [loop(8, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]
không thể giải quyết hội đồng quản trị của mình, thì hoàn toàn không có cách nào[loop(3, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]
hoặc bất kỳ số vòng lặp từ 3 - 7 có thể giải quyết nó. Vì vậy, thay vì lặp qua tất cả các kích thước vòng lặp từ 3-8, chúng tôi đặt số vòng lặp trên vòng lặp bên ngoài thành tối đa. Điều này kết thúc việc cắt không gian tìm kiếm xuống theo hệ số maxLoop - minLoop
, hoặc 6 trong trường hợp này.
Điều này đã giúp rất nhiều, nhưng cuối cùng đã làm tăng điểm số. Một số giải pháp tôi đã tìm thấy trước đó bằng vũ lực đòi hỏi số vòng lặp lớn hơn để chạy (ví dụ, bảng 4 và 6). Vì vậy, thay vì đặt số vòng lặp bên ngoài thành 8, chúng tôi đặt số vòng lặp bên ngoài thành 17, một con số kỳ diệu cũng được tính theo thuật toán rất tiên tiến của tôi. Chúng tôi biết rằng chúng tôi có thể làm điều này bởi vì việc tăng số vòng lặp của vòng lặp ngoài cùng không ảnh hưởng đến hiệu lực của giải pháp. Bước này thực sự làm giảm điểm số cuối cùng của chúng tôi xuống 13. Vì vậy, không phải là một bước nhỏ.