하루 지나 풀면 더 오래 남습니다. 1~8단원을 마친 다음 날 풀어 보세요. 1~8단원에서 골고루 12문제를 섞었고, 어느 레슨의 문제인지는 정답을 펼쳐야 보입니다. 출력을 쓰는 문제는 빈칸과 줄바꿈까지 종이에 끝까지 쓴 뒤 정답을 펼치세요. 줄 수를 알려 주지 않는 문제는 마지막 줄까지 다 씁니다. 막히면 정답 앞의 힌트를 먼저 펼칩니다. 점수는 매기지 않습니다.
한 번에 다 풀 필요는 없습니다. 문제 1~11은 폰으로도 풀 수 있고(약 30~40분), 끝의 만들어 보기 하나는 컴퓨터에서 Node로 직접 돌려 봅니다.
5번과 11번은 긴 코드입니다. 추적 표(줄 · 바뀐 이름 · 값 · 출력)를 그리며 따라가세요. 배열·객체를 다루는 코드는 배열·객체가 몇 개 있는지, 이름표가 어느 배열·객체에 붙는지 그려 보며 따라가면 덜 틀립니다. 마지막 문제는 오늘 가장 많이 팔린 메뉴를 찾는 코드를 빈 데서 만들어 봅니다. 컴퓨터가 있으면 저장해 돌려 맞춰 보고, 폰으로만 본다면 문제에 적힌 값으로 내 코드를 한 줄씩 따라가 맞춰 봅니다. 실행 결과는 모두 Node 26으로 돌린 것입니다.
문제 1
피아노 학원에 가는 요일을 차례로 돌며 마지막 요일과 모든 요일을 모읍니다. 무엇이 찍힐까요? 두 줄을 글자 그대로 쓰세요.
const days = ['월', '수', '금'];
let last = '';
let all = '';
for (const d of days) {
last = d;
all = all + d;
}
console.log(last);
console.log(all);
힌트
바퀴마다 d · last · all이 어느 값에 붙는지 표로 적어 보세요. 5번 줄과 6번 줄의 오른쪽이 어떻게 다른지 봅니다.
정답
금
월수금- 4번 줄: 반복이 days의 칸을 하나씩 꺼내 d를 그 값에 붙입니다. '월' · '수' · '금' 세 바퀴입니다.
- 5번 줄: 바퀴마다 last를 그 바퀴의 d 값에 다시 붙입니다. 다시 붙이면 옛 값과의 연결은 끊겨, 셋째 바퀴가 끝나면 last는 '금'에만 붙어 있습니다(규칙 3).
- 6번 줄: 지금 all의 값에 d를 이어 붙인 새 글자에 all을 다시 붙입니다. '월' → '월수' → '월수금'입니다.
- 8~9번 줄:
금과월수금을 찍습니다.
흔한 오답은 첫 줄을 월수금으로 쓰거나, 둘째 줄을 월 수 금으로 쓰는 것입니다. 첫 줄 월수금은 last를 값이 쌓이는 상자로 봐서 옛 값이 남는다고 본 것입니다(상자 생각). 값이 쌓이는 것은 6번 줄처럼 옛 값에 이어 붙인 새 값에 다시 붙일 때뿐입니다. 월 수 금은 +가 글자 사이에 빈칸을 넣는다고 본 것입니다. +는 빈칸 없이 잇고, 빈칸은 console.log에 쉼표로 여럿을 넘길 때 들어갑니다.
다시 볼 레슨: 이름표 다시 붙이기와 const · for...of로 배열 돌기 · 합 모으기와 세기
문제 2
운동 앱에서 월요일(mon)과 화요일(tue) 계획을 한 목록으로 시작했다가, 화요일만 새 목록으로 바꿉니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요. 오류로 멈추면 찍힌 줄 다음에 오류 메시지의 …Error: 줄만 써도 됩니다.
const list = [];
const plan = {
mon: list,
tue: list,
};
plan.mon.push('요가');
console.log(plan.tue);
plan.tue = ['수영'];
plan.mon.push('걷기');
console.log(plan.tue);
console.log(list);
힌트
1~5번 줄이 끝났을 때 배열이 몇 개인지, list · plan.mon · plan.tue 세 이름표가 어느 배열에 붙는지 그려 보세요. 8번 줄은 배열 안을 바꾸는지, 이름표 하나를 옮겨 붙이는지 봅니다.
정답
모두 세 줄입니다.
[ '요가' ]
[ '수영' ]
[ '요가', '걷기' ]- 1번 줄: 빈 배열 하나를 만들고 list가 붙습니다.
- 2~5번 줄: plan의 mon 속성과 tue 속성에 list가 가리키는 그 배열을 담습니다. 배열은 복사되지 않아, 배열 하나에 이름표 셋(list · plan.mon · plan.tue)이 붙습니다.
- 6~7번 줄: plan.mon으로 그 배열 끝에 '요가'를 더합니다. plan.tue도 같은 배열이라
[ '요가' ]를 찍습니다. - 8번 줄: 새 배열
['수영']을 만들어 plan.tue만 그 배열로 옮겨 붙입니다. 이제 배열은 둘이고, list와 plan.mon은 첫 배열에 그대로 붙어 있습니다. plan을const로 만들었어도 속성은 다시 붙일 수 있습니다. - 9번 줄: plan.mon으로 첫 배열 끝에 '걷기'를 더합니다.
- 10~11번 줄: plan.tue가 붙은 새 배열
[ '수영' ]과 list가 붙은 첫 배열[ '요가', '걷기' ]를 찍습니다.
흔한 오답은 [] · [ '수영' ] · []로 쓰거나, 둘째 줄을 [ '수영', '걷기' ]로 쓰거나, 8번 줄에서 멈춘다고 쓰는 것입니다. 첫째는 2~5번 줄에서 mon과 tue에 list가 한 벌씩 복사되어 들어간다고 본 것입니다(복사된다는 생각). 둘째는 8번 줄에서 plan.mon도 plan.tue를 따라 새 배열로 옮겨 간다고 본 것입니다(따라 옮겨 간다는 생각). 다시 붙이면 그 이름표 하나만 옮겨 가고, 같은 배열에 붙은 다른 이름표는 그대로입니다(규칙 3). 셋째는 const로 만든 객체는 속성도 바꿀 수 없다고 본 것입니다(const는 얼린다는 생각). const가 막는 것은 plan을 다른 값에 다시 붙이는 일뿐입니다.
다시 볼 레슨: 배열과 이름 둘 · 객체를 넘기고 복사하기 · 이름표 다시 붙이기와 const
문제 3
주문 값을 미리 계산해 둔 뒤 잔 수를 바꿉니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요. 오류로 멈추면 찍힌 줄 다음에 오류 메시지의 …Error: 줄만 써도 됩니다.
const order = { qty: 2 };
let pay = 4000 * order.qty;
order.qty = 3;
console.log(pay);
console.log(4000 * order.qty);
힌트
2번 줄에서 pay가 붙는 것이 식 4000 * order.qty인지, 그때 계산한 값인지 보세요.
정답
모두 두 줄입니다.
8000
12000- 1번 줄: 객체
{ qty: 2 }를 만들고 order가 붙습니다. - 2번 줄:
=는 오른쪽을 그때 한 번 계산합니다(규칙 2).4000 * 2인 8000을 계산해 pay를 8000에 붙입니다. pay가 붙는 것은 식이 아니라 값 8000입니다. - 3번 줄: order가 가리키는 객체의 qty 속성을 3으로 바꿉니다. pay는 8000 그대로입니다.
- 4번 줄:
8000을 찍습니다. - 5번 줄: 이 줄에서 식을 새로 계산해
4000 * 3인12000을 찍습니다.
흔한 오답은 첫 줄을 12000으로 쓰거나, 3번 줄에서 멈춘다고 쓰는 것입니다. 첫 줄 12000은 pay에 식이 저장되어, qty가 바뀌면 pay도 다시 계산된다고 본 것입니다(식이 저장된다는 생각). 바뀐 qty로 값을 얻으려면 5번 줄처럼 식을 다시 계산하거나, 그 값에 pay를 다시 붙입니다. 3번 줄에서 멈춘다고 쓰면 const로 만든 객체는 속성도 바꿀 수 없다고 본 것입니다(const는 얼린다는 생각).
다시 볼 레슨: 변수: let으로 이름 붙이기 · 객체: 이름 붙은 칸 묶음
문제 4
메뉴마다 값표 글자를 만들어 새 배열에 담으려 합니다. 무엇이 찍힐까요? 네 줄을 글자 그대로 쓰세요.
const menu = ['라테', '모카'];
const tags = menu.map((m) => {
console.log(m + ' 4500원');
});
console.log(tags);
console.log(tags.length);
힌트
2~4번 줄의 화살표 함수에 return이 있는지 보고, map이 새 배열의 칸에 무엇을 담는지 떠올려 보세요.
정답
라테 4500원
모카 4500원
[ undefined, undefined ]
2- 2번 줄:
map은 배열의 칸마다 넘긴 함수를 부르고, 그 함수가 돌려준 값을 차례로 담은 새 배열을 돌려줍니다. - 3번 줄: 함수는
라테 4500원과모카 4500원을 찍기만 하고return이 없습니다.return이 없는 함수는undefined를 돌려줍니다(규칙 5). - 2~4번 줄: 그래서 tags는
undefined둘을 담은 새 배열에 붙습니다. 칸 값이undefined여도 칸은 둘입니다. - 5~6번 줄:
[ undefined, undefined ]와2를 찍습니다.
흔한 오답은 셋째 줄을 [ '라테 4500원', '모카 4500원' ]으로 쓰는 것입니다. 함수가 찍은 글자가 새 배열에 담긴다고 본 것입니다(찍는 것과 돌려주는 것이 같다는 생각). 찍은 것은 화면에 보이기만 하고, map이 담는 것은 함수가 return으로 돌려준 값입니다. 3번 줄을 return m + ' 4500원';으로 고치면 그 배열이 찍히고, 값표 두 줄은 찍히지 않습니다. 넷째 줄을 0으로 쓰면 undefined만 든 배열에는 칸이 없다고 본 것입니다.
다시 볼 레슨: map·filter·find · 돌려받은 값 쓰기 · 함수를 넘기기
문제 5
주문마다 잔 수를 담은 배열에서 10잔 이상인 첫 단체 주문을 찾는 함수입니다. 칸을 하나 볼 때마다 찍습니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요.
function firstBig(cups) {
let found = 0;
cups.forEach((c) => {
console.log('확인', c);
if (c >= 10) {
found = c;
return found;
}
});
return found;
}
const today = [3, 12, 7, 20];
console.log(firstBig(today));
힌트
바퀴마다 c · 5번 줄의 조건 · found · 출력을 추적 표에 적습니다. 7번 줄의 return을 만난 바퀴에서 다음에 어느 줄로 가는지 봅니다.
정답
모두 다섯 줄입니다.
확인 3
확인 12
확인 7
확인 20
20바퀴마다 한 줄로 줄인 추적 표입니다. 실행되는 줄마다 한 줄씩 쓰면 22줄입니다.
| 바퀴 | c | c >= 10 | found | 출력 · 다음 |
|---|---|---|---|---|
| 1 | 3 | false | 0 | 확인 3 |
| 2 | 12 | true | 12 | 확인 12 · 7번 줄 return |
| 3 | 7 | false | 12 | 확인 7 |
| 4 | 20 | true | 20 | 확인 20 · 7번 줄 return |
| — | 20 | 10번 줄이 20을 돌려줌 · 20 |
둘째 바퀴의 7번 줄 return은 3번 줄에서 forEach에 넘긴 화살표 함수의 칸만 끝냅니다(규칙 5). forEach는 돌려받은 12를 쓰지 않고 다음 칸 7로 그 함수를 또 부릅니다. map이라면 돌려받은 값을 새 배열에 담지만, forEach는 쓰지 않고 버립니다. 넷째 바퀴에서 found가 20에 다시 붙고, 다 돈 뒤 10번 줄이 그때 found가 붙은 20을 돌려줍니다. 함수 이름이 firstBig이어도 자바스크립트는 이름의 뜻을 모르고 쓴 대로만 실행합니다(규칙 6).
흔한 오답은 확인 3 · 확인 12 · 12 세 줄입니다. 7번 줄의 return이 firstBig까지 끝낸다고 본 것입니다(콜백 안 return이 바깥까지 끝낸다는 생각). 확인 네 줄 다음에 12를 쓰면 바퀴가 이어지는 것은 맞혔지만, 넷째 바퀴에서 found가 20에 다시 붙는 것을 놓친 것입니다. 찾자마자 끝내려면 3번 줄을 for (const c of cups) {로, 9번 줄을 }로 바꿉니다. for...of는 함수가 아니라서 7번 줄의 return이 firstBig의 칸을 끝내고, 확인 3 · 확인 12 · 12가 찍힙니다. 첫 값만 필요하면 today.find((c) => c >= 10)이 12를 돌려줍니다.
다시 볼 레슨: map·filter·find · 함수 만들고 부르기
문제 6
수영 기록 배열을 두 가지로 복사한 뒤, 사본 하나를 바꿉니다. 무엇이 찍힐까요? 세 줄을 글자 그대로 쓰세요.
const swim = [
{ name: '민호', sec: 41 },
{ name: '서연', sec: 38 },
];
const a = [...swim];
const b = structuredClone(swim);
a[0].sec = 39;
a.pop();
console.log(swim.length);
console.log(swim[0].sec);
console.log(b[0].sec);
힌트
5~6번 줄이 끝났을 때 배열이 몇 개, 기록 객체가 몇 개인지 그려 보세요. a의 칸이 가리키는 객체가 swim의 칸이 가리키는 객체와 같은지 봅니다.
정답
2
39
41- 1~4번 줄: 기록 객체 둘을 담은 배열을 만들고 swim이 붙습니다.
- 5번 줄:
[...swim]은 칸을 펼쳐 새 배열을 만듭니다. 새로 만드는 것은 바깥 배열 한 겹뿐이라, a의 두 칸은 swim의 두 칸과 같은 기록 객체를 가리킵니다. - 6번 줄:
structuredClone은 안쪽 객체까지 통째로 새로 만듭니다. b의 칸은 새 기록 객체 둘을 가리킵니다. 이제 배열은 셋, 기록 객체는 넷입니다. - 7번 줄: a의 첫 칸이 가리키는 객체, 곧 swim의 첫 칸과 같은 객체의 sec을 39로 바꿉니다.
- 8번 줄:
pop은 a 배열의 끝 칸만 뺍니다. swim은 다른 배열이라 칸이 둘 그대로입니다. - 9~11번 줄:
2·39·41을 찍습니다.
흔한 오답은 둘째 줄을 41로, 첫 줄을 1로, 셋째 줄을 39로 쓰는 것입니다. 둘째 줄 41은 펼치기가 안쪽 기록 객체까지 새로 만든다고 본 것입니다(복사된다는 생각). 펼치기는 바깥 한 겹만 새로 만들어, a와 swim은 같은 기록 객체를 같이 가리킵니다. 첫 줄 1은 a와 swim이 같은 배열이라고 본 것입니다. 5번 줄이 const a = swim;이었다면 1 · 39 · 41이 찍힙니다. 셋째 줄 39는 structuredClone도 바깥 한 겹만 새로 만든다고 본 것입니다.
다시 볼 레슨: 객체를 넘기고 복사하기 · 객체를 담은 배열 · 배열 바꾸기와 복사
문제 7
도서관 앱이 회원 정보를 화면에 보이기 전에 비밀번호 pw를 뺍니다. 무엇이 찍힐까요? 세 줄을 글자 그대로 쓰세요.
function hide(user) {
const { pw, ...rest } = user;
return rest;
}
const me = {
id: 'mj',
pw: '0815',
};
const shown = hide(me);
shown.id = 'mj2';
console.log(me.id, shown.id);
console.log('pw' in me);
console.log('pw' in shown);
힌트
9번 줄이 끝났을 때 객체가 몇 개인지, me와 shown이 어느 객체에 붙는지 그려 보세요. 2번 줄의 구조 분해가 user에서 pw를 떼어 내는지도 봅니다.
정답
mj mj2
true
false- 9번 줄: hide를 부르면 매개변수 user가 me가 가리키는 객체에 붙습니다.
- 2번 줄: 구조 분해가 pw 속성의 값 '0815'를 꺼내 이름 pw를 그 값에 붙이고,
...rest가 남은 id 속성을 새 객체{ id: 'mj' }에 모읍니다. 꺼내는 것은 값을 읽는 일이라, user가 가리키는 객체에는 pw 속성이 그대로 있습니다. - 3번 줄: 새 객체를 돌려주고, shown이 그 새 객체에 붙습니다. 이제 객체는 둘입니다.
- 10번 줄: shown이 가리키는 새 객체의 id만 'mj2'로 바꿉니다. me의 id는 'mj' 그대로입니다.
- 11번 줄:
mj mj2를 찍습니다. - 12~13번 줄:
in은 그 키의 속성이 있는지 묻습니다. me에는 pw가 있어true, shown에는 없어false입니다.
흔한 오답은 mj2 mj2 · true · true로 쓰거나, 둘째 줄을 false로 쓰는 것입니다. 앞의 것은 rest가 user, 곧 me와 같은 객체를 가리킨다고 본 것입니다. ...rest는 남은 속성을 새 객체에 모읍니다. 3번 줄이 return user;였다면 shown도 me와 같은 객체에 붙어 이렇게 찍힙니다. 둘째 줄 false는 구조 분해가 원래 객체에서 pw를 떼어 낸다고 본 것입니다.
다시 볼 레슨: 단축 속성과 나머지 모으기 · 객체를 넘기고 복사하기 · 객체: 이름 붙은 칸 묶음
문제 8
남은 우유 팩 수로 우유를 주문할지 정하는 함수입니다. 무엇이 찍힐까요? 세 줄을 글자 그대로 쓰세요.
function check(left) {
if (left < 3)
console.log('우유 주문');
return '부족';
return '넉넉';
}
console.log(check(1));
console.log(check(5));
힌트
2번 줄의 if에는 중괄호가 없습니다. 이때 if가 맡는 것이 어디까지인지 떠올려 보고, check(5)를 부를 때 3번 줄과 4번 줄이 실행되는지 따로 봅니다.
정답
우유 주문
부족
부족- 2~3번 줄: if에 중괄호가 없으면 바로 뒤 문장 하나, 곧 3번 줄만 if에 듭니다(규칙 4). 4번 줄은 들여 썼어도 if 밖입니다.
- 7번 줄: check(1)은
1 < 3이 참이라 3번 줄이우유 주문을 찍습니다. 이어 4번 줄의return이 함수의 칸을 끝내고 '부족'을 돌려줘, 7번 줄이부족을 찍습니다. - 8번 줄: check(5)는
5 < 3이 거짓이라 3번 줄만 건너뜁니다. 4번 줄은 if 밖이라 늘 실행되어 '부족'을 돌려줍니다. 5번 줄은 한 번도 실행되지 않습니다.
흔한 오답은 셋째 줄을 넉넉으로 쓰는 것입니다. 들여 쓴 3~4번 줄이 함께 if의 덩어리라고 본 것입니다(들여쓰기가 덩어리라는 생각). 덩어리는 들여쓰기가 아니라 중괄호가 정합니다. 2번 줄 끝에 {를, 4번 줄 아래에 }를 더하면 우유 주문 · 부족 · 넉넉이 찍힙니다.
파이썬에서는 들여쓰기가 덩어리를 정해 3~4번 줄이 함께 if에 듭니다 — 자바스크립트는 중괄호가 덩어리를 정하고, 들여쓰기는 읽기 좋게 할 뿐입니다.
다시 볼 레슨: if와 else · 함수 만들고 부르기
문제 9
앱이 받은 JSON 글에서 잔 수와 값을 꺼내 계산합니다. 무엇이 찍힐까요? 네 줄을 글자 그대로 쓰세요.
const text =
'{"cups":"3","pay":"4500원"}';
const o = JSON.parse(text);
console.log(o.cups + 1);
console.log(o.cups * 2);
console.log(Number(o.pay));
console.log(parseInt(o.pay));
힌트
2번 줄의 JSON 글에서 cups와 pay의 값이 따옴표 안에 있는지 보세요.
힌트 둘
JSON.parse는 글에 적힌 꼴 그대로 값을 만듭니다.
정답
31
6
NaN
4500- 3번 줄:
JSON.parse가 글을 객체로 바꿉니다. JSON 글에서 3과 4500원이 따옴표 안에 있어, o.cups는 글자 '3', o.pay는 글자 '4500원'입니다. - 4번 줄: 글자와 수가
+로 만나면 이어 붙여 '31'이 됩니다. - 5번 줄:
*에는 이어 붙이기가 없어, 글자 '3'을 수 3으로 바꿔 곱하고6을 찍습니다. - 6번 줄:
Number()는 글 전체가 수일 때만 수로 바꿉니다. '원'이 붙어 있어NaN입니다. - 7번 줄:
parseInt()는 앞에서부터 숫자를 읽다가 숫자가 아닌 글자에서 멈춰, '4500원'에서 4500을 얻습니다.
흔한 오답은 첫 줄을 4로, 셋째 줄을 4500으로 쓰는 것입니다. 4는 JSON에서 꺼낸 값은 늘 수라고 보았거나, 글자 '3'도 +에서 수처럼 더해진다고 본 것입니다(문자열과 수를 가리지 않는 생각). Number(o.cups) + 1로 바꾼 뒤 더해야 4가 됩니다. JSON 글에 "cups":3처럼 따옴표 없이 적혀 있었다면 o.cups가 수 3이라 4가 찍힙니다. 셋째 줄 4500은 Number()도 parseInt()처럼 앞의 숫자만 읽는다고 본 것입니다. 이 과목이 Number()를 쓰는 것은 잘못된 글에서 NaN을 내어 표시를 남기기 때문입니다.
파이썬에서는 '3' * 2가 '33'이고 '3' + 1은 오류입니다 — 자바스크립트는 '3' * 2가 6이고, '3' + 1은 오류 없이 '31'입니다.
다시 볼 레슨: JSON: 객체와 글 사이 · 글자를 수로: Number와 NaN
문제 10
장바구니 cart의 메뉴 이름을 차례로 찍으려 합니다. 라테와 쿠키 두 줄만 찍혀야 하는데, 아래처럼 7번 줄에서 멈췄습니다. 몇 번 줄의 무엇을 고쳐야 할까요? 한 줄만 고쳐, 고친 줄을 쓰세요.
const cart = [
{ item: '라테', qty: 2 },
{ item: '쿠키', qty: 1 },
];
const n = cart.length;
for (let i = 0; i <= n; i++) {
console.log(cart[i].item);
}
라테쿠키main.js:7 console.log(cart[i].item); ^ TypeError: Cannot read properties of undefined (reading 'item') at main.js:7:23힌트
오류 메시지의 reading 'item'은 'undefined에서 item을 읽으려 했다'는 뜻입니다. 멈춘 바퀴의 i가 몇인지, cart에 그 자리 번호의 칸이 있는지 적어 보세요.
정답
6번 줄의 <= 연산자를 < 연산자로 고쳐 for (let i = 0; i < n; i++) {로 씁니다.
const cart = [
{ item: '라테', qty: 2 },
{ item: '쿠키', qty: 1 },
];
const n = cart.length;
for (let i = 0; i < n; i++) {
console.log(cart[i].item);
}라테
쿠키cart는 칸이 둘이라 자리 번호는 0과 1이고, n은 2입니다. i <= n이면 i가 0 · 1 · 2일 때 세 바퀴를 돕니다. 셋째 바퀴의 cart[2]는 없는 칸이라 오류 없이 undefined이고, 그 undefined에서 item을 읽으려다 7번 줄에서 TypeError로 멈췄습니다. 앞의 두 바퀴가 라테와 쿠키를 찍은 것과 결과의 at main.js:7:23 줄이 실행 중 오류라는 표시입니다. 멈춘 줄은 7번이지만, 틀린 값 undefined를 꺼내게 만든 것은 6번 줄의 조건입니다. i < n이면 i가 0과 1일 때만 돌아 두 줄을 찍고 끝납니다.
- 6번 줄의
i <= n을i <= n - 1이나i < cart.length로 고쳐도 맞습니다.i < 2처럼 칸 수를 직접 써도 이번에는 같게 찍히지만, cart에 칸을 더하면 따라 바뀌지 않아 n이나cart.length로 씁니다. - 5번 줄을
const n = cart.length - 1;로 고쳐도 같게 찍힙니다. 다만 n이 칸 수가 아니라 마지막 자리 번호가 됩니다. - 7번 줄을
cart[i]?.item으로 고치면 멈추지는 않지만 셋째 줄에undefined가 찍힙니다. 셋째 바퀴는 그대로 돕니다. - 6번 줄의
let i = 0을let i = 1로 고치면쿠키만 찍고 같은 7번 줄에서 멈춥니다. - 6번 줄을
for (const c of cart) {로 바꾸면 7번 줄도c.item으로 고쳐야 해, 두 줄을 고치게 됩니다.
파이썬에서는 cart[2]처럼 칸 밖을 읽으면 바로 IndexError로 멈춥니다 — 자바스크립트는 칸 밖을 오류 없이 undefined로 읽고, 그 값에서 .item을 읽을 때 멈춥니다.
문제 11
카페가 줄 선 손님을 n명씩 부르고, 남은 손님 수를 돌려받습니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요.
function call(queue, n) {
for (let i = 0; i < n; i++) {
console.log(queue.shift());
}
return queue.length;
}
const line =
['민호', '서연', '하준', '지우'];
const left = call(line, 2);
line.push('도윤');
console.log(call(line, left));
console.log(line);
힌트
9번 줄에서 call을 부를 때 배열이 몇 개인지, queue가 어느 배열에 붙는지 그려 보세요. 바퀴마다 i · shift가 꺼낸 값 · 배열에 남은 칸을 추적 표에 적습니다.
정답
모두 여섯 줄입니다.
민호
서연
하준
지우
1
[ '도윤' ]바퀴마다 한 줄로 줄인 추적 표입니다. 실행되는 줄마다 한 줄씩 쓰면 25줄입니다.
| 줄 | i | i < n | 출력 · 다음 | 배열에 남은 칸 |
|---|---|---|---|---|
| 9 | 0 | true | 민호 | 서연 · 하준 · 지우 |
| 9 | 1 | true | 서연 | 하준 · 지우 |
| 9 | 2 | false | 2를 돌려줌 · left는 2 | 하준 · 지우 |
| 10 | 하준 · 지우 · 도윤 | |||
| 11 | 0 | true | 하준 | 지우 · 도윤 |
| 11 | 1 | true | 지우 | 도윤 |
| 11 | 2 | false | 1을 돌려줌 · 1 | 도윤 |
| 12 | [ '도윤' ] | 도윤 |
9번 줄에서 call을 부르면 매개변수 queue가 line이 가리키는 그 배열에 붙습니다. 배열은 복사되지 않아, 3번 줄의 shift가 queue로 빼낸 칸은 line에서도 빠집니다. 첫 부름이 끝나면 배열에는 '하준'과 '지우'만 남고, 5번 줄이 칸 수 2를 돌려줘 left가 2에 붙습니다. 10번 줄이 '도윤'을 더해 칸이 셋이 되고, 11번 줄의 둘째 부름은 n이 left의 값 2라 두 번만 꺼냅니다. '도윤'이 남아 1을 찍고, 12번 줄이 [ '도윤' ]을 찍습니다.
흔한 오답 하나는 셋째 · 넷째 줄을 민호 · 서연으로 다시 쓰고, 이어 3과 다섯 칸 배열을 쓰는 것입니다. 함수에 넘기면 배열이 한 벌 복사되어, 함수 안의 shift가 line에 닿지 않는다고 본 것입니다(복사된다는 생각). 9번 줄과 11번 줄에서 [...line]으로 사본을 넘겼다면 그렇게 찍히고, 마지막 줄은 다섯 이름이 다 든 배열입니다. 다른 하나는 지우 다음에 도윤 · 0 · []을 쓰는 것입니다. 10번 줄에서 배열이 늘면 left도 따라 3이 된다고 본 것입니다(식이 저장된다는 생각). left는 9번 줄이 돌려준 값 2에 붙은 이름이라, 배열이 늘어도 2 그대로입니다(규칙 2).
다시 볼 레슨: 배열 바꾸기와 복사 · 매개변수와 인자 · for의 세 칸
문제 12 (만들어 보기)
이번에는 빈 데서 씁니다. 오늘 팔린 음료를 팔린 차례대로 담은 배열 today에서, 가장 많이 팔린 메뉴와 그 잔 수를 빈칸 하나로 이어 한 줄로 찍습니다. 가장 많이 팔린 메뉴가 여럿이면 그중 today에 먼저 나오는 메뉴를 찍습니다. 이렇게 나와야 합니다.
라테 2
2번 줄 끝에 '모카'를 하나 더해 ['모카', '라테', '라테', '모카']로 바꾸면 이렇게 나와야 합니다.
모카 2
2번 줄을 ['라테', '모카', '라테', '모카']로 바꾸면 라테 2가 나와야 합니다.
아래 칸의 1~2번 줄은 그대로 두고, 그 아래를 씁니다.
const today =
['모카', '라테', '라테'];
힌트
하위 목표 넷으로 나눠 씁니다 — 1. 빈 객체 · 2. 하나씩 꺼내 칸 올리기 · 3. 첫 메뉴를 최고로 두고, 센 객체의 키를 하나씩 꺼내 견주어 바꾸기 · 4. 끝나고 쓰기. 세기 틀과 가장 큰 값 찾기 틀을 차례로 잇습니다.
정답
모범 답입니다.
const today =
['모카', '라테', '라테'];
const sold = {};
for (const item of today) {
const n = sold[item] ?? 0;
sold[item] = n + 1;
}
let top = today[0];
for (const k in sold) {
if (sold[k] > sold[top]) {
top = k;
}
}
console.log(top, sold[top]);라테 23번 줄이 하위 목표 1, 4~7번 줄이 2, 8~13번 줄이 3, 14번 줄이 4입니다. 세기가 끝나면 sold는 { '모카': 1, '라테': 2 }입니다. top은 첫 메뉴 '모카'로 시작하고, 9번 줄의 for...in이 sold의 키를 칸이 생긴 차례대로 꺼냅니다. '라테'의 칸 2가 top인 '모카'의 칸 1보다 커서 top이 '라테'로 옮겨 가고, 14번 줄이 라테 2를 찍습니다.
둘째 시험 값에서는 sold가 { '모카': 2, '라테': 2 }가 됩니다. '라테' 차례에 2 > 2가 거짓이라 top은 '모카' 그대로이고, 모카 2가 찍힙니다. 칸은 처음 본 차례대로 생기고 같을 때는 바꾸지 않으니, 가장 많이 팔린 메뉴가 여럿이면 먼저 나온 메뉴가 남습니다.
- 다르게 써도 되는 곳: top을
''로, 가장 많은 잔 수 most를 0으로 따로 두고sold[k] > most일 때 top과 most를 함께 바꾼 뒤console.log(top, most);로 찍어도 됩니다. 9번 줄에서Object.keys(sold)를for...of로 돌거나, 9번 줄을for (const k of today) {로 바꿔 today를 다시 돌아도 됩니다. 이름은count·best처럼 달라도 됩니다. - 같은 잔 수가 된 메뉴로 나중에 바꾸는 코드는 앞의 두 값을 맞혀도 셋째 시험 값에서
모카 2를 찍습니다. - 흔한 오답:
- 10번 줄을
>=연산자로 쓰면 같은 잔 수인 뒤의 메뉴로 바뀌어, 둘째 시험 값에서라테 2가 찍힙니다. - 세는 반복 안에서
if (sold[item] > sold[top])로 바로 견주면 처음 시험 값은 맞지만, 둘째 시험 값에서라테 2가 찍힙니다. 셋째 바퀴에 '라테'가 2가 되어 top이 옮겨 가고, 넷째 바퀴의 '모카' 2는2 > 2가 거짓이라 되돌아오지 못합니다. 같은 반복 안에서>=연산자로 견주면 두 시험 값은 맞지만, 셋째 시험 값에서 넷째 바퀴에 2가 된 '모카'로 바뀌어모카 2가 찍힙니다. 다 센 뒤에 견줍니다. - today를
for...in으로 돌면 item이 메뉴가 아니라 자리 번호 '0' · '1' · '2'에 붙어, sold가{ '0': 1, '1': 1, '2': 1 }이 되고모카 undefined가 찍힙니다. - sold를
for...of로 돌면 9번 줄에서sold is not iterable이라는TypeError로 멈춥니다. sold[item]대신sold.item으로 쓰면 메뉴가 아니라 이름이 item인 칸 하나를 올려, sold가{ item: 3 }이 되고모카 undefined가 찍힙니다.- top을
''로 시작하고sold[top]과 견주면sold['']이undefined라 견주는 것이 늘 거짓이고, top이 끝까지 빈 문자열로 남습니다. 빈 문자열과undefined가 빈칸으로 이어져, 빈칸 뒤에undefined만 찍힙니다. ?? 0없이sold[item] = sold[item] + 1;로 올리면 칸이 모두NaN이 되고,NaN과 견주는 것도 늘 거짓이라모카 NaN이 찍힙니다.let top = today[0];을 반복 안에 두면 top이 그 반복의 중괄호 안에서만 삽니다. 견주는 반복(9~13번 줄) 안에 두면 14번 줄에서, 세는 반복(4~7번 줄) 안에 두면 10번 줄에서 먼저ReferenceError로 멈춥니다(규칙 4).- 찍는 줄을 반복 안에 두면 키마다 한 줄씩
모카 1과라테 2가 찍힙니다. 다 견준 뒤에 한 번 찍습니다.
- 10번 줄을
파이썬에서는 for item in today:가 리스트의 값을 꺼냅니다 — 자바스크립트 for...in은 배열에서 자리 번호를 꺼내니, 배열의 값은 for...of로, 객체의 키는 for...in으로 꺼냅니다.
다시 볼 레슨: 세기: 메뉴별로 모으기 · 가장 큰 값 찾기 · 객체 돌기
틀린 문제는 그 레슨으로 돌아가 모션 그림과 '자주 틀리는 곳'을 보고, 하루쯤 지난 뒤 그 문제만 다시 풀어 보세요. 그날 바로 다시 풀면 방금 본 정답이 기억나 맞힌 것인지 알기 어렵습니다.