let formatedData = data
.split('\n')
.map(d => d.split(''))
// get all surrounding positions given a position [row,col]
let surroundingPos = (pos) => (
[[pos[0]-1,pos[1]-1], [pos[0]-1,pos[1]], [pos[0]-1,pos[1]+1],
[pos[0] ,pos[1]-1], [pos[0] ,pos[1]+1],
[pos[0]+1,pos[1]-1], [pos[0]+1,pos[1]], [pos[0]+1,pos[1]+1]]
)
// get all surrounding cells given a table of seats and the position from the table [row,col]
let surroundingCells = (list) => (pos) => (
surroundingPos(pos)
.map(p => typeof list[p[0]] !== 'undefined' ? list[p[0]][p[1]] : undefined)
.filter(p => typeof p !== 'undefined')
)
// get first visible seat given a list, a position from the list and a direction
let firstSeatVisible = (list) => (pos) => (direction) => {
if (typeof list[pos[0]+direction[0]] === 'undefined') return undefined
if (typeof list[pos[0]+direction[0]][pos[1]+direction[1]] === 'undefined') return undefined
if (list[pos[0]+direction[0]][pos[1]+direction[1]] === 'L') return [pos[0]+direction[0],pos[1]+direction[1]]
return firstSeatVisible(list)([pos[0]+direction[0],pos[1]+direction[1]])(direction)
}
// get the positions of all the visible seats from the point of view of a given seat
let visiblePos = (list) => (pos) => (
[[-1,-1], [-1,0], [-1,+1],
[0,-1], [0,+1],
[1,-1], [+1,0], [+1,+1]]
.map(d => firstSeatVisible(list)(pos)(d))
.filter(p => typeof p !== 'undefined')
)
// create a table of all the visible seats from the point of view of each seat of the table
// so at a given position of this new table correspond all the visible seats
// from the point of view of the seat at same position in first table
let cacheVisibleSeatsByPos = formatedData
.map((row,rowIndex,list) => row.map((cell,colIndex) => visiblePos(list)([rowIndex,colIndex])))
// get all the visible seats from the point of view of the seat at given pos
// the cache is expected in this function
let visibleCellUseCache = (listVisiblePos) => (list) => (pos) => (
listVisiblePos[pos[0]][pos[1]].map(visiblePos => list[visiblePos[0]][visiblePos[1]])
)
// same function as above but this function has already the cache and expect only the two remaining params
let visibleCell = visibleCellUseCache(cacheVisibleSeatsByPos)
// Play a round, needs the a function getSeatsFunction to know which method is used to find the seats around each seat
// If the number of occupied seat around each site is greater than the tolerance this seat will be empty
// Return the status of the table after a round is played
let playRoundGeneric = (getSeatsFunction) => (tolerance) => (list) => (
list
.map((row,rowIndex, list) => (
row.map((cell, colIndex) => {
if (cell === '.') return cell
let nbSeatsOccupied = getSeatsFunction(list)([rowIndex,colIndex]).filter(d => d === '#').length
if (cell === 'L') {
return nbSeatsOccupied > 0 ? 'L' : '#'
} else {// cell === '#') {
return nbSeatsOccupied >= tolerance ? 'L' : '#'
}
})
)))
// This function plays a round, test the surrounding and have a tolerance of 4
let playRoundSurrounding = playRoundGeneric(surroundingCells)(4)
// This function plays a round, test the visible seats and have a tolerance of 5
let playRoundVisible = playRoundGeneric(visibleCell)(5)
// Play rounds until all is done, this function is generic and expect a "round" function to be given as parameter
let playUntilStableGeneric = (playFunction) => (previous) => (current) => {
if (JSON.stringify(previous) === JSON.stringify(current)) return current
return playUntilStableGeneric(playFunction)(current)(playFunction(current))
}
let result1 = playUntilStableGeneric(playRoundSurrounding)([])(formatedData)
let result2 = playUntilStableGeneric(playRoundVisible)([])(formatedData)
console.log(result1.map(row => row.filter(cell => cell === '#')).flat().length)
console.log(result2.map(row => row.filter(cell => cell === '#')).flat().length)