005. 코틀린의 반복문

코틀린의 반복문

코틀린에서 Java와 가장 유사한 기능 중 하나는 반복문일 것이다.

코틀린의 반복문 중 for는 Java의 for-each 형태로 동작하며, C#과 마찬가지로 for (item in elemtents) 형태로 동작한다.

while loop

코틀린은 whiledo-while문을 둘 다 가지고 있다. 이 두 개의 반복 기법은 Java와 동일하다.

1
2
3
4
5
6
while (condition) {
/*...*/
}
do {
/*...*/
} while (condition)

whiledo-while은 Java와 동일하므로 for 문을 이용한 다양한 용도를 알아보자.

범위와 수열에서의 반복

위에서 언급했듯 코틀린의 for문은 Java의 일반적인 for문이 아닌 for-each 형태로 동작한다.

여기서 일반적이란 뜻은 특정 변수를 초기화 한 후, 해당 변수가 반복되며 특정 값만큼 갱신되고, 최종적으로 정해진 값에 도달하면 반복을 종료하는 등의 동작을 말한다.

위의 일반적인 동작을 코틀린에서 사용하려면 .. 연산자를 이용하며, 이는 특정 범위 혹은 구간을 표현하는 연산자로 사용된다.

1
val oneToTen = 1..10 // 코틀린에서 범위는 폐구간으로 표현된다. 1, 2, 3, 4, 5, 6, 7, 8, 9 , 10

정수 범위 내에서 가장 기본적인 루프는 모든 값에 대한 접근이며, 이러한 범위를 progression 라고 한다.

이제 정수의 범위를 이용해 Fizz-Buzz 게임을 해보자.

Fizz-Buzz 게임은 어떤 정수가 있을 때, 해당 정수가 3으로 나누어지면 Fizz, 5로 나누어지면 Buzz

그리고 3과 5로 둘 다 나누어지면 FizzBuzz로 말하는 게임이다.

아래 코드는 1부터 100까지의 정수에 대해 Fizz-Buzz게임을 수행하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
fun fizzBuzz(i : Int) = when {
i % 15 == 0 -> "FizzBuzz "
i % 3 == 0 -> "Fizz "
i % 5 == 0 -> "Buzz "
else -> "$1 "
}

/* 출력 결과 */
>>> for (i in 1..100) {
print(fizzBuzz(i))
}
1 2 Fizz 4 Buzz Fizz 7 ...

이번엔 1부터 100까지가 아니라 반대로 100부터 1까지 가는데, 2씩 차감하여 짝수를 대상으로 Fizz-Buzz게임을 실행하는 경우를 작성해보자.

1
2
3
4
5
/* 출력 결과 */
>>> for (i in 100 downTo 1 step 2) {
print(fizzBuzz(i))
}
Buzz 98 Fizz 94 92 FizzBuzz 88 ...

step, downTountil 등 키워드의 활용은 추후 보강하도록 하겠다.

참고 Kotlin Loops Tutorials

Map 구조에서의 반복

Collection을 쓰는 경우 ..를 사용하여 순차적으로 아이템에 접근하는 것이 일반적일 것이다.

코틀린의 Collection은 Java와 동일하기 때문에 특별히 다룰 내용이 많지는 않다.

따라서 대표적인 Collection인 Map을 예로 들어 어떻게 반복이 진행되는 지 파악해보자.

이번에 살펴볼 예제는 문자(character)의 이진표현을 Map 형태에 저장하는 프로그램이다.

1
2
3
4
5
6
7
8
9
10
val binaryReps = TreeMap<Char, String>()

for (c in 'A'..'F') {
val binary = Integer.toBinaryString(c.toInt())
binaryReps[c] = binary
}

for ((letter, binary) in binaryReps) {
println("$letter = $binary")
}

..를 사용하여 접근하는 것은 위의 코드에서 확인할 수 있듯 숫자뿐만 아니라 문자에도 적용된다.

위의 코드는 A에서 F까지 모든 문자에 대해 순차적으로 접근하고 있음을 추론할 수 있다.

for문 내의 인덱싱을 위한 c에 대한 이진값을 추출한 후, c를 Key로 하여 binaryReps에 저장하고, 이를 또 순차적으로 꺼내서 출력하고 있는 것이다.

추가적으로 Map의 특징을 이용해 put을 호출하는 것보다 map[key] 형태를 사용해 초기화하고 있음을 확인할 수 있다.

참고 물론 get대신 value를 뽑아내는 것도 가능하다.

해당 부분을 Java와 비교해보자.

먼저 코틀린의 경우

1
binaryReps[c] = binary

그리고 자바의 경우이다.

1
binaryReps.put(c, binary);

출력 결과는 아래와 같을 것이다.

1
2
3
4
5
6
A = 1000001
B = 1000010
C = 1000011
D = 1000100
E = 1000101
F = 1000110

Map과 마찬가지로, Collection들에 대해 구조분해 구문을 사용하면 별도의 변수 선언없이 인덱스를 유지한 상태로 반복뭉늘 수행할 수 있다.

아래의 코드를 참고하자.

1
2
3
4
5
6
7
8
9
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) {
println("$index: $element")
}

/* 출력 결과 */
0: 10
1: 11
2: 1001

in 키워드를 활용한 반복

반복을 수행하면서 in 연산자를 사용하여 특정 값이나 객체가 특정 범위나 Collection에 속해있는지 검증할 수 있다.

반대로 속해있지 않은 지 검증하는 !in 연산도 가능하다.

1
2
3
4
5
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'

/* 출력 결과 */
>>> println(isLetter('q'))
true
1
2
3
4
5
fun isNotDigit(c: Char) = c !in '0'..'9'

/* 출력 결과 */
>>> println(isNotDigit('x'))
true

위와 같은 테크닉을 이용하면 쉽게 문자열을 검증할 수 있고, 내부적으로 아무런 문제도 발생하지 않는다.

1
c in 'a'..'z'

in 키워드는 표현식을 만들 떄도 사용할 수 있다.

아래 예시를 통해 확인해보자.

1
2
3
4
5
6
7
8
9
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's a digit!"
in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
else -> "I don't know..."
}

/* 출력 결과 */
>>> println(recognize('8'))
It's a digit!

만약 Java의 Comparable 인터페이스를 구현한 클래스여서 서로간의 비교가 가능한 경우 객체들을 활용한 범위 설정도 가능하다.

숫자나 문자열처럼 모든 객체의 정의를 알 수는 없으므로, 모든 범위에 대한 반복을 불가능하지만 in 연산자를 통한 소속 여부는 얼마든지 검증할 수 있다.

예를 들어, JavaScala 사이의 모든 문자열에 대해 접근할 수는 없겠지만 Kotlin이 해당 범위 안에 소속되어있는 지는 알 수 있다.

1
2
>>> println("Kotlin" in "Java".."Scala")
true

in 연산자는 다른 Collection 타입에 대해서도 잘 동작한다.

1
2
>>> println("Kotlin" in setOf("Java", "Scala"))
false