아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.
컬렉션 api 사용
- 사용하기 쉬움 : 20-50개 메소드 조합
- 간결함
- 안전함
- 빠름
- 보편적임
1. 변경가능, 변경 불가능 컬렉션
scala.collection
.mutable, immutable, generic
scala.collection
scala.collection.immutable
scala.collection.mutable
스칼라는 항상 변경 불가능한 컬렉션을 선택한다.
2. 컬렉션 일관성
Travelsable
Iterable
Seq
IndexedSeq
Vector
ResizableArray
GenericArray
LinearSeq
MutableList
List
Stream
Buffer
ListBuffer
ArrayBuffer
Set
SortedSet
TreeSet
HashSet (mutable)
LinkedHashSet
HashSet (immutable)
BitSet
EmptySet, Set1, Set2, Set3, Set4
Map
SortedMap
TreeMap
HashMap (mutable)
LinkedHashMap (mutable)
HashMap (immutable)
EmptyMap, Map1, Map2, Map3, Map4
Buffer 트레이트는 mutable만 존재
|
3. Traversable 트레이트
def foreach[U] (f: Elem => U)
컬렉션의 모든 원소를 순회하면서 주어진 연산 f를 각 원소에게 적용하는 것을 목적으로 한다.
메소드는 다음 범주 중에 하나에 속한다.
- 추가 메소드 : ++는 두 순회 가능 객체를 하나로 엮거나, 어떤 순회 가능 객체의 뒤에 이터레이터의 모든 원소를 추가함
- 맵 연산 : map, flapMap, collect는 어떤 함수를 컬렉션에 있는 원소에 적용해 새로운 컬렉션을 만들어 냄
- 변환 연산 : toIndexedSeq, toIterable, toStream, toArray, toList, toSeq, toSet, toMap은 Traversable 컬렉션을 더 구체적인 컬렉션으로 변환
- 복사 연산 : copyToBuffer, copyToArray는 컬렉션의 원소를 버퍼나 배열에 각각 복사
- 크기 연산 : isEmpty, nonEmpty, size, hasDefiniteSize
- 원소를 가져오는 연산 : head, last, headOption, lastOption, find
- 어떤 컬렉션이 매번 원소를 같은 순서로 반환하는 경우 순서가 있는 컬렉션이라고 명
- 하위 컬렉션을 가져오는 연산 : takeWhile, tail, init, slice, take, drop, filter, dropWhile, fiterNot, withFilter : 인덱스 범위나 술어에 따라 컬렉션의 일부 반환
- 분할 연산 : splitAt, span, partition, groupBy 는 컬렉션을 여려 하위 컬렉션으로 나눈다
- 원소 테스트 메소드 : exists, forall, count
- 폴드 연산 : foldLeft, foldRight, /:, :, reduceLeft, reduceRight 이상 연산을 연속된 원소에 반복 적용
- 특정 몰드 메소드 : sum, product, min, max 는 특정 타입 (수 이거나 변경 가능함 타입)에 컬렉션에 적용
- 문자열 연산 : mkString, addString, stringPrefix
- 뷰 연산 : view는 필요에 따라 계산이 나중에 이뤄지는 연산
4. Iterable 트레이트
def foreach[U] (f: Elem => U): Unit = {
val it = iterator
while (it.hasNext) f(it.next))
}
에터레이터를 반환하는 메소드 : grouped, sliding
메소드
추상 메소드 : xs.iterator
이터페이터
xs grouped size
xs sidling size
xs takeRight n
xs dropRight n
xs zip ys
xs zipAll (ys, x, y)
xs.zipWithIndex
xs sameElements ys
Traversal이 있는 이유 중 하나는 iterator를 구현하는 것보다 foreach 메소드의 구현이 효율적일 때가 있다.
iterable 하위 븐류인 Seq, Set, Map 3가지 트레이트
5. 시퀀스 트레이트 : Seq, IndexedSeq, LinearSeq
Seq 트레이트는 시퀀스 표현
시퀀스에서 사용할 수 있는 연산
- 인덱스와 길이 연산 : apply, isDefinedAt, length, indices, lengthCompare
- Seq[T]는 타입의 시퀀스는 inx 인자를 받아서 돌려주는 부분함수
- Seq[T] PartialFunction [Int, T] 를 확장
- seq 0부터 길이 -1 까ㅣㅈ의 인덱스가 붙는다.
- 인덱스 찾기 연산 : indexOf, lastIndexOf, indexOfSlice, lastIndexOfSlice, indexWhere, lastIndexWhere, seqmentLength, prefixLength
- 추가 연산 : +:, :+, padTo : 시퀀스 맨앞에나 맨 위에 원소 추가
- 변경 연산 : updated, patch는 원래 시퀀스를 일부 원소를 바꿔서 나옴
- 정렬 연산 : sorte, sortWith, sorBy
- 반전 연산 : reverse, reverseIterator, reverseMap
- 비교 연산 : startsWith, endsWith, contains, corresponds, containsSlice
- 중복 집합 연산 : intersect, diff, union, distinct
Seq 트레이느는 LinearSeq, indexedSeq 두가지 하위 트레이트가 있음.
선형 시퀀스는 더 효율적인 head, tail 연산
인덱스 시퀀스는 효율적인 apply, length, update
List, Stream은 선형 시퀀스
Array, ArrayBuffer는 인덱스드 시퀀스
Vector 클래스는 선형 시퀀스와 인덱스 시퀀스 중간
버퍼
변경 가능한 시퀀스의 한 범주,
원소 추가 : + =, + + +
맨 앞 원소 추가 : + = : , + + = :
ListBuffer, ArrayBuffer있음.
6. 집합 Set
Set은 원소 중복을 허용하지 않는 Iterable 이다.
- 검사 : contains, apply, subsetOf
- 추가 연산 : +, ++
- 제거 연산 : -, - -
- 집합 연산 : 합집합, 교집합, 차집합 union intersect diff
7. 맵
맵은 key와 value의 쌍 (이를 mapping, 연관 association )
- 검색 연산 : apply, get, getOrElse, contains, isDefinedAt
- 추가와 변경 연산 : + , + + , updated
- 제거 연산 ; - , - -
- 하위 컬렉션 생성 메소드 :keys, keySet, keysIterable, valuesIteravble, values
- 변환 연산 : filterKeys, mapValues
이전 결과를 저정하두면 cache 구현도 가능
8. 변경 불가능한 구체적인 컬렉션 클래스
리스트
리스트 : 유한한 변경 불가능한 시퀀스, 리스트 첫원소, 나머지 부분 접근에는 상수시간, 그 외 연산은 선형 시간
스트림
스트림 : 리스트와 비슷하지만 원소를 지연 계산함, 스트림은 무한할 수 있음
리스트는 :: 연산자로 구성, 스트림은 #:: 를 사용해 구성
var str = 1 #:: 2 #:: 3 #:: Stream.empty
// head는 1 tail은 2, 3
def fibFrom(a: Int, b: Int): Stream[Int] =
a #:: fibFrom(b, a + b)
val fibs = fibFrom(1, 1).take(7)
fibs.toList()
|
벡터
벡터는 헤드가 아닌 원소도 효율적으로 접근할 수 있는 컬렉션 타입임. 벡터는 넓고 얇은 트리로 표현
벡터는 변경 불가능
벡터를 변경하려면, deep copy 필요하지만, 전체 노드 카피가 아닌 해당 업데이트 하는 트리만 카피
노드당 32개 원소 32 * 32 = 1024
2개 브랜치 2 15
3개 브랜치 2 20
4개 브랜치 2 25
변경 불가능한 스택
LIFO 시퀀스 Stack, push, pop, peek 상수시간 소요
변경 불가능한 큐
FIFO
범위
range란 간격이 일정한 정수를 순서대로 나열한 시퀀스, 1,2,3 도 범위, 5, 8, 11, 14도 범위
범위를 만들때, to 와 by 메소드 사용
1 to 3
5 to 14 by 3 ( 5, 8, 11, 14)
해시 트라이
변경 불가능한 집합이나 맵을 효율적으로 구현하는 표준적인 방법
트라이의 표현은 벡터와 비슷하게 32개의 원소나 32개의 하위 트리를 포함하는 노드를 사용하는 트리
선택시 해쉬코드 사용
어떤 키를 찾는 작업은 키의 해시코드의 최하위 5비트를 루트의 하위 트리를 선택하기 위해 사용
그 다믐 5비트는 그 다음 단계의 트리 선택
스칼라의 맵이나 집합에서는 기본 구현으로 트라이를 사용
원소가 1개에서 4개 사이인 맵이나 집합은 원소를 필드로 저장하는 단일 객체로 생성
비어 있는 변경 불가 집합이나 맵은 싱글톤 - 메모리 중복 사용 방지
적흑 트리 red black tree
균형 이진 트리, treeset, treemap은 적흑트리
비트 집합
비트 집합은 작은 정수의 컬렉션을 그 보다 더 큰 정수의 비트로 표현함.
내부적으로 비트 집합은 64비트 Long 배열 사용
배열의 첫번째 Long은 0에서 63까지의 정수 두번째 Long은 64부터 127까지 표현
집합에 원소를 추가하는 데는 비트 집합의 배열에 있는 Long의 개수에 선형으로 비례하는 시간이 걸린다
리스트 맵
리스트 맵은 key value 쌍의 연결 리스트로 맵을 표현
리스트 맵에 대한 연산은 선형으로 크기에 비례
9. 변경 가능한 구체적인 컬렉션 클래스
배열버퍼
배열 버퍼는 배열과 크기를 저장
배열 버퍼에 원소 추가하는데 상수 분할 상환 시간 amortized time
분할 상황 시간 분석 - 배열 할당은 자주 일어자니 않으므로 배열 할당에 소요되는 시간 비용이 크더라도, 평균적인 비용은 상수시간에 근사함
버퍼 구성한 다음 array 변환 toArray
리스트 버퍼
배열 버퍼와 비슷하지만 내부에 연결 리스트 사용
버퍼 구성한 다음 리스트로 변환 toList
문자열 버퍼
문자열 만들대 유용 toString
연결 리스트
next로 연결됨, 빈 시퀀스도 nullPointerException은 반환안함.
빈 연결 리스트는 next가 자기 자신을 가리킴
이중 연결 리스트
양향향 next와 prev 제공
변경 가능한 리스트
단일 연결 리스트에 맨마지막 노드를 가리키는 포인터 존재, 리스트 뒤에 추가가 상수시간에 됨,
MutableList에서 mutable.LinearSeq 표준 구현임.
큐
enqueue대신에 + = 나 + + = 연산 사용
배열 시쿼스
고정 크기의 변경가능한 시퀀스
스택
정해진 변수로 계속 변경 가능
배열 스택
ArrayStack은 필요에 따라 크기를 변화시키는 배열, 인덱스를 통한 빠른 접근
해시 테이블
원소를 배열에 추가, 원소의 해시코드에 따라 그 위치를 결정
스칼라 변경 가능 맵은 해시 테이블 기반
약한 해시
weak reference 약한 참조, 어떤 객체를 가리키는 모든 참조가 약한 참조뿐이라면 gC는 그 객체가 사용 중이지 않다고 판단
키나 함수 결과를 해시 맵에 저장하면 계속 키가 커지는 데 참조를 다 하면, 맵에서 삭제됨.
concurrent map
여러 스레스에서 접근 가능, atomic operatation
변경 가능한 비트 집합
제자리에서 비트 변경 가능
10. 배열
자바 배열과 일대일 대응 : Array[Int] int[] : Array[String] String[]
그리고 추가적인 기능
- 스칼라 배열은 제네릭할 수 있다 Array[T]
- 스칼라 시퀀스와 호환 가능
- 모든 시퀀스의 연산을 지원
가능한 이유 : 암시적 변환을 대칭적으로 사용함. 배열을 Seq로 사용할 때마다 암시적으로 그것을 Seq의 서브타입으로 감싸준다.
서브 클래스는 scala.collection.mutable.WrappedArray
배열에 적용 가능한 다른 변환 : 배열을 모든 시퀀스 메소드를 지원하는 ArrayOps 라는 객체로 감싼다
ArrayOps 변환이 WrappedArray 변환보다 우선순위가 높다
Array[T] 타입
런타임 힌트 class tag /
완전히 제네릭 한 경우는 context bound 사용
import scala.reflect.ClassTag
def evenElems[T: ClassTag](xs: Vector[T]) : Array[T] = {
val arr = new Array[T] ((xs.length + 1) / 2)
for (i <- 0 until xs.length by 2)
arr(i/2) = xs(i)
arr
}
|
11. 문자열
문자열은 직접적인 시퀀스는 아니다. 하지만 이들도 시퀀스로 변활할 수 있고, 모든 시퀀스 연산을 지원
12. 성능특성
변경불가능
List, Stream, Vector, Stack, Queue, Range, String
변경 가능
ArrayBuffer, ListBuffer, StringBuffer, MutableList, Queue, ArraySeq, Stack, ArrayStack, Array
변경불가능
HashSet/HashMap TreeSet/TreeMap BitSet ListMap
13. 동일성
컬렉션 라이브러리는 동일성과 해시에 대해 일관된 접근 방법을 취한다.
컬랙션을 집합, 맵, 시퀀스로 구분, 다른 범주에 속하는 컬렉션은 같지 않다. Set(1, 2, 3) 과 List(1, 2, 3)은 같지 않다.
같은 범주에 속한 두 컬렉션이 포한하는 원소가 모두 같으면 두 컬렉션은 서로 같으며, 그 역도 성립한다.
List(1, 2, 3) == Vector(1, 2, 3)
HashSet(1, 2) == TreeSet(1, 2)
14. 뷰
컬렉션에는 새로운 컬렉션을 만들 수 있는 메소드가 많다.
map, filter, + + : 변환기 transformer
변환기 구현에는 엄격한 방식과 엄격하지 않은 방식 (게으른)
엄격한 변환기는 새 컬렉션에 모든 원소를 다 집어 넣는다.
엄격하지 않은 변환기는 컬렉션에 대한 프록시만 만들고, 원소는 요청이 있을 때만 만든다.
염격하지 않는 변환기 예
def lazyMap[T, U] (col1: Iterable[T], f: T => U) =
new Iterable[U] {
def iterator = col.iterator map f
}
|
Stream 메소드를 제외하고는 기본적으로 모두 엄격함. Stream 은 자신의 모든 변환기를 게으르게 구현함.
모든 컬렉션을 엄격하게 만들거나 되돌리는 체계적인 방법이 있다. 컬렉션 뷰를 바탕으로
val v = Vector(1 to 10: _*)
v map (_ + 1) map (_ * 2)
(v.view map (_ + 1) map (_ * 2)).force
val vv = v.view
vv map (_ + 1)
res13 map (_ *2)
rea14.force
|
v.view 호출은 SeqView 필요에 따라 계산하는 Seq 반환
중간구조를 없애기 위해서
view 사용을 고려해야하는 두가지 이유 :
첫째 성능 : 컬렉션을 뷰로 바꿔서 중간 결과 생성을 피할 수 있음
둘째, 변경 가능한 시퀀스의 뷰에 대해서
회문 : parlindrome
def isPalindrome(x: String) = x == x.reverse
def findParlindrome(s: Seq[String]) = s find isPalindrome
findParlindrome(words take 1000000) / 단점은 회문이 시퀀스에 첫단어 일지라도, 항상 백만 단어 짜리 시퀀스 생성
findPalindrome(words.view take 1000000)
원소가 백만개 있는 시퀀스를 만드는 대신 가벼운 뷰하나 생성
val subarr = arr.view.slide(3, 6)
이뷰는 원소는 복사하지 않고, 각 원소에 대한 참조만을 갖는다.
|
뷰가 모든 것을 모듈화 하는데 도움이 됨.
스크림을 제외한 모든 컬렉션과 뷰는 엄격하다, 엄격한 것에서 게으른 컬렉션으로 가는 방법을 view 메소드를 사용하는 방법뿐
부수 효과 없는 컬렉션 반환을 사용하는 완전히 함수적인 코드에서 뷰사용
모든 변경을 명시적으로 수행해야 하는 변경 가능한 컬렉션에서만 뷰 사용
가장 피해야 하는 것은 부수효과 있으면서 새 컬렉션을 만드는 연산과 뷰를 혼용하는 것
15. 이터레이터
이터페이터는 컬렉션이 아님, 원소에 하나하나 접근할 수 있는 수단, hasNext, next로 접근
버퍼이터레이터 :
16. 컬렉션 처음 만들기
17. 자바와 스칼라 컬렉션 변환
JavaConversions 객체에 대한 암시변환 제공
스칼라direction자바
Iterator |
<=> |
java.util.Iterator |
Iterator |
<=> |
java.util.Enumaration |
Iterator |
<=> |
java.util.Iterable |
Iterable |
<=> |
java.util.Collection |
mutable.Buffer |
<=> |
java.util.List |
mutable.Set |
<=> |
java.util.Set |
mutable.Map |
<=> |
java.util.Map |
Seq |
=> |
java.util.List |
mutable.Seq |
=> |
java.util.Set |
Set |
=> |
java.util.Set |
Map |
=> |
java.util.Map |
18. 결론
함수 리터럴과 변경 불가능한 다양한 컬렉션 타입과의 결합