My solutions for Advent of Code.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
adventofcode/2020/20.py

265 lines
6.7 KiB

#!/usr/bin/env python
from collections import defaultdict as dd
from aoc import get_input # AoC
import re # regex
import numpy as np
from copy import deepcopy as copy
from sys import setrecursionlimit
setrecursionlimit(20000)
data = get_input(20).strip()
pics = dict()
for genpic in data.split("\n\n"):
foundTitle = None
for i, row in enumerate(genpic.splitlines()):
if(foundTitle == None):
titleCheck = re.match(r"^Tile ([0-9]+):", row)
if(titleCheck != None):
foundTitle = titleCheck[1]
pics[foundTitle] = []
else:
try:
pics[foundTitle].append( list(row) )
except:
pics[foundTitle] = []
pics[foundTitle].append( list(row) )
for pid, pic in pics.items():
pics[pid] = np.asarray(pic)
mapWidth = int(len(pics) ** (1/2))
def compileBorder(row):
out = ""
for char in row:
out += char
return out
def compileSideBorder(pic, xOffset=0):
out = ""
for row in pic:
out += row[xOffset]
return out
# check 4 border locations, up, down, left, right
picBorders = dict()
def genBorders(picid):
pic = pics[picid]
## compile its borders
borders = dict()
borders["U"] = compileBorder(pic[0])
borders["R"] = compileSideBorder(pic, -1)
borders["D"] = compileBorder(pic[-1]) # up: 0, right: 1, down: 2, left: 3
borders["L"] = compileSideBorder(pic, 0)
return borders
for pid, pic in pics.items():
picBorders[pid] = genBorders(pid)
# picmap = dict() # map of the arranged pics
def flip(otherside):
# 0 up, 1 right, 2 down, 3 left
sidething = { # used to rotate as the rotation is allways the opposite relative to the whole system
"U": "D",
"R": "L",
"D": "U",
"L": "R"
}
newotherside = sidething[otherside]
# update the connected tiles rotation
return newotherside
def borderCheck(border, other):
other_rev = other[::-1]
if( border == other or border == other_rev ):
return True, border
else:
return False, other
fitsDict = dd(set)
# fits struc
#
# fits[id] = { "L": (otherid, rotation) }
# where key = "L", "R", "U", "D", "UL", "UR", "DL", "DR"
# otherid is the one that fits
# rotation is the other that fits rotation to be able to fit
def getFits(borders, fits=fitsDict, ignore=[], ignoreBorder=[]):
#print(fits)
#print("\n#", borders, "\n")
ignoreLen = len(ignoreBorder)
borLen = len(borders)
print(ignoreLen, borLen)
if(ignoreLen >= borLen):
#print("goodbye")
return fits # if there is nothing to do then return the result
newborders = copy(borders)
for pid, border in borders.items(): # borders[pid]
#print(f"\nChecking {pid=}")
if( pid in ignore ):
continue
for pos, bor in border.items(): # check each border : borders[pid][pos]
#print(f"##Border {pid=} {pos=} {bor=}")
for pid2, border2 in borders.items(): # check for others borders ; borders[pid2]
if(pid2 == pid or pid2 in ignore or border2 in ignoreBorder):
continue
for pos2, bor2 in border2.items(): # check for other matching border ; borders[pid2][pos2]
check, newBor2 = borderCheck(bor, bor2)
#print(f"####Border2 {pid2=} {pos2=} {bor2=} {newBor2=} {check=}")
if( not check ):
continue
if(check): # if the two borders match:
fits[pid] = fits[pid] or dict()
fits[pid][pos] = (pid2, pos2)
#ignore.append(pid)
ignoreBorder.append(bor2)
fits[pid] = fits[pid] or {"E": None} # It has to end somewhere
return getFits(newborders, fits, ignore) # do the other borders
print(picBorders)
fits = getFits(picBorders, fitsDict)
print("------------")
print(fits)
fitsListKeys = list(fits.keys())
#picmap = [["#" for x in range(mapWidth)] for y in range(mapWidth)] # inp: coord [][] -> pid
picmap = dd(dict)
piccoords = dict() # inp: pid -> out: coord (tuple)
seenPics = []
def translatePos(pos, x, y):
if(pos == "U"):
return x, y-1
elif(pos == "D"):
return x, y+1
elif(pos == "L"):
return x-1, y
elif(pos == "R"):
return x+1, y
def setPos(pid, x, y, pmap, coords, seen=[]):
pmap[y] = pmap[y] or dict()
pmap[y][x] = pid
coords[pid] = (x, y)
seen.append(pid)
return seen
def printMap(pmap):
print(" #---# ")
for y in range(mapWidth, -5, -1):
row = pmap[y]
for x in range(-8, mapWidth):
try:
print( row[x], end=" " )
except:
print("----", end=" ")
print("")
print(" #---# ")
def genTheMap(pmap, coords, seenPID=[], fits=fits):
i = 0
for pid, fit in fits.items():
print("#", pid, fit)
if( i == 0 ):
seenPID = setPos(pid, 0, 0, pmap, coords, seenPID)
print(f"{pid} at 0, 0")
for cpos, child in fit.items():
cx, cy = translatePos(cpos, 0, 0)
print(f"{child[0]} at {cx} {cy}")
seenPID = setPos(child[0], cx, cy, pmap, coords, seenPID)
i += 1
else:
if( pid in seenPID ): # check if pid is in map
# if exists then check around it
print(f"{pid=} : {fit=}")
for cpos, child in fit.items():
if(not child[0] in seenPID):
x, y = coords[pid]
cx, cy = translatePos(cpos, x, y)
print(f"{child=}: {child[0]} at {cx} {cy}")
seenPID = setPos(child[0], cx, cy, pmap, coords, seenPID)
else:
print(f"{pid=} : {fit=}")
for seenp in seenPID: # check if anyone has the PID as a connection
if(seenp == pid): # this should not happen
continue
fit2 = fits[seenp]
for pos, fit in fit2.items():
fid = fit[0]
if( fid == pid ): # found it
pos = flip(pos) # flip it because it is reversed
x, y = coords[seenp] # get the seen ones pos
cx, cy = translatePos(pos, x, y) # get our pos
seenPID = setPos(pid, cx, cy, pmap, coords, seenPID)
# NOTE: LOOP AGAIN IF LOST PIDS
i += 1
if(len(pmap) < len(pics)):
return genTheMap(pmap, coords, seenPID, fits)
else:
return fits
genTheMap(picmap, piccoords)
printMap(picmap)