Python 3, 67 simgeleri
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'))
Bu program tek bir giriş kartını test edecek, ancak bu test sürücüsünü daha kullanışlı buluyorum . Her bir tahtayı aynı anda test edecek ve bu çözümü bulmanın ne kadar sürdüğünü yazdıracaktır. Bu kodu makinemde çalıştırdığımda (Intel i7-7700K dört çekirdekli CPU @ 4.20 GHz, 16.0 GB RAM), aşağıdaki çıktıyı alıyorum:
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
Bu son test, dakika sınırlamasının altında zorlukla gıcırdıyor.
Arka fon
Bu, şimdiye kadar cevapladığım en eğlenceli zorluklardan biriydi! Bir av patlaması yaşadım ve işleri kısmak için buluşsal yöntemler arıyordum.
Genel olarak, burada PPCG'de nispeten kolay soruları cevaplama eğilimindeyim. Özellikle dize etiketine düşkünüm çünkü genellikle dillerim için oldukça uygun. Bir gün, yaklaşık iki hafta önce, rozetlerime bakıyordum ve bir için canlanma hiç almadığımı fark ettim rozetine . Bu yüzden cevapsızlara baktımBir şey gözümü yakalayıp yakalamadığını görmek için bu soruyu buldum. Maliyeti ne olursa olsun cevaplamaya karar verdim. Düşündüğümden biraz daha zor oldu, ama sonunda gurur duyduğumu söyleyebileceğim kaba bir cevap aldım. Ancak bu zorluk benim için tamamen norm dışıdır, çünkü genellikle tek bir cevaba bir saatten fazla zaman harcamıyorum. Bu cevap beni 2 haftadan biraz fazla sürdü ve nihayet bu aşamaya gelmek için en az 10+ iş aldı, ancak dikkatli bir şekilde takip etmiyordum.
İlk yineleme saf bir kaba kuvvet çözeltisiydi. Uzunluğa kadar tüm parçacıkları oluşturmak için aşağıdaki kodu kullandımN :
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
Bu noktada, olası her cevabı test etmenin çok yavaş olacağından emindim, bu yüzden çok yavaş kullandım . Testcase 12, bu yaklaşımı kullanarak 3.000 saniyenin biraz üzerinde sürdü. Bu açıkça çok yavaş. Ancak bu bilgileri ve her bilgisayar için kısa çözümleri zorlamak için bir grup bilgisayar döngüsü kullanarak yeni bir model bulabilirim. Bulunan hemen hemen her çözümün genellikle aşağıdaki gibi görüneceğini fark ettim:isSnippetRedundant
daha kısa bir pasajla yazılabilecek parçacıkları filtreliyordum. Örneğin, pasajı vermeyi reddederim ["F", "F", "F"]
çünkü tam olarak aynı etkilerle elde edilebilirdi [Loop(3, ["F"])
, bu yüzden uzunluk-3 parçacığını test ettiğimiz noktaya ulaşırsak, hiçbir uzunluk-3 parçacığının mevcut tahtayı çözemeyeceğini biliyoruz. Bu çok iyi anımsatıcılar kullandı, ama sonuçta waaaay oldu
[<com> loop(n, []) <com>]
her iki taraftaki tek koçanlar isteğe bağlı olacak şekilde birkaç katman derinliğinde yuvalanmıştır. Bu, aşağıdaki gibi çözümler anlamına gelir:
["F", "F", "R", "F", "F", "L", "R", "F", "L"]
asla görünmezdi. Aslında, 3'ten fazla döngü olmayan tokenden oluşan bir dizi asla yoktu. Bunu kullanmanın bir yolu, tüm bunları filtrelemek ve onları test etmekten rahatsız etmemek olacaktır. Ancak onları üretmek hala ihmal edilemeyecek bir zaman alıyordu ve bu gibi milyonlarca parçacıktan süzülmek zamanını neredeyse kesecekti. Bunun yerine, yalnızca bu kalıbı takip eden parçacıklar oluşturmak için kod oluşturucuyu büyük ölçüde yeniden yazdım. Sahte kodda, yeni jeneratör şu genel kalıbı takip eder:
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>]
Bu, en uzun test senaryosunu 140 saniyeye indirdi, bu da saçma bir gelişme. Ama buradan, hala iyileştirmem gereken bazı şeyler vardı. Daha agresif bir şekilde gereksiz / değersiz kodu filtrelemeye ve kodun daha önce test edilip edilmediğini kontrol etmeye başladım. Bu onu daha da azalttı, ama yeterli değildi. Sonunda, eksik olan son parça döngü sayacıydı. Oldukça gelişmiş algoritmam sayesinde (okuma: rastgele deneme ve hata ) Döngülerin çalışmasına izin verecek en uygun aralığın [3-8] olduğunu belirledim. Ancak orada büyük bir gelişme var: Tahtamızı [loop(8, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]
çözemediğini biliyorsanız, kesinlikle[loop(3, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]
veya 3-7 arasındaki herhangi bir döngü sayısı bunu çözebilir. Bu nedenle, 3-8 arasındaki tüm döngü boyutlarını yinelemek yerine, dış döngüdeki döngü sayısını maks. Bu, arama alanını maxLoop - minLoop
bu durumda 6 veya 6 kat azaltmaktadır .
Bu çok yardımcı oldu, ancak skoru şişirdi. Daha önce kaba kuvvetle bulduğum bazı çözümler için daha büyük döngü sayıları gerekir (örneğin, pano 4 ve 6). Dış döngü sayısını 8 olarak ayarlamak yerine, dış döngü sayısını 17 olarak ayarladık, son derece gelişmiş algoritmam tarafından da hesaplanan büyülü bir sayı. Bunu yapabileceğimizi biliyoruz çünkü en dıştaki döngünün döngü sayısının artırılmasının çözümün geçerliliği üzerinde bir etkisi yoktur. Bu adım aslında son puanımızı 13 azalttı. Bu yüzden önemsiz bir adım değil.