/ JVM 관련 언어

Scala WIL(Weekly I Learned) Part.06

리스트

리스트는 배열과 유사합니다. 그러나 중요한 두 가지 차이점이 있습니다. 첫째, 리스트는 변경 불가능합니다. 즉, 리스트 원소를 할당문으로 변경할 수 없습니다. 둘째, 리스트의 구조는 재귀적이지만, 배열은 평면적입니다.

배열과 마찬가지로 리스트도 동일한 타입으로 이뤄집니다. 스칼라 리스트 타입은 공변적(covariant)이기 때문에 ST의 서브타입이면 List[S]List[T]의 서브타입 입니다. 리스트가 공변성이기 때문에 모든 타입 T에 대해 List[Nothing]List[T]의 서브타입임을 도출할 수 있습니다.

리스트 생성하기


val nums = 1 :: 2 :: 3 :; 4 :: Nil
val fruit = List("apples", "oranges", "pears")
val xs: List[String] = List()
val List(a,b,c) = fruit
val a :: b :: c = fruit

기본연산

리스트의 모든 연산은 다음 세가지를 가지고 표현할 수 있습니다.

  • head는 어떤 리스트의 첫 번째 원소를 반환한다.
  • tail은 어떤 리스트의 첫번째 원소를 제외한 나머지 원소로 이뤄진 리스트다.
  • isEmpy는 리스트가 비어 있다면 true를 반환한다.

1차 메소드

어떤 메소드가 함수를 인자로 받지 않는다면, 그 메소드를 1차 메소드(first-order method)라 부릅니다.

  • 리스트 연결하기

:::은 리스트 연결 연산자 입니다. :::의 두 인자는 리스트(List(1,2,3) ::: List(3,4,5)입니다. 해당 연산자는 패턴 매칭을 사용해서 구현 가능합니다.


def append[T](xs: List[T], ys: List[T]): List[T] = 
xs match {
  case List() => ys
  case x :: xs => x :: append(xs, ys)
}

  • 리스트 길이 구하기

length는 배열에 비교해서 비싼 연산입니다.

  • 리스트 양 끝에 접근하기

head의 쌍대성 연산인 last는 비어 있지 않은 리스트의 마지막 원소를 반환하며, tail의 쌍대 연산인 init는 마지막 원소를 제외한 모든 원소를 포함한 리스트를 반환합니다. 상수 시간 복잡도인 headtail과 달리, initlast는 결과를 계산하기 위해 전체 리스트를 순회해야 합니다. 그러므로 이 둘은 리스트 길이만큼 시간이 걸립니다.

  • 리스트 뒤집기

다른 모든 리스트 연산과 마찬가지로 reverse는 연산 대상 리스트를 변경하기보다 새로운 리스트를 생성합니다. 리스트는 변경 불가능하기 때문에 그런 변경은 가능하지 않습니다.

  • 접두사와 접미사

droptake 연산은 리스트에서 임의의 접두사나 접미사를 반환합니다. xs take n은 리스트의 처음부터 n번째까지 원소를 반환합니다. xs drop n은 첫번째에서 n번째까지 원소를 제외한 xs 리스트의 모든 원소를 반환합니다. splitAt연산은 주어진 인덱스 위치에서 리스트를 분할해서(xs take n, xs drop n), 두 리스트가 들어 있는 순서쌍을 반환합니다.

  • 원소 선택하기

임의의 원소를 선택하는 것은 apply 메소드를 통해 지원하며, indices 메소드는 리스트에서 유효한 ㅁ모든 인덱스의 리스트를 반환합니다. 임의의 원소를 선택하는 연산을 배열보다 리스트에서 덜 사용하는 이유는 xs(n)에서 인덱스 n의 값에 비례해서 시간이 걸리기 때문입니다.

  • 리스트 펼치기

flattern 메소드는 리스트의 리스트를 인자로 받아서, 하나의 리스트로 반듯하게 펼칩니다.

  • 두 리스트를 순서쌍으로 묶기

zip은 두 리스트를 인자로 받아서 순서쌍의 리스트를 만듭니다. unzip은 튜플의 리스트를 튜플로 변경합니다.

  • 리스트 변환하기

toArray를 사용하면 리스트에서 배열로 손쉽게 데이터를 변환할 수 있으며, copyToArray는 특정 지점부터 연속적으로 복사합니다.

고차 메서드

  • map 메소드는 원소에 함수 f를 적용해서 나온 결과 값으로 이뤄진 리스트를 반환합니다. flatMap은 리스트의 원소에 함수 f를 적용해서 나온 모든 리스트를 연결한 단일 리스트를 반환합니다. foreach는 피연산자로 프로시저(Unit)를 받으며, 해당 원소에 프로시저를 적용합니다.

  • filter, partition, find, takeWhile, dropWhile, span

filter는 타입의 술어 ㅎ마수 p를 받습니다. partition 메소드는 filter와 같지만, 리스트의 순서쌍을 반환합니다. find 메서도는 filter와 비슷하지만, 첫번째 원소만 반환합니다. takeWhiledropWhile는 술어 함수를 받아서 해당 리스트를 반환합니다.

  • forall, exists

리스트의 모든 원소가 술어 함수 p를 만족할 때 결과는 true이며, exists는 하나라도 존재하면 true를 반환합니다.

  • 폴드연산(/:, :\)

왼쪽폴드(/:) 연산((z /: xs)(op))은 세 가지가 있습니다. z는 시작값, xs는 폴드 대상, op는 이항 연산자입니다. 오른쪽 폴드(:\)도 존재합니다. 해당 연산자를 사용한 것이 직관적이지 않을 수 있기 때문에 List 클래스에 있는 foldLeftfoldRight라는 이름의 메소드를 대신 사용할 수 있습니다.

  • 리스트 정렬

sortWith는 두 원소를 비교할 때 사용합니다.

List 객체의 메소드

List.apply는 원소로부터 리스트를 만들 수 있으며, List.range는 범위를 만듭니다. List.fill은 균일한 리스트를, List.tabulate는 제공된 함수로 계산한 원소의 리스트를 생성합니다. List.concat는 여러 메소드를 연결합니다.

여러 리스트를 함께 처리

zipped 메소드는 단일 리스트가 아니라 여러 리스트에서 동작하는 공통 연산을 일반환합니다.

컬렉션

시퀸스

대표적인 시퀸스로 배열과 리스트는 앞서 알아보았습니다.

  • 리스트 버퍼

ListBuffer는 변경 가능한 객체입니다. ListBuffer는 원소를 추가할 필요가 있을 때 더 효율적으로 리스트를 생성합니다. toList를 호출해서 리스트를 반환합니다.

  • 배열 버퍼

ArrayBuffer는 끝부분과 시작 부분에 원소를 추가하거나 삭제할 수 있다는 점만 제외하면 배열과 동일합니다.

집합과 맵

집합의 핵심 특징은 원소가 하나만 들어가도록 보장하는 자료구조입니다. 맵은 값과 원소 사이의 연관 관게를 생성합니다.

튜플

정해진 개수의 원소를 한데 묶는 자료구조 입니다.