본문 바로가기
개발 공부 기록하기/- Kotlin & Java

for, foreach, foreachIndexed 루프탈출 하기

by soulduse 2018. 5. 27.
반응형

간단한 리스트 데이터가 있다.

mutableListOf("밥먹기", "세수하기", "공부하기", "코딩하기", "운동하기", "티비보기")

리스트 중 3번째 데이터 까지만 보여주고 나머지는 생략을 하고싶은 경우는 어떻게 할까?

대략적으로 내가 원하는 그림


우선 필요한 정보와 옵션적인 메소드를(?) 선언하고

lateinit var todoList: MutableList<String>
lateinit var result: StringBuffer
var isMadeTitle = false

@Before
fun 사용할_데이터들_설정() {
todoList = mutableListOf("밥먹기", "세수하기", "공부하기", "코딩하기", "운동하기", "티비보기")
result = StringBuffer()
}

private fun printResult(){
println("결과 : $result")
}

간단하게 for문으로 짜보면 다음과 같이 짤 수 있다.

@Test
fun for_테스트() {
for (index in 0..todoList.size) {
if (index <= 2) {
result.append("\n${todoList[index]}")
}
else {
result.append(" .. 생략")
break
}
}


printResult()
}

원하는 결과가 잘 나오는 것을 확인 할 수 있다.


그렇다면 코틀린에서 기본 제공하는 foreach, foreachIndexed를 활용해보면 어떨까?

@Test
fun foreach_테스트() {
var index = 0
todoList.forEach {
if (index <= 2) {
result.append("\n$it")
} else {
result.append(" .. 생략")
return@forEach
}

index++
}

printResult()
}

우선 foreach의 경우 index가 없기 때문에 별도의 index를 만들어서 넣어주었다.

자 실행시켜보면 .. 


난 분명히 해당 조건에 걸리면 return 시켰는데 어떻게 된것인겨?

return이 아닌가 break를 시켜보려고 하니


break랑 continue는 온리 루프안에서만 허용된다고 한다. foreach가 루프가 아닌가?

/**
* Performs the given [action] on each element.
*/
@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}

안을 까보니 람다식을 받고 있는 형태이다. 


어떻게 동작될까? 궁금해졌다.


우선 값을 찍는 프린터 함수를 추가 하고

private fun print(index: Int, item: String){
println("""
|-------------------
|- index : $index
|- item : $item
""".trimMargin())
}

아래 함수를 다시 실행해보니

@Test
fun foreach_테스트() {
var index = 0
todoList.forEach {
if (index <= 2) {
result.append("\n$it")
} else {
result.append(" .. 생략")
return@forEach
}

print(index, it)
index++
}

printResult()
}

해당 조건에 도달하면 if조건 아래까지 실행되지 않았다.

하지만 loop를 완전히 빠져나가는 것이 아니라 계속해서 else 조건에 진입하였고, 결과값에 append 된 것을 알 수 있다.

마치 continue처럼 동작하는 것을 알 수 있다.


그렇다면 어떻게 해야될까 고민하다

@Test
fun forEachIndexed_테스트2() {
todoList.forEachIndexed { index, item ->
if(index <= 2){
result.append("\n$item")
}

if(index > 2 && !isMadeTitle){
result.append(" .. 생략")
isMadeTitle = true
return@forEachIndexed
}

print(index, item)
}

printResult()
}

이런 변태적인 루프가 탄생하였는데, 내가 원하는 결과 값은 잘 도출되지만, 

(아이템 갯수만큼 루프를 돌며 조건을 계속해서 체크한다는 뜻 )

지금처럼 아이템 갯수가 적으면 상관없지만 10만개라면? 이미 내가 원하는 값은 얻었고, 결과가 만들어졌음에도 불구하고

for문이 10만번 돌면서 if조건을 체크할 것이다.


따라서 다른 방법이 필요했다.


어떻게 하면 좋을까 고민하다가 자료를 찾아보았는데

역시 이런 고민을 코틀린 공식홈페이지에서 설명해주고 있는 부분이 있었다.


https://kotlinlang.org/docs/reference/returns.html


break 직접적으로 할 순 없지만 방법이 있었다. 그리고 return 을 했을때, continue처럼 동작하는거라고 설명되어있다.


다시 수정해보자

@Test
fun forEach_끊어가기(){
run loop@{
var index = 0
todoList.forEach { item ->
if(index <= 2){
result.append("\n$item")
} else {
result.append(" .. 생략")
return@loop
}
index++
}
}

printResult()
Assert.assertThat(result.lastIndexOf(" .. 생략"), CoreMatchers.`is`(14))
}

결과는 만족스럽게 잘 동작한다. :)


추가로 foreach에 보기에 안좋았던 index값을 제거하고 이미 만들어진 forEachIndexed 를 활용해서 바꿔보자

@Test
fun forEach_끊어가기2(){
run loop@{
todoList.forEachIndexed { index, item ->
if(index <= 2){
result.append("\n$item")
} else {
result.append(" .. 생략")
return@loop
}
}
}

printResult()

Assert.assertThat(result.lastIndexOf(" .. 생략"), CoreMatchers.`is`(14))

}


for, forEach, forEachIndexed를 좀 더 제대로 알고 사용할 수 있게 된 것 같다. :)

이상으로 포스팅 끝!



반응형