모던 JavaScript 튜토리얼 - while과 for 반복문

crewd·2021년 1월 12일
1
post-thumbnail

모던 자바스크립트 튜토리얼

ko.javascript.info

while과 for 반복문

상품 목록에서 상품을 차례대로 출력하거나 숫자를 1부터 10까지 하나씩 증가시키면서 동일한 코드를 반복 실행해야 하는 경우 등 개발을 하다 보면 여러 동작을 반복해야 하는 경우가 종종 생긴다.

반복문(loop) 을 사용하면 동일한 코드를 여러 번 반복할 수 있다.

'while'의 반복문

while 반복문의 문법은 다음과 같다.

while(condition) {
  // 코드
  // '반복문 본문(body)'이라 불림
}

condition(조건)이 truthy면 반복문 본문의 코드가 실행된다.
아래 반복문은 조건 i < 3을 만족할 동안 i를 출력해준다.

let i = 0;
while(i < 3) { // 0, 1, 2가 출력
  alert( i );
  i++
}

반복문 본문이 한 번 실행되는 것을 반복(iteration, 이터레이션) 이라고 부른다.
위 예시에서는 반복문이 세 번의 이터레이션을 만든다.

i++가 없었다면 이론적으로 반복문은 영원히 반복 되었을 것이다. 그런데 브랑줘는 이런 무한 반복을 멈추게 해주는 실질적인 수단을 제공한다. 서버사이드 자바스크립트도 이런 수단을 제공해 주므로 무한 반복되는 프로세스를 죽일 수 있다.

반복문 조건엔 비교뿐만 아니라 모든 종류의 표현식, 변수가 올 수 있다. 조건은 while에 의해 평가되고 평가 후엔 불린값으로 변경된다.

아래 예시에서는 while( i != 0 )을 짧게 줄여 while( i )로 만들어보았다.

let i = 3;
while(i) { // i가 0이 되면 조건이 falsy가 되므로 반복문이 멈춘다.
  alert(i);
  i--;
}

✔ 본문이 한 줄이면 대괄호를 쓰지 않아도 된다.
반복문 본문이 한 줄짜리 문이라면 대괄호 {...}를 생략할 수 있다.

let i = 3;
while(i) alert(i--);

'do...while' 반복문

do..while 문법을 사용하면 condition을 반복문 아래로 옮길 수 있다.

do {
  // 반복문 본문
} while(condition);

이때 본문이 먼저 실행되고, 조건을 확인한 후 조건이 truthy인 동안엔 본문이 계속 실행된다.

let i = 0;
do {
  alert(i);
  i++;
} while(i < 3);

do...while 문법은 조건이 truthy인지 아닌지에 상관없이, 본문을 최소한 한번이라도 실행하고 싶을 때만 사용해야 한다. 대다수의 상황에서는 do..while보다 while이 적합하다.

'for' 반복문

for반복문은 while반복문 보다는 복잡하지만 가장 많이 쓰이는 반복문이다.

for(begin; condition; step) {
  // ... 반복문 본문 ...
}

for문을 구성하는 각 요 소가 무엇을 의미하는지 알아보자 아래 반복문을 실행하면 i0부터 3이 될 때까지(단, 3은 포함하지 않음) alert(i)가 호출된다.

for(let i = 0; i < 3; i++) {
  alert(i);
}

이제 for문의 구성 요소를 하나씩 살펴보자.

일반적인 반복문 알고리즘은 다음과 같다.

begin을 실행함
→ (condition이 truthy이면 → body를 실행한 후, step을 실행함)
→ (condition이 truthy이면 → body를 실행한 후, step을 실행함)
→ (condition이 truthy이면 → body를 실행한 후, step을 실행함)
→ ...

begin이 한 차례 실행된 이후에, condition 확인과 body, step이 계속 반복 실행된다.

반복문을 처음 배운다면, 위 예시를 실행했을 때 어떤 과정을 거쳐 얼럿 창이 출력되는지 종이에 적어가며 공부해보면 반복문을 쉽게 이해할 수 있다.

정확히 어떤 과정을 거치는지는 아래 예시에서 확인해보자

// for (let i = 0; i < 3; i++) alert(i)

// begin을 실행함
let i = 0
// condition이 truthy이면 → body를 실행한 후, step을 실행함
if (i < 3) { alert(i); i++ }
// condition이 truthy이면 → body를 실행한 후, step을 실행함
if (i < 3) { alert(i); i++ }
// condition이 truthy이면 → body를 실행한 후, step을 실행함
if (i < 3) { alert(i); i++ }
// i == 3이므로 반복문 종료

✔ 인라인 변수 선언
지금까진 '카운터'변수 i를 반복문안에 선언했다. 이런 방식을 '인라인' 변수 선언이라고 부른다.
이렇게 선언한 변수는 반복문 안에서만 접근할 수 있다.

for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // Error: i is not defined

인라인 변수 선언 대신, 정의되어있는 변수를 사용할 수도 있다.

let i = 0;

for (i = 0; i < 3; i++) { // 기존에 정의된 변수 사용
  alert(i); // 0, 1, 2
}

alert(i); // 3, 반복문 밖에서 선언한 변수이므로 사용할 수 있음

구성요소 생략하기

for문의 구성요소를 생략하는 것도 가능하다.
반복문이 시작될 때 아무것도 할 필요가 없으면 begin을 생략하는 것이 가능하다.

let i = 0; // i를 선언하고 값 할당

for(; i < 3; i++) { // 'begin'이 필요하지 않기 때문에 생략
  alert(i); // 0, 1, 2
}

step역시 생략 할 수 있다.

let i = 0;

for (; i < 3;) {
  alert( i++ );
}

위와 같이 for문을 구성하면 while(i < 3)과 동일해진다.
모든 구성 요소를 생략할 수도 있는데, 이렇게 되면 무한 반복문이 만들어진다.

for(;;) {
  // 본문 무한 반복
}

for문의 구성요소를 생략할때 주의할 점은 두 개의 ; 세미콜론을 꼭 넣어주어야 한다는 점이다. 하나라도 없으면 문법 에러가 발생한다.

반복문 빠져나오기

대개는 반복문의 조건이 falsy가 되면 반복문이 종료된다.

그런데 특별한 지시자인 break를 사용하면 언제든 원하는 때에 반복문을 빠져나올 수 있다.

아래 예시의 반복문은 사용자에게 일련의 숫자를 입력하도록 안내하고, 사용자가 아무런 값도 입력하지 않으면 반복문을 '종료’한다.

let sum = 0;

while (true) {

  let value = +prompt("숫자를 입력하세요.", '');

  if (!value) break; // (*)

  sum += value;

}
alert( '합계: ' + sum );

(*)로 표시한 줄에 있는 break는 사용자가 아무것도 입력하지 않거나 Cancel 버튼을 눌렀을 때 활성화가 된다. 이때 반복문이 즉시 중단되고 제어 흐름이 반복문 아래 첫 번째 줄로 이동한다. 여기선 alert가 그 첫 번째 줄이 된다.

반복문의 시작 지점이나 끝 지점에서 조건을 확인하는 것이 아니라 본문 가운데 혹은 본문 여러 곳에서 조건을 확인해야 하는 경우, '무한반복문 + break 조합을 사용하면 좋다.

다음 반복으로 넘어가기

continue 지시자는 break의 '가벼운 버전'이다. continue는 전체 반복문을 멈추지 않고 현재 실행 중인 이터레이션을 멈추고 반복문이 다음 이터레이션을 강제로 실행시키도록 한다. (조건을 통과할 때)

continue는 현재 반복을 종료시키고 다음 반복으로 넘어가고 싶을 때 사용할 수 있다.

아래 반복문은 continue를 사용해 홀수만 출력한다.

for (let i = 0; i < 10; i++) {

  // 조건이 참이라면 남아있는 본문은 실행되지 않는다.
  if (i % 2 == 0) continue;

  alert(i); // 1, 3, 5, 7, 9가 차례대로 출력됨
}

i가 짝수면 continue가 본문 실행을 중단시키고 다음 이터레이션이 실행되게 한다. (i가 하나 증가하고, 다음 반복이 실행) 따라서 alert 함수는 인수가 홀수 일 때만 호출된다.

✔ `continue는 중첩을 줄이는 데 도움을 준다.
홀수를 출력해주는 예시는 알처럼 생길 수도 있다.

for (let i = 0; i < 10; i++) {

  if (i % 2) {
    alert( i );
  }

}

기술적인 관점에서 봤을 때, 이 예시는 위쪽에 있는 예시와 동일하다. continue를 사용하는 대신 코드를 if 블록으로 감싼 점만 다르다.

그런데 이렇게 코드를 작성하면 부작용으로 중첩 레벨(대괄호 안의 alert 호출)이 하나 더 늘어난다. if안의 코드가 길어진다면 전체 가독성이 떨어질 수 있다.

❗ '?' 오른쪽엔 breakcontinue가 올 수 없다.
표현식이 아닌 문법 구조(syntax construct)는 삼항 연산자 ?에 사용할 수 없다는 점을 항상 유의하자. 특히 breakcontinue 같은 지시자는 삼항 연산자에 사용하면 안된다.

break/continue와 레이블

여러 개의 중첩 반복문을 한 번에 빠져나와야 하는 경우가 종종 생기곤 한다.

ij를 반복하면서 프롬프트 창에 (0,0) 부터 (2, 2)까지를 구성하는 좌표 (i, j)를 입력하게 해주는 예시를 살펴보자.

for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let input = prompt(`(${i},${j})의 값`, '');

    // 여기서 멈춰서 아래쪽의 `완료!`가 출력되게 하려면 어떻게 해야 할까?
  }
}

alert('완료!');

사용자가 Cancel 버튼을 눌렀을 때 반복문을 중단시킬 방법이 필요하다.

input 아래에 평범한 break 지시자를 사용하면 안쪽에 있는 반복문만 빠져나올 수 있다.
이것만으로는 충분하지 않다. (중첩 반복문을 포함한 반복문 두 개 모두를 빠져나와야 하기 때문)
이럴 때 레이블을 사용할 수 있다.

레이블(label)은 반복문 앞에 콜론과 함께 쓰이는 식별자다.

labelName: for (...) {
  ...
}

반복문 안에서 break <labelName>문을 사용하면 레이블에 해당하는 반복문에 빠져나올 수 있다.

outer: for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let input = prompt(`(${i},${j})의 값`, '');

    // 사용자가 아무것도 입력하지 않거나 Cancel 버튼을 누르면 두 반복문 모두를 빠져나온다.
    if (!input) break outer; // (*)

    // 입력받은 값을 가지고 무언가를 함
  }
}
alert('완료!');

위 예시에서 break outerouter라는 레이블이 붙은 반복문을 찾고, 해당 반복문을 빠져나오게 해준다. 따라서 제어 흐름이 (*)에서 alert('완료!')로 바뀐다.
레이블을 별도의 줄에 써주는 것도 가능하다.

outer:
for (let i = 0; i < 3; i++) { ... }

continue 지시자를 레이블과 함께 사용하는 것도 가능하다. 두 가지를 같이 사용하면 레이블이 붙은 반복문의 다음 이터레이션이 실행된다.

❗ 레이블은 마음대로 '점프'할 수 있게 해주지 않는다.
레이블을 사용한다고 해서 원하는 곳으로 마음대로 점프할 수 있는 것은 아니다.
아래 예시처럼 레이블을 사용하는 것은 불가능하다.

break label; // 아래 co문으로 점프할 수 없다.

label: for(...)

breakcontine는 반복문 안에서만 사용할 수 있고, 레이블은 반드시 break 또는 continue 지시자 위에 있어야한다.

요약

지금까지 세 종류의 반복문에 대해 살펴봤다.

  • while: 각 반복이 시작하기 전에 조건을 확인한다.
  • do..while: 각 반복이 끝난 후에 조건을 확인한다.
  • for(;;): 각 반복이 시작하기 전에 조건을 확인한다. 추가 세팅을 할 수 있다.

'무한'반복문은 보통 while(true)를 써서 만든다. 무한 반복문은 여타 반복문과 마찬가지로 break 지시자를 사용해 멈출 수 있다.

현재 실행 중인 반복에서 더는 무언가를 하지 않고 다음 반복으로 넘어가고 싶다면 continue 지시자를 사용할 수 있다.

반복문 앞에 레이블을 붙이고, break/continue에 이 레이블을 함께 사용할 수 있다. 레이블은 중첩 반복문을 빠져나와 바깥의 반복문으로 갈 수 있게 해주는 유일한 방법이다.

✔ 과제


반복문의 마지막 값

중요도: 3

아래 코드를 실행했을 때 얼럿 창에 마지막으로 뜨는 값은 무엇일까요? 이유도 함께 설명해보세요.

let i = 3;

while(i) {
  alert(i--);
}

해답
답 : 1
반복이 하나씩 끝날 ㄸ ㅐ마다 i는 1씩 줄어든다. while(i)i = 0일때 멈춘다.

let i = 3;

alert(i--); // 3이 출력되고 i는 2로 줄어든다.

alert(i--) // 2가 출력되고 i는 1로 줄어든다..

alert(i--) // 1이 출력되고 i는 0으로 줄어든다.

// i가 0이 되었기 때문에 while(i)는 종료된다.

while 반복문의 출력값 예상하기

중요도: 4

while 반복문이 순차적으로 실행될 때마다 얼럿 창에 어떤 값이 출력될지 예상해보세요.

아래 두 예시는 같은 값을 출력할까요?

  1. 전위형 증가 연산자를 사용한 경우(++i):
let i = 0;
while (++i < 5) alert( i );
  1. 후위형 증가 연산자를 사용한 경우(i++):
let i = 0;
while(i++ < 5) alert(i);

해답

  1. 전위형 증가 연산자를 사용한 경우에는 1부터 4까지 출력된다.
let i = 0;
while (++i < 5) alert( i );

++ii를 먼저 증가시키고 새로운 값을 반환하기 때문에 첫 번째 while 반복문에서는 1과 5를 비교(1 < 5)하고, 얼럿 창에는 1이 출력된다.

1에 이어서 2, 3, 4...이 출력된다. i앞에 ++가 붙어 있기 때문에 5는 항상 증가 이후의 값과 비교된다.

i = 4 이후에 i의 값이 5로 증가하면 while(5 < 5)안의 비교가 실패하기 때문에 반복문은 멈춘다. 따라서 5는 출력되지 않는다.

  1. 후위형 증가 연산자를 사용한 경우엔 1부터 5까지 출력된다.
let i = 0;
while(i++ < 5) alert(i);

후위 증가 연산자를 적용하면 i++i를 증가 시키긴 하지만 기존값을 반환한다. 따라서 첫 번째 while 반복문에선 0과 5를 비교(0 < 5)합니다. 이 점이 전위 증가 연산자와의 차이다.

그런데 alert문은 조건문과 별개의 문이므로 얼럿창에는 1이 출력된다 i는 이미 증가한 이후이기 때문이다.

1이 출력된 이후에 2, 3, 4...가 이어서 출력된다.

i = 4일 때 잠시 생각을 가다듬어 보자. 전위 증가 연산자(++i)를 사용하면 값이 먼저 증가하기 때문에 5와 5를 비교하게 되는데, 여기선 후위 증가 연산자(i++)를 사용하고 있으므로 i는 증가하지만 기존 값인 4가 비교에 사용된다. 따라서 while(4 < 5)가 되고, 해당 조건은 참이므로 하단 블록이 실행되어 alert 창이 뜨게 된다.

다음 반복문은 while(5 < 5)이므로 마지막 출력되는 값은 5가 됩니다.

'for' 반복문의 출력값 예상하기

중요도: 4

for 반복문이 순차적으로 실행될 때마다 얼럿 창에 어떤 값이 출력될지 예상해보세요.

아래 두 예시는 같은 값을 출력할까요?

  1. 후위형 증가 연산자를 사용한 경우(i++):
for (let i = 0; i < 5; i++) alert( i );
  1. 전위형 증가 연산자를 사용한 경우(++i):
for (let i = 0; i < 5; ++i) alert( i );

해답

두 경우 모두 0부터 4까지 출력된다.

for (let i = 0; i < 5; ++i) alert( i );

for (let i = 0; i < 5; i++) alert( i );

for문의 알고리즘을 떠올려보면 쉽게 추론할 수 있는 문제다.

  1. 모든 작업이 시작되기 전 일단 i = 0이다.
  2. i < 5 조건을 만족하는지 확인한다.
  3. 위 조건이 true이면 반복문의 본문 alert(i)가 실행되고, 그 이후 i++가 실행된다.

i++는 위 알고리즘의 두 번째 단계(조건 확인)와 별개로 실행된다. 전혀 다른 구문이기 때문이다.

증가 연산자가 반환하는 값은 (2) 에서 쓰이지 않기 때문에 i++++i에 차이가 없다.

for 반복문을 이용하여 짝수 출력하기

중요도: 5

for 반복문을 이용하여 2부터 10까지 숫자 중 짝수만을 출력해보세요.

해답

for(let i = 0; i <= 10; i++) {
  if(i % 2 == 0) alert(i);
}

나머지 연산자 %를 사용하면 짝수인지 확인할 수 있다.

'for' 반복문을 'while' 반복문으로 바꾸기

중요도: 5

for 반복문을 while 반복문으로 바꾸되, 동작 방식에는 변화가 없도록 해보세요. 출력 결과도 동일해야 합니다.

for (let i = 0; i < 3; i++) {
  alert( `number ${i}!` );
}

해답

let i = 0;
while(i < 3) {
  alert( `number ${i}!` );
  i++;
}

사용자가 유효한 값을 입력할 때 까지 프롬프트 창 띄우기

중요도: 5

사용자가 100보다 큰 숫자를 입력하도록 안내하는 프롬프트 창을 띄워보세요. 사용자가 조건에 맞지 않은 값을 입력한 경우 반복문을 사용해 동일한 프롬프트 창을 띄워줍시다.

사용자가 100을 초과하는 숫자를 입력하거나 취소 버튼을 누른 경우, 혹은 아무것도 입력하지 않고 확인 버튼을 누른 경우엔 더는 프롬프트 창을 띄워주지 않아도 됩니다.

사용자가 오직 숫자만 입력한다고 가정하고 답안을 작성하도록 해봅시다. 숫자가 아닌 값이 입력되는 예외 상황은 처리하지 않아도 됩니다.

해답

let num;

do {
  num = prompt("100을 초과하는 숫자를 입력해주세요.", 0);
} while (num <= 100 && num);

do..while반복문을 사용해 아래 두 조건이 모두 truthy인 경우 프롬프트 창이 뜨게 하면 된다.

  1. num <= 100인지 확인하기. 100보다 작거나 같은 값을 입력한 경우 프롬프트 창이 떠야 한다.
  2. numnull이나 빈 문자열인지 확인하기. numnull이나 빈 문자열이면 && num이 거짓이 되므로 while 반복문이 종료된다.

참고: numnull인 경우 num <= 100true가 되므로 두 번째 조건이 없으면 취소 버튼을 눌러도 반복문이 계속해서 실행된다. 따라서 위 두 조건을 모두 확인해야 한다.

소수 출력하기

중요도: 3

소수(prime number)는 자신보다 작은 두 개의 자연수를 곱하여 만들 수 없는 1보다 큰 자연수입니다.

다시 말해서 1과 그 수 자신 이외의 자연수로는 나눌 수 없는 자연수를 소수라고 부르죠.

5는 2나 3, 4로 나눌 수 없기 때문에 소수입니다. 5를 이들 숫자로 나누면 나머지가 있기 때문이죠.

2부터 n까지의 숫자 중 소수만 출력해주는 코드를 작성해봅시다.

n = 10이라면 결과는 2,3,5,7이 되어야겠죠.

주의: 작성한 코드는 임의의 숫자 n에 대해 동작해야 합니다.

해답

소수를 판단하는 알고리즘은 다양하다.

먼저 중첩 반복문을 사용한 알고리즘을 살펴보자.

범위 내 모든 숫자 i에 대해서 {
  1과 i 사이에 제수가 있는지를 확인
  있으면 => 소수가 아님
  없으면 => 소수이므로 출력해줌
}

레이블을 사용해 위 알고리즘을 구현한 코드는 다음과 같다.

let n = 10;

nextPrime:
for (let i = 2; i <= n; i++) { // 각 i에 대하여 반복문을 돌림

  for (let j = 2; j < i; j++) { // 제수(나눗수)를 찾음
    if (i % j == 0) continue nextPrime; // 소수가 아니므로 다음 i로 넘어감
  }

  alert( i ); // 소수
}

위에서 사용한 알고리즘은 최적화할 부분이 많다. 제수를 2와 i의 제곱근 사이에서 찾으면 좀 더 나아진다. 아주 큰 n에 대해서 이차 체(Quadratic sieve)나 수 체(General number field sieve)와 같이 좀 더 어려운 수학과 복잡한 알고리즘을 이용해 소수 검색 알고리즘을 개선할 수 있을 것이다.

profile
공부

0개의 댓글