पायथन 3, 67 टोकन
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'))
यह प्रोग्राम एकल इनपुट बोर्ड का परीक्षण करेगा, लेकिन मुझे यह परीक्षण ड्राइवर अधिक उपयोगी लगता है । यह एक ही समय में हर एक बोर्ड का परीक्षण करेगा और प्रिंट करेगा कि उस समाधान को खोजने में कितना समय लगा। जब मैं अपनी मशीन (Intel i7-7700K quad core CPU @ 4.20 GHz, 16.0 GB RAM) पर उस कोड को चलाता हूं, तो मुझे निम्न आउटपुट मिलते हैं:
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
यह अंतिम परीक्षण मिनट प्रतिबंध के तहत मुश्किल से स्क्वीक्स है।
पृष्ठभूमि
यह सबसे मजेदार चुनौतियों में से एक थी जिसका मैंने कभी जवाब दिया है! मेरे पास एक विस्फोट पैटर्न का शिकार था और चीजों को काटने के लिए उत्तराधिकार की तलाश कर रहा था।
आमतौर पर, यहाँ PPCG पर मैं अपेक्षाकृत आसान सवालों के जवाब देता हूं। मैं विशेष रूप से स्ट्रिंग टैग का शौकीन हूं क्योंकि यह आम तौर पर मेरी भाषाओं के लिए काफी अनुकूल है। एक दिन, लगभग दो सप्ताह पहले, मैं अपने बैज के माध्यम से देख रहा था और मुझे एहसास हुआ कि मैंने कभी भी रिवाइवल बैज हासिल नहीं किया था । तो मैंने अनुत्तरित के माध्यम से देखायह देखने के लिए कि क्या किसी ने मेरी आंख को पकड़ा है, और मुझे यह प्रश्न मिला। मैंने फैसला किया कि मैं इसका जवाब दूंगा, कोई फर्क नहीं पड़ता। यह समाप्त होने से थोड़ा मुश्किल था जितना मैंने सोचा था कि यह होगा, लेकिन मुझे अंततः एक क्रूर-बल जवाब मिला कि मैं कह सकता हूं कि मुझे गर्व है। लेकिन यह चुनौती मेरे लिए आदर्श से पूरी तरह से बाहर है क्योंकि मैं आमतौर पर एक घंटे या एक से अधिक उत्तर पर खर्च नहीं करता हूं। इस जवाब ने मुझे 2 सप्ताह से थोड़ा अधिक समय दिया और कम से कम 10+ काम अंत में इस चरण में लाने के लिए हुआ, हालांकि मैं सावधानी से नज़र नहीं रख रहा था।
पहला पुनरावृत्ति एक शुद्ध जानवर बल समाधान था। मैंने निम्नलिखित कोड का उपयोग लंबाई 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
इस बिंदु पर, मुझे यकीन था कि हर एक संभावित उत्तर का परीक्षण बहुत धीमी गति से होगा, इसलिए मैं isSnippetRedundant
उन स्निपेट को फ़िल्टर करता था जिन्हें एक छोटे स्निपेट के साथ लिखा जा सकता था। उदाहरण के लिए, मैं स्निपेट की पैदावार करने से इंकार कर दूंगा ["F", "F", "F"]
क्योंकि सटीक समान प्रभाव के साथ प्राप्त किया जा सकता है [Loop(3, ["F"])
, इसलिए यदि यह उस बिंदु पर पहुंचता है जहां हम लंबाई -3 स्निपेट का परीक्षण करते हैं, तो हम जानते हैं कि कोई भी लंबाई -3 स्निपेट वर्तमान बोर्ड को हल नहीं कर सकता है। इसने बहुत सारे अच्छे ज्ञानशास्त्रों का उपयोग किया, लेकिन अंत में वायाय थाबहुत धीमा। इस दृष्टिकोण का उपयोग करते हुए टेस्टकेस 12 को 3,000 सेकंड से अधिक समय लगा। यह स्पष्ट रूप से काफी धीमा है। लेकिन इस जानकारी और कंप्यूटर साइकिल के एक समूह का उपयोग करने के लिए हर बोर्ड को कम समाधान के लिए मजबूर करने के लिए, मुझे एक नया पैटर्न मिल सकता है। मैंने देखा कि पाया जाने वाला लगभग हर समाधान आम तौर पर निम्नलिखित की तरह दिखाई देगा:
[<com> loop(n, []) <com>]
नेस्टेड कई लेयर्स गहरी हैं, जिनमें से प्रत्येक तरफ एक ही कोम वैकल्पिक है। इसका मतलब है कि समाधान:
["F", "F", "R", "F", "F", "L", "R", "F", "L"]
कभी नहीं दिखाई देगा। वास्तव में, 3 से अधिक गैर-लूप टोकन का अनुक्रम कभी नहीं था। इसका उपयोग करने का एक तरीका इन सभी को फ़िल्टर करना होगा और उन्हें जांचने की जहमत नहीं उठानी होगी। लेकिन उन्हें पैदा करना अभी भी एक गैर-नगण्य मात्रा में समय ले रहा था, और लाखों स्निपेट्स के माध्यम से इस तरह फ़िल्टर करना मुश्किल से समय काट देगा। इसके बजाय मैंने इस पैटर्न के बाद केवल स्निपेट्स जेनरेट करने के लिए कोड-जनरेटर को फिर से लिखा। छद्म कोड में, नया जनरेटर इस सामान्य पैटर्न का अनुसरण करता है:
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>]
इसने सबसे लंबे परीक्षण मामले को 140 सेकंड तक काट दिया, जो एक हास्यास्पद सुधार है। लेकिन यहां से, अभी भी कुछ चीजें थीं जिन्हें मुझे सुधारने की आवश्यकता थी। मैंने अनावश्यक रूप से अनावश्यक / बेकार कोड को फ़िल्टर करना शुरू कर दिया और यह देखने के लिए जाँच किया कि क्या कोड का परीक्षण पहले किया जा चुका है। इसने इसे और नीचे कर दिया, लेकिन यह पर्याप्त नहीं था। अंत में, जो अंतिम टुकड़ा गायब था वह लूप काउंटर था। मेरे अत्यधिक उन्नत एल्गोरिथ्म के माध्यम से (पढ़ें: यादृच्छिक परीक्षण और त्रुटि ) मैंने निर्धारित किया कि लूप को चलाने की अनुमति देने के लिए इष्टतम रेंज [3-8] है। लेकिन वहाँ एक बड़ा सुधार है: अगर हम जानते हैं कि [loop(8, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]
हमारे बोर्ड को हल नहीं किया जा सकता है, तो इसका कोई तरीका नहीं है[loop(3, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]
या 3-7 से कोई भी लूप काउंट इसे हल कर सकता है। इसलिए 3-8 से सभी लूप आकारों के माध्यम से पुनरावृत्ति करने के बजाय, हम लूप काउंट को बाहरी लूप पर अधिकतम पर सेट करते हैं। यह maxLoop - minLoop
इस मामले में, या 6 के एक कारक द्वारा खोज स्थान को समाप्त करता है ।
इससे बहुत मदद मिली, लेकिन स्कोर में तेजी आई। कुछ समाधान जो मैंने पहले पाया था कि ब्रूट बल द्वारा चलाने के लिए बड़े लूप संख्या की आवश्यकता होती है (उदाहरण के लिए, बोर्ड 4 और 6)। इसलिए बाहरी लूप काउंट को 8 पर सेट करने के बजाय, हम बाहरी लूप काउंट को 17 पर सेट करते हैं, एक जादुई संख्या जो मेरी अत्यधिक उन्नत एल्गोरिदम द्वारा गणना की जाती है। हम जानते हैं कि हम ऐसा कर सकते हैं क्योंकि बाहरी लूप की लूप गणना बढ़ने से समाधान की वैधता पर कोई प्रभाव नहीं पड़ता है। इस कदम ने वास्तव में हमारे अंतिम स्कोर को 13. घटा दिया है, इसलिए यह एक तुच्छ कदम नहीं है।