
while
while (조건식) { … }
for문을 마스터한 감각을 살려서 while문도 정복해보자.
우선 for문 형태의 반복문을 while문형으로 전환해보기!
예제1.
void main() {
String message = "hello";
for (var i = 0; i < 5; i++) {
message += '!';
print(message);
}
}
↓
void main() {
int count = 0;
String message = "hello";
while (count < 5) {
print(message += "!");
count++;
}
}
/*
hello!
hello!!
hello!!!
hello!!!!
hello!!!!!
*/제대로 돌아간다.
(실은 처음에는 count++;를 넣는 걸 깜빡해서 무한루프 한 번 또 다녀왔음…)
그래도 바로 뭐가 문젠지 파악하고 수정한 나새끼, 대단해.
do-while
do { … } while (조건식);
일단 do문을 실행한 뒤 while 조건식이 참인지 거짓인지를 판단한다.
while 문의 조건식이 참이면 do문을 다시 실행, while문의 조건식이 거짓이면 멈춘다.
void main() {
int count = 6;
do {
print("count = $count");
count++;
} while (count < 5);
}
// count = 6일단 do문을 먼저 실행하기 때문에 count = 6이 출력되고,
while의 조건문이 충족되지 않기 때문에 do문이 다시 실행되지 않는 걸 알 수 있다.
for-in
for (순회식) { … }
void main () {
List<String> actors = ["박정민", "구교환", "주지훈"];
for (var actor in actors) {
print("올해의 대새 남자배우는 $actor !!");
}
}
/*
올해의 대새 남자배우는 박정민 !!
올해의 대새 남자배우는 구교환 !!
올해의 대새 남자배우는 주지훈 !!
*/for-in 문은 콜렉션 안에 있는 요소들을 하나씩 호출하는 기능이다.
1회 순회하는 동안 요소 값이 변하지 않으므로, 이때의 변수 actor는 상수처럼 취급된다.
따라서 일반적인 for문에서는 변수가 상수일 수 없는 것과 달리 (변수는 증감식에 따라 변동되니까)
for-in 문에서는 상수final 타입으로 지정해도 문제가 없다.
break
반복문을 중단하는 키워드
예제1.
void main() {
int count = 1;
while (count < 10) {
count++;
if (count == 5) {
print("count = $count");
}
}
}
// count = 5예제1에서 if문의 조건식이 참인 경우는 5번 만에 나오는데
while문에 의해 count < 10 가 거짓이 될때까지 프로그램이 돌아가게 된다.
이게 < 10 이니까 5번 더 돌아간 거지만 < 10000 이거나 하는 식으로 풀이 더 넓다면 그 풀을 다 검증해야 프로그램이 멈추는 식이다.
이런 비효율을 멈추게 하는 것이 break 키워드이다.
예제2. (알고 보니 나쁜 예)
void main() {
int count = 1;
while (count < 10) {
count++;
if (count == 5) {
print("count = $count");
break;
}
}
}
// count = 5결과값은 똑같지만 예제1에서는 while문이 거짓이 될때까지 코드가 반복되는 반면,
break를 사용한 예제2에서는 if값이 참인 순간 반복을 멈추어 훨씬 효율적이다.
이게 나중에 서버에 올려서 코드가 돌아가고 사용량에 따라 서버비가 과금될 때를 생각한다면
break는 참으로 목숨같은 키워드라 할 수 있겠다.
재미있는 예제 없을까.
예제3. (나쁜 예)
void main() {
int count = 0;
List<String> winterFood = ["과메기", "대방어", "굴보쌈", "군고구마", "오뎅탕", "붕어빵"];
var best = "대방어";
while (count <= winterFood.length) {
if (winterFood.contains(best)) {
print("찾았다 내 최애 겨울음식 $best");
count++;
break;
}
}
}
// "찾았다 내 최애 겨울음식 대방어"연습 삼아 내가 생각한대로 코드를 작성하고 돌려보았다.
원하는 결과값이 나오기도 했다.
그치만 이렇게 쓴다면 count는 무슨 의미가 있지? 없어도 되지 않나- 싶어서
GPT에게 코드에 대한 평가를 요청했다.
GPT의 평가:
이 코드는 겉보기에는 돌아가지만, 구조적으로는 전반적으로 비효율적이고 논리도 어긋나 있어.
이 while문은 사실상 딱 한 번만 실행돼.
그 이유는:
1. winterFood.contains(best)
→ 리스트에 ‘대방어’가 있으니 무조건 true
2. if문 안으로 들어감
3. print 실행됨
4. count++ 실행됨
5. break로 while 종료됨
즉, loop는 의미가 없고, while은 장식이야.
실제로는 그냥 if문 하나만 실행한 것과 동일해.
** 이 코드의 근본적인 문제들 **
문제 1. while문의 목적이 없다
문제 2. count라는 변수가 반복과 아무 상관이 없음
문제 3. break는 “찾으면 멈춰라” 구조에서 쓰는 게 맞지만, 루프가 돌지 않으니 break의 의미가 사라진다.라는 평가를 내놓았다.
너무 맞는 말이잖아…
GPT가 지적해준 부분들을 고려하여 코드를 다시 작성해본다.
예제4.
void main () {
int count = 0;
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
String best = "과메기";
while(count < winterFood.length) {
if (winterFood[count] == best) {
print("찾았다 내 최애 겨울음식 $best!");
break;
}
count++;
}
}사실 처음에는 count++을 if문 안에 넣었었는데, count++의 역할은 if문의 목적에 부합하는 것이라기보다는 while문의 목적에 부합하는 요소라고 생각되어 if문에서 꺼내서 while문에 넣었다.
그리고 내 판단이 맞는지 GPT에게 물어보았다.
count++가 if문 안에 있을 때
while(count < winterFood.length) {
if (winterFood[count] == best) {
print("찾았다!");
count++; // 여기에 들어있다
break;
}
}
여기서는 의미가 확 뒤틀린다.
count++는 이렇게 해석돼:
"best를 찾았을 때만 count를 증가시켜라"
즉, 반복의 자연스러운 흐름을 담당하는 게 아니라
특정 조건이 만족될 때에만 실행되는 “부수효과”로 내려앉는다.
이게 왜 문제냐면…
의미적 문제를 분석해보자
1) 찾기 전에 count가 증가하지 않는다
best가 첫 번째 요소에 없다면?
while문은 계속 반복되는데
count는 전혀 증가하지 않고
winterFood[count]는 계속 같은 요소만 검사한다
→ 무한 루프가 된다.
이게 아주 큰 문제야.
즉, count 증가가 if문 안에 있는 동안
코드가 정상 동작하는 경우는 딱 하나:
winterFood[0]이 바로 best일 때세상에. 엄청 다른 문제였어?
예제3에서도 count++을 if문 안에 넣었었는데, 그건 왜 결과값이 출력됐지?
GPT에게 물어보니 그 이유는 다음과 같다.
예제3에서 winterFood.contains(best)는 반복과는 무관한, 이미 전체 리스트를 한 번에 검사하는 함수.
winterFood에 "대방어"가 있으니까 이 if 조건은 무조건 참. 그러므로 count++도, break도 실행되는 것인데, 달리 말하자면 이 식에서는 count++도 break도 무의미하다는 것.
예제4에서는 winterFood[count]는 반복되는 count 값을 기반으로 리스트 요소를 하나씩 순회한다.
따라서 count++은 루프의 진행을 담당하는 핵심 변수.
만약 count++이 if문 안에 있다면,
best가 첫 번째 요소가 아닐 때 if문은 false가 되고, count++은 실행되지 않음.
count는 영원히 0이고, if문은 영원히 false = 무한반복개미쳤네.
또다시 서버비로 파산할 뻔했다..
개발자들은 대체 어떻게 살아가는 것인가…
사소한 실수(사소하지 않음)가 파산을 불러올 수 있는 이런 상황이라니…
그렇지만 그렇다면 또 의문이 생긴다.
예제4의 코드구조를 다시 보자면,
void main () {
int count = 0;
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
String best = "과메기";
while(count < winterFood.length) {
if (winterFood[count] == best) {
print("찾았다 내 최애 겨울음식 $best!");
break;
}
count++;
}
}의문 count < winterFood.length 조건이 있기 때문에 어차피 루프는 돌지 않는 것 아닌가? 어차피 1회만에 if가 true인 것은 마찬가지 아닌가?
=> 이 의문에 대한 답을 스스로 생각해보자면: 추론 best를 찾는 순간 멈추기 때문에 “양미리”와 “군고구마”는 검토하지 않고 멈추는 것. 만약에 뒤에 요소가 2억개쯤 있었다면, 훨씬 효율적인 것. 1회만 루프하더라도 순서대로 검토하는 것에 의미가 있는 것 아닐까.
라고 생각하니 또 이어서 떠오르는 의문: 의문 아니 그러면 애초에 다음의 코드가 더 효율적인 거 아닌가.
예제5.
void main () {
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
String best = "과메기";
print(winterFood.contains(best));
}라고 했을 때 추정해보자면
추론 두 코드의 의미가 다르다. while문을 사용한 코드는 앞에서부터 순차적으로 검토하는 방식이라면, contains를 사용한 경우는 이 전체에서 찾아내는 것이라 작동하는 방식이 다르지 않을까?
나는 이 의문과 추정에 대해 적절한 의문과 적절한 추론인지 GPT에게 또 물어보았다.
1.
“어차피 루프는 1번만 돈다?” -> 5번 돌았음.
1회차: count=0 → "귤" 비교 → 불일치 → count=1
2회차: "굴보쌈"
3회차: "붕어빵"
4회차: "대방어"
5회차: "과메기" → 여기서 찾고 break
즉, 5번 돌고 멈춘다.
count < winterFood.length는 “0부터 length-1까지는 돌 수 있다”는 상한선일 뿐이고, 실제로 몇 번 도는지는 if와 break에 달려 있는 구조야.
2.
“더 효율적인가?”
Dart의 List.contains()는 내부적으로:
for (var element in list) {
if (element == target) return true;
}
return false;
이런 식으로 동작한다고 보면 돼.
즉, 앞에서부터 순차적으로 검토해.
그러나 그 의미(=목적) 가 다르다.
- while 버전:
“앞에서부터 하나씩 보면서 찾는 과정 자체에 개입하고 싶을 때”
예: 몇 번째에 있는지, 찾는 과정 로그 찍기, 조건을 복잡하게 만들기 등
- contains 버전:
“리스트 안에 그 값이 있기만 한지 알고 싶을 때”
결과: bool (true/false)만 딱 반환
=> while 버전: “탐색 과정과 위치 정보가 중요한 로직”
contains 버전: “존재 여부만 중요하고, 나머지는 관심 없음”Hㅏ… 그렇구나…
애초에 contains라는 메서드는 for문의 축약형이었구나.
게다가 contains는 bool 값을 갖는다는 것도 제대로 알아버렸다.
머리속이 시원해지는 기분이다.
내가 제대로 깨달았는지 확인하기 위해,
이번에는 else를 넣어서 if 값이 거짓인 결과를 출력해봐야겠다.
예제6.
void main () {
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
String best = "과메기";
int count = 0;
while (count < winterFood.length) {
if (winterFood[count] == best) {
print("찾았다 내 최애 겨울음식 $best !");
break;
}
print("${winterFood[count]}도 좋지만,");
count++;
}
}
/*
귤도 좋지만,
굴보쌈도 좋지만,
붕어빵도 좋지만,
대방어도 좋지만,
찾았다 내 최애 겨울음식 과메기 !
*/여기서 멈출 수 없지, 한걸음 더 가본다.
예제7-1.
void main () {
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
String best = "과메기";
int count = 0;
bool found = false;
while (count < winterFood.length) {
if (winterFood[count] == best) {
found = true;
print("찾았다 내 최애 겨울음식 $best!");
break;
}
print("${winterFood[count]}도 좋지만,");
count++;
}
if(!found) {
print("나의 최애 겨울음식은 $best!");
}
}
/*
귤도 좋지만,
굴보쌈도 좋지만,
붕어빵도 좋지만,
대방어도 좋지만,
찾았다 내 최애 겨울음식 과메기!
*/예제7-2.
void main () {
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
String best = "오뎅탕";
int count = 0;
bool found = false;
while (count < winterFood.length) {
if (winterFood[count] == best) {
found = true;
print("찾았다 내 최애 겨울음식 $best!");
break;
}
print("${winterFood[count]}도 좋지만,");
count++;
}
if(!found) {
print("나의 최애 겨울음식은 $best!");
}
}
/*
귤도 좋지만,
굴보쌈도 좋지만,
붕어빵도 좋지만,
대방어도 좋지만,
과메기도 좋지만,
양미리도 좋지만,
군고구마도 좋지만,
나의 최애 겨울음식은 오뎅탕!
*/하~ 마음에 드는 코드를 완성했다.
여기까지 오는 과정에서 GPT에게 또 무수하게 묻고 이해하고 이런 과정이 있었다.
그 과정에서 가장 크게 배운 점은 bool을 쓰는 방법!
뿌듯하다.. 행복..
인지하지 못했지만 어제 다른 분들과 얘기 나누면서 나의 진도가 너무 느리다는 게 고민이었는데,
이런 식으로 하나하나 진행하니 느릴 수밖에.
그래도 내 방식이 틀렸다과 생각하진 않는다.
방식을 유지하되 시간을 더 확보해보자.