엄범


- 인라인 함수로 정의되어 있어 알아서 인라이닝되므로 성능 신경쓰지말고 그냥 사용하면 된다.

- 연쇄해서 사용하는 경우 연산의 순서가 성능에 영향을 줄 수 있기 때문에 고려해야 한다. 보통 ``kt filter()``를 먼저 적용해 추리는게 도움이 되고, 메서드 체이닝 시 앞쪽에서 부터 적용되기 때문에 ``kt filter()``를 먼저 써주는게 좋다.

  - 고 하는데 지연평가 안되나?


filter/map

```kt
>>> val people = listOf(Person("A", 25), Person("umbum", 25), Person("B", 19), Person("C", 23))
>>> println(people.filter({ it.age > 24 }))    // 객체의 리스트를 반환
[Person@37c5cf61, Person@700a2863]
>>> println(people.filter({ it.age > 24 }).map({ it.name }))
[A, umbum]
```

all/any/count/find

```kt
>>> println(people.all({ it.age > 20 }))
false
>>> println(people.any({ it.age > 20 }))
true
>>> println(people.count({ it.age > 20}))  // 갯수 세기는 이게 제일 효율적.
3
>>> println(people.any({ it.age == 25 }))
true
>>> println(people.find({ it.age == 25}))  // 만족하는 원소 없으면 null 반환
Person@277a0b1e    // 만족하는 원소 중 첫 번째 원소 반환.
```


groupBy 

컬렉션의 모든 원소를 특성에 따라 여러 그룹으로 나누고 싶을 때. 아~~주 편함.
e.g., 사람을 나이에 따라서 분류할 때.
```kt
>>> println(people.groupBy { it.age })
{25=[Person@2a4b9aa0, Person@2be48c1e], 
 19=[Person@7403b7df], 
 23=[Person@20984847]}

>>> println(people.groupBy { it.age > 20 })

{true=[Person@2a4b9aa0, Person@2be48c1e, Person@20984847], 

 false=[Person@7403b7df]}

```


flatMap ( map + flatten )

람다를 컬렉션의 모든 객체에 적용하고(map) 람다를 적용한 결과로 얻어지는 여러 리스트를 하나의 리스트로 합친다.(flatten)
```kt
>>> class Book(val title: String, val authors: List<String>)
>>> val books = listOf(Book("Tursday Next", listOf("Jasper Fforde")),
...         Book("Mort", listOf("Terry Pratchett")),
...         Book("Good Omens", listOf("Terry Pratchett", "Neil Gaiman")))
>>> println(books.flatMap({ it.authors }).toSet())
[Jasper Fforde, Terry Pratchett, Neil Gaiman]
```
* 여러 리스트를 하나의 리스트로 합치고만 싶을 때는 ``kt flatten()``을 따로 사용하면 된다.

시퀀스(Sequence) : 컬렉션의 크기가 큰 경우

컬렉션 함수를 그냥 사용하는 경우, 결과 컬렉션을 즉시 생성한다.

즉, 다음과 같이 연쇄해서 사용하는 경우 ``kt filter()``의 실행 결과(중간 계산 결과)를 새로운 컬렉션에 담고, 이 컬렉션에 대해 다시 ``kt map()``을 적용한 결과를 결과 컬렉션에 담아 반환하게 된다.

```kt

people.filter({ it.age > 24 }).map({ it.name })

```

컬렉션의 크기가 크다면 이렇게 중간 계산 결과를 매번 담는게 굉장히 비효율적이다.

Sequence는 컬렉션 원소를 하나 씩 가져와서 ``kt .filter().map()``을 차례로 적용한 결과를 바로 결과 컬렉션에 저장하는 방식이기 때문에 중간 계산 결과를 담을 필요가 없어 원소가 많은 경우 성능이 눈에 띄게 좋아진다.

```kt

people.asSequence()

.filter({ it.age > 24 })

.map({ it.name })

.toList()

```

* ``kt Sequence`` 인터페이스 안에는 ``kt iterator``라는 단일 메소드가 존재(SAM)한다. 시퀀스의 원소는 필요할 때 계산된다. 지연 평가 된다는 뜻.

자바8 스트림과 같은 개념인데, 코틀린은 따로 옵션 안주면 Java 6 대응이므로 둘이 같지는 않다. 코틀린 스트림을 사용하면 안드로이드 등 예전 버전 자바를 사용하는 경우도 대응이 된다는 장점이 있고, 자바8 스트림을 사용하면 ``kt filter()/map()`` 등의 연산을 CPU에서 병렬적으로 실행한다는 장점이 있다.


Note )

시퀀스는 크기가 큰 컬렉션에 대해서만 사용하도록 한다.

원래 ``kt filter()``등은 ``kt inline``으로 선언된 함수이기 때문에 사용시 함수 본문이 인라이닝되어 추가 객체나 클래스를 생성하지 않는다. 그러나 시퀀스를 사용하게 되면, 중간 시퀀스가 람다를 필드에 저장하는 객체로 표현되며 최종 연산은 중간 시퀀스에 있는 여러 람다를 연쇄 호출하는 형태이기 때문에 람다를 저장해야 해서 람다를 인라인하지 않는다.

그래서 작은 컬렉션에 대해서는 시퀀스를 사용하지 않는 편이 좋다.


중간 연산(=지연 연산)과 최종 연산

시퀀스에 대한 연산은 중간 연산(intermediate)과 최종 연산(terminal)으로 나뉜다.

중간 연산은 항상 지연 계산된다. 최종 연산의 그 원소가 필요할 때 그 원소에 대해서만 모든 연산이 순차적으로 적용되는 방식이기 때문에, 최종 연산이 없다면 아무것도 실행되지 않는다.

```kt

>>> listOf(1, 2, 3, 4).asSequence()

...         .map({ print("map($it) "); it * it })

...         .filter({ print("filter($it) "); it % 2 == 0 })

kotlin.sequences.FilteringSequence@f9e69b

```


이렇게 최종 연산을 붙여주어야 한다.

```kt

>>> listOf(1, 2, 3, 4).asSequence()

...         .map({ print("map($it) "); it * it })

...         .filter({ print("filter($it) "); it % 2 == 0 })

...         .toList()

map(1) filter(1) map(2) filter(4) map(3) filter(9) map(4) filter(16) [4, 16]

```


시퀀스 만들기 : generator

시퀀스를 사용하는 일반적인 용례 중 하나는 객체의 조상으로 이루어진 시퀀스를 만드는 것이다.

```kt

>>> fun File.isInsideHiddenDirectory() =

...         generateSequence(this, { it.parentFile }).any({ it.isHidden })

>>> val file = File("/umbum/.hidden/aaa")

>>> println(file.isInsideHiddenDirectory())

true

```