기둥과 보 설치
문제
풀이
현석
재영
결과: 하드코딩 후 30%만 맞춤…
뭔가 유효한지를 판단하는 메서드를 보와 기둥 각각 만들어서 계산하는 게 마음 편할 거 같아서 저장!
결과
약 8시간만에 해결! 내 시간…😭
배운 점: private class field
을 이번에 처음 알게되어 써보았습니다!
클래스를 잘 써보지 않았는데, 코테를 하면서 객체지향형 프로그래밍을 조금씩 익혀보려 합니다 😊
// 실패!
class Builder{
constructor(size) {
this._size = size
this.createWorkSpace(size)
}
get workSpace() {
return this._workSpace
}
createWorkSpace(size) {
this._workSpace = new Array(size)
.fill(null)
.map(row => new Array(size).fill(null).map(pos => new Array(2).fill(false)))
}
validateFloor([x, y]) {
return (y - 1 >= 0 && this._workSpace[x][y - 1][0])
|| (x + 1 <= this._size && y -1 >= 0 && this._workSpace[x + 1][y - 1][0])
|| (x- 1 >= 0 && this._workSpace[x - 1][y][1] && x + 1 <= this._size && this._workSpace[x + 1][y][1])
}
validatePillar([x, y]) {
return y === 0
|| (y - 1 >= 0 && this._workSpace[x][y - 1][0] )
|| (x - 1 >= 0 && this._workSpace[x - 1][y][1])
}
validate(type, [x, y]) {
if (x < 0 || y < 0|| x > this._size || y > this._size) return false
if (x === this._size && type === 1) return false
if (type === 1) return this.validateFloor([x,y])
else if (type === 0) return this.validatePillar([x,y])
}
buildFloor(position) {
const [x, y] = position
this._workSpace[x][y][1] = true
}
buildPillar(position) {
const [x, y] = position
this._workSpace[x][y][0] = true
}
build(type, position) {
const valid = this.validate(type, position)
if (!valid) return
if (type === 1) {
this.buildFloor(position)
} else if (type === 0) {
this.buildPillar(position)
}
}
destroy(type, position) {
const [x, y] = position
if (x < 0 || y < 0|| x > this._size || y > this._size) return false
if (x === this._size && type === 1) return false
if (this.testImpact(type, position)) return
console.log(position)
this._workSpace[x][y][type] = false
}
testImpact(type, position) {
const [x,y] = position
this._workSpace[x][y][type] = false
const anothorType = 1 - type
if (this._workSpace[x][y][anothorType] && !this.validate(anothorType, position)) {
this._workSpace[x][y][type] = true
console.log(position, type)
return true
}
const dx = [1, -1, 0, 0]
const dy = [0, 0, -1, 1]
const check = ([x, y]) => {
let result = false
loop:
for (let i=0; i< 4; i++) {
const nx = dx[i] + x
const ny = dy[i] + y
if (nx < 0 || ny < 0 || nx > this._size || ny > this._size) continue
for (let type = 0; type < 2; type++) {
if (!this._workSpace[nx][ny][type]) continue
if (!this.validate(type, [nx, ny])) {
result = true
break loop
}
}
}
this._workSpace[x][y][type] = true
return result
}
return check(position)
}
status() {
const result = []
for (let i = 0; i < this._size; i++) {
for (let j = 0; j < this._size; j++) {
for(let k = 0; k < 2; k++) {
if (!this._workSpace[i][j][k]) continue
result.push([i, j, k])
}
}
}
return result
}
}
function solution(n, build_frame) {
const size = n + 1
const structureType = ['pillar', 'floor']
const builder = new Builder(size)
build_frame.forEach(([x, y, type, method], index) => {
if (method === 1) {
builder.build(type, [x, y])
} else if (method === 0) {
builder.destroy(type, [x,y])
}
})
return builder.status().sort((a,b) => a[0] - b[0] || a[1] - b [1] || a[2] - b[2])
}
class BuildMap {
#PILLAR_CODE = 0;
#FLOOR_CODE = 1;
#COMMAND_ADD_CODE = 1;
#COMMAND_DELETE_CODE = 0;
#BuildTypes = Object.freeze({
[this.#PILLAR_CODE]: '기둥',
[this.#FLOOR_CODE]: '보',
});
#CommandTypes = Object.freeze({
[this.#COMMAND_DELETE_CODE]: '삭제',
[this.#COMMAND_ADD_CODE]: '추가',
});
constructor(n) {
this.mapSize = n + 1;
this.arr = Array.from(
{ length: this.mapSize },
() => Array.from({ length: this.mapSize }, () => [false, false]) // pillar, floor(now -> right)
);
}
checkBuild(x, y, buildType, commandType) {
const commands = {
[this.#CommandTypes[this.#COMMAND_DELETE_CODE]]: {
[this.#BuildTypes[this.#PILLAR_CODE]]:
this.checkDeletePillarPossible.bind(this),
[this.#BuildTypes[this.#FLOOR_CODE]]:
this.checkDeleteFloorPossible.bind(this),
},
[this.#CommandTypes[this.#COMMAND_ADD_CODE]]: {
[this.#BuildTypes[this.#PILLAR_CODE]]:
this.checkAddPillarPossible.bind(this),
[this.#BuildTypes[this.#FLOOR_CODE]]:
this.checkAddFloorPossible.bind(this),
},
};
return commands[commandType][buildType](x, y);
}
checkAddPillarPossible(x, y) {
if (this.arr[x][y][0]) return false;
const isPillarBelowStand = !y || this.arr[x][y - 1][0];
const isFloorStand = x > 0 && (this.arr[x][y][1] || this.arr[x - 1][y][1]);
return !y || isPillarBelowStand || isFloorStand;
}
checkAddFloorPossible(x, y) {
if (this.arr[x][y][1]) return false;
const isPillarStand =
!y ||
this.arr[x][y - 1][0] ||
(x < this.mapSize - 1 && this.arr[x + 1][y - 1][0]);
const isFloorBothSide =
x < this.mapSize - 1 &&
x > 0 &&
this.arr[x - 1][y][1] &&
this.arr[x + 1][y][1];
return isPillarStand || isFloorBothSide;
}
delete(x, y, buildCode) {
this.arr[x][y][buildCode] = false;
}
backupBeforeDelete(x, y, buildCode) {
this.arr[x][y][buildCode] = true;
}
simulateDelete(x, y, buildCode) {
this.delete(x, y, buildCode);
if (buildCode) {
// floor
const isPillarStandPossible =
!this.arr[x][y][0] ||
(y > 0 && this.arr[x][y - 1][0]) || // 바로 밑 기둥
(x > 0 && this.arr[x - 1][y][1]); // 왼쪽 보
const isRightPillarStandPossible =
x === this.mapSize - 1 || // 맨 끝에 있다면 기둥 생성 못하므로 의미 없음.
!this.arr[x + 1][y][0] || // 오른쪽에 기둥이 있는 게 맞는지
this.arr[x + 1][y][1] || // 오른쪽에 보가 이어졌는지
!y || // 바닥인지
this.arr[x + 1][y - 1][0]; // 오른쪽 아래에 기둥이 있는지
const isLeftFloorStandPossible =
!x || // 맨 왼쪽이라면 true
!y || // 맨 아래층이라도 true (그럴리는 없음)
!this.arr[x - 1][y][1] || // 만약 애초부터 계산하지 않아도 된다면 true
this.arr[x][y - 1][0] || // 현재 밑에 기둥이 있는지
this.arr[x - 1][y - 1][0]; // 왼쪽 밑에 기둥이 있는지
// 계산 시 맨 끝이면 의미가 없음.
// 현재 위치 오른쪽 밑에 기둥이 있는지
// 현재 위치 오른쪽 오른쪽 밑에 기둥이 있는지
const isRightFloorStandPossible =
x >= this.mapSize - 2 ||
!this.arr[x + 1][y][1] ||
this.arr[x + 1][y - 1][0] ||
this.arr[x + 2][y - 1][0];
return (
isPillarStandPossible &&
isRightPillarStandPossible &&
isLeftFloorStandPossible &&
isRightFloorStandPossible
);
} else {
// pillar
// 내 위에 기둥이 없거나, 기둥이 보에 받쳐져 있으면 가능.
const overPillarStandPossible =
!this.arr[x][y + 1][0] ||
(x > 0 && this.arr[x][y + 1][1] && this.arr[x - 1][y + 1][1]) ||
(this.arr[x][y + 2][1] && this.arr[x - 1][y + 2][1]);
// 현재 보가 없거나, 지탱할 만한 뭔가가 있어야 한다.
const nowFloorStandPossible = !this.arr[x][y][1] || this.arr[x][y + 1][1];
// 왼쪽 보도 없거나, 버틸 수만 있다면 가능.
const nowLeftFloorStandPossible =
!x || !this.arr[x - 1][y][1] || this.arr[x - 1][y][0];
return (
overPillarStandPossible &&
nowFloorStandPossible &&
nowLeftFloorStandPossible
);
}
}
checkDeletePillarPossible(x, y) {
const result = this.simulateDelete(x, y, 0);
this.backupBeforeDelete(x, y, 0);
return result;
}
checkDeleteFloorPossible(x, y) {
const result = this.simulateDelete(x, y, 1);
this.backupBeforeDelete(x, y, 1);
return result;
}
updateBuildMap(x, y, buildCode, commandCode) {
// 1 = delete => false, 0 = add => true
this.arr[x][y][buildCode] = !!commandCode;
}
render(buildFrame) {
buildFrame.forEach((command) => {
const [x, y, buildCode, commandCode] = command;
if (
this.checkBuild(
x,
y,
this.#BuildTypes[buildCode],
this.#CommandTypes[commandCode]
)
) {
this.updateBuildMap(x, y, buildCode, commandCode);
}
});
}
getFinalBuildMaterials() {
const buildMaterials = [];
for (let i = 0; i < this.mapSize; i += 1) {
for (let j = 0; j < this.mapSize; j += 1) {
for (let buildCode = 0; buildCode < 2; buildCode += 1) {
if (this.arr[i][j][buildCode]) {
buildMaterials.push([i, j, buildCode]);
}
}
}
}
return buildMaterials;
}
}
const solution = (n, buildFrame) => {
const buildMap = new BuildMap(n);
buildMap.render(buildFrame);
return buildMap.getFinalBuildMaterials();
};
class BuildMap {
#PILLAR_CODE = 0;
#FLOOR_CODE = 1;
#COMMAND_ADD_CODE = 1;
#COMMAND_DELETE_CODE = 0;
#BuildTypes = Object.freeze({
[this.#PILLAR_CODE]: '기둥',
[this.#FLOOR_CODE]: '보',
});
#CommandTypes = Object.freeze({
[this.#COMMAND_DELETE_CODE]: '삭제',
[this.#COMMAND_ADD_CODE]: '추가',
});
constructor(n) {
this.mapSize = n + 1;
this.arr = Array.from(
{ length: this.mapSize },
() => Array.from({ length: this.mapSize }, () => [false, false]) // pillar, floor(now -> right)
);
}
checkBuild(x, y, buildType, commandType) {
const commands = {
[this.#CommandTypes[this.#COMMAND_DELETE_CODE]]: {
[this.#BuildTypes[this.#PILLAR_CODE]]:
this.checkDeletePillarPossible.bind(this, x, y),
[this.#BuildTypes[this.#FLOOR_CODE]]:
this.checkDeleteFloorPossible.bind(this, x, y),
},
[this.#CommandTypes[this.#COMMAND_ADD_CODE]]: {
[this.#BuildTypes[this.#PILLAR_CODE]]: this.possibleAdd.bind(
this,
x,
y,
this.#PILLAR_CODE
),
[this.#BuildTypes[this.#FLOOR_CODE]]: this.possibleAdd.bind(
this,
x,
y,
this.#FLOOR_CODE
),
},
};
return commands[commandType][buildType]();
}
isValidPillar(x, y) {
if (x < 0) return false;
if (y < 0) return false;
// 바닥에 설치할 경우
if (y === 0) return true;
// 한쪽 보 끝에 위치할 경우
if (this.arr[x][y][1]) return true;
if (x > 0 && this.arr[x - 1][y][1]) return true;
// 다른 기둥 위에 있을 경우
if (this.arr[x][y - 1][0]) return true;
return false;
}
isValidFloor(x, y) {
if (x < 0 || x >= this.mapSize - 1) return false;
if (y <= 0) return false;
// 한쪽 끝 부분이 기둥 위에 있는 경우
if (this.arr[x][y - 1][this.#PILLAR_CODE]) return true;
if (this.arr[x + 1][y - 1][this.#PILLAR_CODE]) return true;
// 다른 보와 동시에 연결되어 있을 경우
if (
x &&
this.arr[x - 1][y][this.#FLOOR_CODE] &&
this.arr[x + 1][y][this.#FLOOR_CODE]
)
return true;
return false;
}
possibleAdd(x, y, buildTypeCode) {
if (this.arr[x][y][buildTypeCode]) return false;
this.arr[x][y][buildTypeCode] = true;
const result = (buildTypeCode ? this.isValidFloor(x, y) : this.isValidPillar(x, y));
this.arr[x][y][buildTypeCode] = false;
return result;
}
delete(x, y, buildCode) {
this.arr[x][y][buildCode] = false;
}
backupBeforeDelete(x, y, buildCode) {
this.arr[x][y][buildCode] = true;
}
checkDeletePillarPossible(x, y) {
if (!this.arr[x][y][this.#PILLAR_CODE]) return false;
this.delete(x, y, this.#PILLAR_CODE);
// pillar
// 내 위에 기둥이 있을 경우.
const overPillarStandPossible =
y + 1 >= this.mapSize ||
!this.arr[x][y + 1][this.#PILLAR_CODE] ||
this.isValidPillar(x, y + 1);
// 위쪽의 보가 없거나, 지탱할 만한 뭔가가 있어야 한다.
const nowOverFloorStandPossible =
y + 1 >= this.mapSize ||
!this.arr[x][y + 1][1] ||
this.isValidFloor(x, y + 1);
// 왼쪽 보도 없거나, 버틸 수만 있다면 가능.
const nowOverLeftFloorStandPossible =
x - 1 < 0 ||
y + 1 >= this.mapSize ||
!this.arr[x - 1][y + 1][this.#FLOOR_CODE] ||
this.isValidFloor(x - 1, y + 1);
const result =
overPillarStandPossible &&
nowOverFloorStandPossible &&
nowOverLeftFloorStandPossible;
this.backupBeforeDelete(x, y, this.#PILLAR_CODE);
return result;
}
checkDeleteFloorPossible(x, y) {
if (!this.arr[x][y][1]) return false;
this.delete(x, y, this.#FLOOR_CODE);
// floor
// 만약 현재 위치에 기둥이 있다면 유효한지 체크
const isPillarStandPossible =
!this.arr[x][y][this.#PILLAR_CODE] || this.isValidPillar(x, y);
// 현재의 오른쪽에 세워진 기둥도 유효한지 체크.
const isRightPillarStandPossible =
x + 1 >= this.mapSize ||
!this.arr[x + 1][y][this.#PILLAR_CODE] ||
this.isValidPillar(x + 1, y);
// 현재의 왼쪽 보도 유효한지 체크
const isLeftFloorStandPossible =
x - 1 < 0 ||
!this.arr[x - 1][y][this.#FLOOR_CODE] ||
this.isValidFloor(x - 1, y);
// 현재의 오른쪽 보도 유효한지 체크
const isRightFloorStandPossible =
x + 1 >= this.mapSize ||
!this.arr[x + 1][y][this.#FLOOR_CODE] ||
this.isValidFloor(x + 1, y);
const result =
isPillarStandPossible &&
isRightPillarStandPossible &&
isLeftFloorStandPossible &&
isRightFloorStandPossible;
this.backupBeforeDelete(x, y, this.#FLOOR_CODE);
return result;
}
updateBuildMap(x, y, buildCode, commandCode) {
// 0 = delete => false, 1 = add => true
this.arr[x][y][buildCode] = !!commandCode;
}
render(buildFrame) {
buildFrame.forEach((command) => {
const [x, y, buildCode, commandCode] = command;
if (
this.checkBuild(
x,
y,
this.#BuildTypes[buildCode],
this.#CommandTypes[commandCode]
)
) {
this.updateBuildMap(x, y, buildCode, commandCode);
}
});
}
getFinalBuildMaterials() {
const buildMaterials = [];
for (let i = 0; i < this.mapSize; i += 1) {
for (let j = 0; j < this.mapSize; j += 1) {
for (let buildCode = 0; buildCode < 2; buildCode += 1) {
if (this.arr[i][j][buildCode]) {
buildMaterials.push([i, j, buildCode]);
}
}
}
}
return buildMaterials;
}
}
const solution = (n, buildFrame) => {
const buildMap = new BuildMap(n);
buildMap.render(buildFrame);
return buildMap.getFinalBuildMaterials();
};