문제 첫인상 및 정리
아무래도 챌린지 첫 날인만큼 너무 긴장하지 말라는 식으로 순수 JS만을 이용한 문제를 내준 것이 아닐까 싶다. 하지만 베이직때보다는 보다 복잡한 로직이 예상되어 각 기능별로 함수를 만들어 코드의 가독성을 높여야 할 것 같았다.
입력
- 프로그램에서는 표준 입력으로
"시간대-참석인원-회의시간"형태로 반복해서 입력받으며 회의실별로 시간표를 출력하고, 다시 입력받는 것을 반복한다.- 이 부분은 반복해서 입력받는 과정에서의 예외처리가 중요하다고 생각한다. 무조건 배열은 다 돌아야 하기 때문에 예약이 안되는 상황같은 예외들이 많아질 수 있기 때문이다.
- 예약 요청은 문자열 배열로 시간대, 회의시간, 참석 인원이 쌍으로 제공된다.
- 시간대는 오전
AM/ 오후PM중에 하나만 가능하다. - 회의 시간은 1시간부터 4시간까지 가능하다.
- 참석 인원은 2명부터 20명까지만 가능하도록 해야한다.
- 예시)
AM-02-1: 오전 시간대 2명 1시간 /PM-15-2: 오후 시간대 15명 2시간
- 시간대는 오전
회의실
- A → B → C 순으로 예약
- 참석 인원이 회의실 인원을 초과하면 예약할 수 없다.
- 오전이나 오후 4칸이 원하는 시간만큼 남지 않으면 더 이상 회의를 넣을 수 없으며 예약은 실패처리
A회의실
- 참석가능인원 : 5
B회의실
- 참석가능인원 : 10
C회의실
-
참석가능인원 : 20
-
함수를 사용하며, 별도 타입을 선언하지 않아도 된다.
-
함수 하나가 10줄 이상 넘어가면 하위 함수로 분리하고, 상위 함수는 하위 함수를 호출한다.
-
함수 선언 이후에 들여쓰기 단계가 3단계를 초과하는 경우는 함수로 분리한다.
-
입력값을 받기 위해서 표준 입력 방식을 사용해도 되고, 소스코드 파일 안에서 함수를 호출하면서 입력값을 넣어도 된다.
-
결과는 VS Code 콘솔에 출력되어야 한다.
-
최종적으로 콘솔에 출력한 결과를 이미지로 캡처한다.
-
요구사항을 분석해서 스스로 할 일 체크리스트를 README.md에 작성한다.
-
체크리스트를 하나씩 완료할 때마다 소스와 실행 결과는 gist에 commit 한다. (Push는 한 번만 해도 무방하다.)
체크리스트
- 회의실 데이터 구조 설계
- 회의실 현황 출력 구조 설계
- 함수 분리해서 설계
- 분리한 함수가 10줄 이상 넘어가면 하위 함수로 바꾸기
- 들여쓰기 단계가 3단계를 초과하는 경우 함수로 분리
- 코드 합쳐 구현하기
회의실 데이터 구조 설계
회의실 데이터의 구조는 Object로 설정하면 어떨까 싶었다. 어차피 한 시간이 검정색 사각형 두 칸을 똑같이 차지하고, 만약 예약 대기가 생길 경우 이러한 예약들도 기록해놓은 다음 대기중인 예약을 보여줘야 할 기능이 있어야 하기 때문에 이러한 요소들을 console.log할 때는 보다 구조화된 데이터야 출력하기 쉬워질 수 있을 것 같았다.
function meetingRoom() {
let reservation = {
nowAmReservation: [],
nowPmReservation: [],
otherReservation: { am: [], pm: [] },
};
let A = { person: 5, ...reservation };
let B = { person: 10, ...reservation };
let C = { person: 20, ...reservation };
}spread연산자를 사용한 이유는 함수 내에 10줄 이상 되지 않아야 한다는 조건이 붙었기 때문에 최대한 코드 수를 줄이고자 이렇게 설계했다.
…
라고 생각하고 나서 회의실 현황 출력 구조를 설계하는데 있어서 이름 속성이 따로 있다면 출력하기 더 편하겠다는 생각이 생겨 이름 속성을 각 미팅룸에 추가하였다.
추가적으로 어차피 출력 구조에서는 예약 현황만 보여주면 되기 때문에 시간만 필요하므로 굳이 오전과 오후 시간을 각각 배열에 넣고 배열을 돌기보다는 시간만 기록하는 것이 더 효율적이겠다 생각했다.
function meetingRoom() {
let reservation = {
nowAmReservation: 0,
nowPmReservation: 0,
otherReservation: { am: 0, pm: 0 },
};
let A = { name: "A", person: 5, ...reservation };
let B = { name: "B", person: 10, ...reservation };
let C = { name: "C", person: 20, ...reservation };
}회의실 현황 출력 구조 설계
구상한 데이터 구조를 통해 회의실 현황 구조를 함수 내부의 함수 실행을 통해 출력하려고 한다.
출력 예시 분석
|오|전|시|간||오|후|시|간|
----------------------------------
회의실 A|🁢🁢|🁢🁢|🁢🁢| ||🁢🁢| | | |
예약대기|🁢🁢|🁢🁢| | || | | | |
----------------------------------
회의실 B|🁢🁢|🁢🁢| | ||🁢🁢|🁢🁢| | |
예약대기| | | | ||🁢🁢|🁢🁢|🁢🁢| |
----------------------------------
회의실 C| | | | ||🁢🁢| | | |
예약대기| | | | || | | | |
----------------------------------
- 보면 가장 위의 오전시간과 오후시간 앞 공간은 공백 8칸으로, \t 을 통해 따로 공백을 들이지 않아도 간격을 똑같이 벌릴 수 있다.
- 오전시간과 오후시간의 경우
|오|전|시|간|과|오|후|시|간이 각각 붙어있는 것과 같은 형태로 보인다. 그렇기에 나중에 회의실 현황을 입력할 때도 오전시간과 오후 시간을 따로 처리하여 문자열로 붙이는 것이 괜찮을 것이라 판단된다. - 각 예약 현황을 보여주는 박스의 경우 배열에 시간의 수 만큼 블록을 만들었다.
join하여 사이에 벽을 세워준 후 양쪽에도 |를 붙여 하나의 문자열로 만들고 출력하는 것이 보다 편해보였다. - 예약대기의 경우도 똑같이 설계하여 보여주되, 예약 대기의 경우 여러개가 있으면 무슨 웨이팅 대기줄마냥 너무 많아질 수 있기 때문에 예약 대기는 바로 다음에 이용 가능한 시간정도만 받고 이후의 예약 건에 대해서는 예외처리를 해주는 것이 낫다고 판단했다.
출력 관련 함수 설계
print()
const print = () => {
console.log("\t|오|전|시|간||오|후|시|간|");
console.log("----------------------------------");
printReservation(A);
console.log("----------------------------------");
printReservation(B);
console.log("----------------------------------");
printReservation(C);
};
print();print 함수 자체는 meetingRoom()함수 내에서 출력해야 해당 객체를 안에서 넘길 수 있으므로 내부함수로 구현하였다. 오전시간과 오후시간 구분같은 경우 위에서 말한 대로 \t만큼의 공백만 앞에 가져가고 구분을 직접 넣어주었다.
현재 회의실 현황 출력함수 printReservation()
const printReservation = (room) => {
console.log(
printPresentReservation(
room.name,
room.nowAmReservation,
room.nowPmReservation
)
);
printOtherReservation(room.otherReservation.am || room.otherReservation.pm) &&
console.log(printOtherReservation(room.otherReservation));
};printReservation은 각 회의실의 객체를 받아 오전시간, 오후시간에 대한 예약 현황을 예약 대기와 현재 현황 모두 출력시켜주는 함수이다. 예약대기의 경우에는 예약이 없을 경우 출력시킬 필요가 없으므로 논리연산자를 통해 조건부로 출렧킬 수 있도록 하였다.
금일 예약된 회의실 출력함수 printPresentReservation()
const printPresentReservation = (name, am, pm) => {
return `회의실${name} ${makeBlockString(am)}${makeBlockString(pm)}`;
};금일 예약된 회의실을 출력하는 함수의 경우 name,am,pm의 parameter를 받아 문자열을 받도록 하여 상위함수에서 출력하도록 하였다.
여기서 makeBlockString()은 각 시간대의 블록만큼 채워서 문자열로 리턴해주는 함수이다.
시간 → 블록이 찬 문자열로 바꿔주는 함수 makeBlockString()
const makeBlockString = (rooms) => {
const blank = "| | | | |";
const filledRoom = "🁢🁢";
const blankRoom = " ";
if (!rooms || rooms < 0 || rooms > 4) return blank; // Added bounds checking for `rooms`
let result = "|";
for (let i = 0; i < 4; i++) {
result += (i < rooms ? filledRoom : blankRoom) + "|";
}
return result;
};각 4시간의 블록들을 각각 시간에 따라 블록을 채운 문자열을 반환하는 함수이다. 처음에 해당 함수를 만들 때 여러가지 방법을 생각했다.
- 배열의 활용
- 블록으로 배열을 채운 후 join하는 방식을 생각했었지만 공백의 처리가 보다 길어질 것 같아 다른 방식을 물색했다.
- 문자열의 슬라이싱
blank와filled변수에 각각 모두 빈, 찬 타임테이블을 할당하고 각각의 변수를 일정 인덱스만큼 슬라이싱 하여 붙이는 방법을 생각했다. 하지만 여기서 인덱스로 접근을 했을 때 다중 바이트 문자열인 블록때문에 인덱스에 맞는 문자열이 나오지 않았다.
- 직접 하나씩 문자열을 더하는 방법
- 사실 가장 정석적으로 무난한 방법이라고 생각된다^^,,,
다른 방법을 사용했을 때보다 직접 하나씩 문자열을 더하는 방법이 가장 안전하고 출력 결과를 직관적으로 예상할 수 있어 문자열을 더하는 방식을 선택하였다.
예약 대기 현황 출력함수 printOtherReservation()
const printOtherReservation = (reservation) => {
if (reservation.am || reservation.pm) {
return `예약대기${makeBlockString(reservation.am)}${makeBlockString(
reservation.pm
)}`;
}
};예약 대기 현황 출력해주는 함수의 경우도 똑같이 시간만큼 찬 블록을 문자열로 리턴해주는 함수를 사용하여 전체 문자열을 합친 문자열을 리턴시켰다. 특별한 점이라고 한다면 예약 대기가 오전, 오후 둘 다 없을 경우 따로 출력시켜야 하지 않아야 하기에 조건에 맞는 경우만 리턴시켜 리턴값이 undefined인 경우엔 출력시키지 않도록 하였다.
기타 함수 분리해서 설계
예외 케이스 검출하는 checkException()
const checkException = (item) => {
let [timeZone, person, time] = item.split("-");
person = parseInt(person);
time = parseInt(time);
if (timeZone.toLowerCase() !== "am" && timeZone.toLowerCase() !== "pm")
return false;
if (person < 2 || person > 20) return false;
if (time < 1 || time > 4) return false;
return true;
};checkException 함수는 각 예외 케이스에 대해 검출하는 로직을 가지고 있다. 생길 수 있는 예외는
- 시간대(am&pm)을 잘못 썼을 때
- 인원 수가 어떤 회의실에서도 받아줄 수 없을 때
- 시간이 1 미만 또는 5 이상으로 넣을 수 없는 시간일 때 가 있다.
해당하는 방을 배정하는 distributeRoom()
const distributeRoom = (people) => {
if (people <= 5) {
return 0;
} else if (people <= 10) {
return 1;
} else if (people <= 20) {
return 2;
}
};사람 수가 들어갈 수 있는 조건만 맞다면 A→B→C 순으로 들어가라고 나와있었기 때문에 A,B,C 회의실을 배열에 넣고 돌려 처음으로 조건에 맞는 곳에 넣으려고 했지만 마스터 JK님이 A: 05, B: 5 10, C: 11~20 명으로 생각하라고 하셨다. 사실 현실적으로 보면 대부분 공간이 너무 많이 남는 곳은 빌려주지 않으니 보다 현실주의적인 로직이라고 생각한다.
아무튼 그래서 각 회의실이 어떤 인덱스를 가지는지 알기 때문에 배열로 들어간 회의실 안에 접근할 수 있도록 인덱스 번호를 리턴하였다.
이 외의 경우는 어차피 범위를 넘어가므로 그 전에 예외처리에서 걸러졌기 때문에 상관이 없다.
배정한 방 예약 처리하는 putPeopleInRoom()
const putPeopleInRoom = (roomArr, timeZone, time, people) => {
let roomName = distributeRoom(people);
if (roomArr[roomName][timeZone] + time < 5) {
roomArr[roomName][timeZone] += time;
} else if (roomArr[roomName].otherReservation[timeZone] + time < 5) {
roomArr[roomName].otherReservation[timeZone] += time;
}
};지금 보았는데 변수명이 오해의 소지가 있다. 사람의 수는 조건만 검증하는데 쓰이고 그냥 예약 시간을 추가하는 함수이다.
구현하는 과정에서 추가 및 변경된 것들
얇은 복사에 의한 에러 개선
function meetingRoom() {
let reservation = {
nowAmReservation: 0,
nowPmReservation: 0,
otherReservation: { am: 0, pm: 0 },
};
let A = { name: "A", person: 5, ...reservation };
let B = { name: "B", person: 10, ...reservation };
let C = { name: "C", person: 20, ...reservation };
}이전에 스프레드 연산자를 사용하여 다른 예약대기 객체를 함께 복사했었는데, 여기서 문제는 복사가 얇은 복사가 되어 참조값이 공유된다는 점이었다. 이 때문에 예약대기가 모두 같게 나오는 오류가 있었다. 이 때문에 서로 참조값을 공유하지 않은 객체들을 가질 수 있도록 만들어야 했는데, 나같은 경우는 따로 복사를 하기보단 새롭게 객체를 만드는 함수를 썼다.
function makeRoom(name) {
return {
name: name,
am: 0,
pm: 0,
otherReservation: { am: 0, pm: 0 },
};
}
function meetingRoom() {
let A = makeRoom("A");
let B = makeRoom("B");
let C = makeRoom("C");
let roomArr = [A, B, C];
...이하생략
}이를 통해 함수의 라인을 조금 줄일 수 있었고, 보다 가독성이 좋아졌다고 생각한다.
readline 모듈을 통한 input 받기
function inputInterface() {
const regex = /^([AP]M)-(0[1-9]|1[0-9]|2[0-9])-([1-5])$/;
const inputs = [];
const question = () => {
rl.question(
"[AM/PM]-00(인원)-0(시간, 5시간 미만)의 순서대로 시간대-참석인원-회의시간을 적어주세요. \n끝내시려면 q를 입력하세요.",
(line) => {
if (line === "q") input(inputs);
else if (regex.test(line)) {
inputs.push(line);
question();
} else {
console.log(
"입력 형식에 맞지 않습니다.다시 입력해주세요. \n 끝내시려면 q를 입력하세요."
);
question();
}
}
);
};
question();
}원래는 일반 배열을 함수의 parameter에 넣는 방식으로만 구현했었는데, 후에 체크포인트를 확인하고 입력을 따로 받을 인터페이스가 체크포인트에 들어있어 추가해야 할 필요성을 느꼈다.
이에 inputInterface()함수를 만들어 readline 모듈을 사용한 질문 입력 로직을 구상했다.
질문 입력의 경우
- 질문을 출력하면서 입력 받기
- 정규표현식을 통해 받은 입력을 테스트
- 테스트 통과 시 나중에 함수에 파라미터로 넣을 배열에
push - 테스트를 통과하지 못했을 시 다시금 입력 형식에 맞게 입력하도록 출력
- 테스트 통과 시 나중에 함수에 파라미터로 넣을 배열에
- 이러한 입력은 q버튼을 누를 때까지 q를 입력한 경우를 제외하고 다시금 qustion 함수를 실행시켜 무한반복되도록 설계하였다.
코드 합쳐 구현
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function makeRoom(name) {
return {
name: name,
am: 0,
pm: 0,
otherReservation: { am: 0, pm: 0 },
};
}
function meetingRoom() {
let A = makeRoom("A");
let B = makeRoom("B");
let C = makeRoom("C");
let roomArr = [A, B, C];
const print = () => {
let waitingReservation;
console.log("\t|오|전|시|간||오|후|시|간|");
console.log("----------------------------------");
waitingReservation = printReservation(A, waitingReservation);
console.log("----------------------------------");
waitingReservation = printReservation(B, waitingReservation);
console.log("----------------------------------");
waitingReservation = printReservation(C, waitingReservation);
};
const input = (item) => {
if (checkException(item)) {
let [timeZone, person, time] = item.split("-");
person = parseInt(person);
time = parseInt(time);
putPeopleInRoom(roomArr, timeZone.toLowerCase(), time, person);
}
print();
};
function inputInterface() {
const regex = /^([AP]M)-(0[1-9]|1[0-9]|2[0-9])-([1-5])$/;
const inputs = [];
const question = () => {
rl.question(
"[AM/PM]-00(인원)-0(시간, 5시간 미만)의 순서대로 시간대-참석인원-회의시간을 적어주세요. \n끝내시려면 q를 입력하세요.",
(line) => {
if (line === "q") {
print();
process.exit();
} else if (regex.test(line)) {
input(line);
question();
} else {
console.log(
"입력 형식에 맞지 않습니다.다시 입력해주세요. \n 끝내시려면 q를 입력하세요."
);
print();
question();
}
}
);
};
question();
}
inputInterface();
}
const printReservation = (room, waitingReservation) => {
console.log(printPresentReservation(room.name, room.am, room.pm));
if (
room.otherReservation.am ||
room.otherReservation.pm ||
waitingReservation
) {
console.log(printOtherReservation(room.otherReservation));
return true;
}
};
const printPresentReservation = (name, am, pm) => {
return `회의실${name} ${makeBlockString(am)}${makeBlockString(pm)}`;
};
const printOtherReservation = (reservation) => {
return `예약대기${makeBlockString(reservation.am)}${makeBlockString(
reservation.pm
)}`;
};
const makeBlockString = (rooms) => {
const blank = "| | | | |";
const filledRoom = "🁢🁢";
const blankRoom = " ";
if (!rooms || rooms < 0 || rooms > 4) return blank;
let result = "|";
for (let i = 0; i < 4; i++) {
result += (i < rooms ? filledRoom : blankRoom) + "|";
}
return result;
};
const checkException = (item) => {
let [timeZone, person, time] = item.split("-");
person = parseInt(person);
time = parseInt(time);
if (timeZone.toLowerCase() !== "am" && timeZone.toLowerCase() !== "pm")
return false;
if (person < 2 || person > 20) return false;
if (time < 1 || time > 4) return false;
return true;
};
const putPeopleInRoom = (roomArr, timeZone, time, people) => {
let roomName = distributeRoom(people);
if (roomArr[roomName][timeZone] + time < 5) {
roomArr[roomName][timeZone] += time;
} else if (roomArr[roomName].otherReservation[timeZone] + time < 5) {
roomArr[roomName].otherReservation[timeZone] += time;
}
};
const distributeRoom = (people) => {
if (people <= 5) {
return 0;
} else if (people <= 10) {
return 1;
} else if (people <= 20) {
return 2;
}
};
meetingRoom();
구현된 함수들을 조합하여 완성된 형태가 되었다.
회고
이전에는 하나의 함수에 우겨넣는 코드를 짰었는데, 각 함수가 10줄 미만이라는 제한을 가지고 코드를 짜다보니 훨씬 오랜시간이 걸렸지만, 그만큼 설계를 하면서 설계에 대한 감각이 보다 늘은 것 같아 의미 있는 경험이었다.