-
// List(3, 4, 5) List(1, 2, 3, 4, 5)
Programming in scala 4th edition 16장
1. 리스트 리터럴
val numbers = List(1, 2, 3) val tuples = List( List(1, 2), List(2, 3), List(3, 4) )
리스트와 배열는 두가지 차이점이 있다.
- 리스트는 변경 불가능하다. 리스트의 원소는 할당문으로 바꿀 수 없다.
- 리스트의 구조는 재귀적이지만 배열은 평면적이다.
2. 리스트 타입
리스트는 같은 타입의 원소로 이루어져있다.
List[T], T 타입의 원소만 가질 수 있음
또한 리스트 타입은 공변적이다. T와 서브타입 S가 있을때 List[S]도 List[T]의 서브 타입이다.
- 예를들어서 List[String]은 List[Object]의 서브 타입이다.
- Nothing은 모든 타입의 맨 아래에 있는 타입이므로 빈 리스트 타입인 List[Nothing]은 모든 리스트 타입 List[T]의 서브 타입이다.
3. 리스트 생성
기본적으로 모든 리스트는 아래 두가지 기호를 이용하여 생성할 수 있다.
- Nil: 빈 리스트
- :: (cons 콘즈): 리스트 앞에 원소를 추가하여 새로운 리스트를 만듬
val numbers = List(1, 2, 3) val numbersUsingCons = 1 :: 2 :: 3 :: Nil print(numbers == numbersUsingCons)
위의 numbers와 numbersUsingCons는 같은 리스트를 생성한다.
4. 리스트 기본 연산
- head: 리스트의 가장 첫번째 원소를 반환
- tail: 리스트의 첫번째를 제외한 나머지 원소로 이루어진 리스트 반환
- isEmpty: 빈 리스트이면 true 를 반환
val numbers = List(1, 2, 3) println(numbers.head) println(numbers.tail) println(numbers.isEmpty)
5. 리스트 패턴
리스트에 패턴 매치를 적용해서 각 부분을 분리할 수도 있다.
val List(one, two, three) = numbers println(one, two, three) // 1, 2, 3 으로 분리됨
val one :: two :: three :: Nil = numbers println(one, two, three) // 1, 2, 3 으로 분리됨
:: 를 사용해서 패턴 매치를 시킬 경우 Nil로 마무리를 지어줘야한다. 그렇지 않으면 마지막 변수가 List 형태로 담긴다.
아래와 같이 분리하는것도 가능하다
val one :: rest = numbers println(one, rest) // (1, List(2, 3))
6. List 클래스의 1차 메서드
1차 메서드: 함수를 인자로 받지 않는 메서드
두 리스트 연결
::: 연산자: 리스트를 연결한다. 앞의 콘즈 연산자는 원소를 리스트의 앞에 추가하는 연산자였다. ::: 연산자는 리스트와 리스트를 붙이는 연산자이다.
List(1, 2) ::: List(3, 4) // List(1, 2, 3, 4)
리스트 길이 구하기
length 메서드는 리스트의 길이를 구한다. 리스트의 길이를 구하는 메서드는 n의 시간 복잡도가 소요된다. 따라서 빈 리스트를 테스트할 경우에는 length == 0 보다는 isEmpty를 사용해야한다.
List(1, 2, 3, 4, 5).length // 5
리스트의 양 끝에 접근하기
head 에 대응되는 last, tail 에 대응되는 init이 있다. head, tail과 마찬가지로 빈 리스트에 호출하면 예외가 발생한다.
- init: 마지막원소를 제외한 리스트를 반환한다
- last: 마지막 원소를 반환한다.
init, last는 모두 리스트 길이만큼의 시간복잡도가 소요된다.
Reverse
reverse는 리스트의 원소의 순서를 뒤집은 리스트를 반환한다.
List(1, 2, 3, 4, 5).reverse //res6: List[Int] = List(5, 4, 3, 2, 1)
drop, take, splitAt
- take: take(n) n 번째 원소까지 담고있는 리스트를 반환한다.
- drop: drop(n) n번째 원소까지를 제외한 리스트를 반환한다.
- splitAt: splitAt(n) n번째 원소까지 담고있는 리스트와 나머지 리스트를 반환한다.
numbers.take(2) numbers.drop(1) numbers.splitAt(1) // 출력 res7: List[Int] = List(1, 2) res8: List[Int] = List(2, 3) res9: (List[Int], List[Int]) = (List(1),List(2, 3))
원소 선택: apply, indices
리스트에서 어떤 원소 하나를 조회하는 일은 n의 시간복잡도가 소요되기 때문에 좋은 방법은 아니다.
- apply: apply(n) n + 1 번째 원소를 반환한다
- indices: 리스트가 가지고 있는 인덱스들을 반환한다.
numbers.apply(0) numbers(0) // apply 의 간소화 numbers.indices // Range(0, 1, 2)
리스트의 리스트를 한 차원 줄이기: flatten
val twoDimension = List( List(1, 2), List(3, 4, 5), List(6, 7, 8, 9) ) twoDimension.flatten //res13: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
두 리스트를 순서쌍으로 묶기: zip, unzip
val list1 = List(1, 2, 3) val list2 = List("a", "b", "c") val zippedList = list1.zip(list2) zippedList // List((1,a), (2,b), (3,c)) val (a, b) = zippedList.unzip println(a) // List(1, 2, 3) println(b) // List("a", "b", "c")
zip은 두 리스트를 튜플로 묶어 새 리스트를 만든다. unzip은 zip의 반대 연산이다.
리스트 출력: toString, mkString
println(zippedList.toString) // List((1,a), (2,b), (3,c)) println(zippedList.mkString("[", "|", "]")) //[(1,a)|(2,b)|(3,c)]
mkString은 (pre, sep, post) 3가지 인자를 받는다. pre, post는 리스트의 시작과 끝, sep은 원소 사이에 들어가는 문자열을 지정할 수 있다.
리스트 변환: iterator, toArray, copyToArray
리스트와 배열을 변환하고 싶으면 toArray, toList를 사용하면 된다.
val chars = List('a', 'b', 'c', 'd') val charArray = chars.toArray println(charArray) // 배열의 해시값 출력 println(charArray.toList) // List(a, b, c, d)
copyToArray(arr, start)는 리스트를 배열의 어떤 시작지점(start)부터 복사한다.
val integerArray = new Array[Int](10) val integerList = List(1, 2, 3) integerList.copyToArray(integerArray, 2) integerArray // Array(0, 0, 1, 2, 3, 0, 0, 0, 0, 0)
iterator는 아래와같이 변환할 수 있다.
val iter = integerList.iterator iter.next
7. List 클래스의 고차 메서드
고차 메서드는 인자로 다른 함수를 전달받는 메서드이며 리스트 클래스의 메서드이다.
리스트 맵핑: map, flatmap, foreach
xs map f 연산: List[T] 타입 xs 와 T => U 타입의 f 를 인자로 받는 연산. xs의 원소에 f를 적용한 결과로 이루어진 리스트를 반환한다.
val integerList = List(1, 2, 3) integerList map ((x) => x * 2) // List[Int] = List(2, 4, 6) integerList map (_ * 2) // 위와 같음
xs flatMap f 연산: f는 xs의 원소 하나를 받아 리스트를 반환하는 함수이다. 이 함수가 반환하는 리스트를 쭉 펼쳐서 하나의 리스트로 만든다.
integerList flatMap (x => List(x * 2, x * 3)) // List(2, 3, 4, 6, 6, 9)
1 <= j < i < 5 인 모든 순서쌍 (i, j)를 만든다고 할 때 아래와 같이 활용할 수 있다.
List.range(1, 5).flatMap( i => List.range(1, i) map (j => (i, j)) ) // List((2,1), (3,1), (3,2), (4,1), (4,2), (4,3))
xs foreach f 연산: f는 리스트의 원소를 입력으로 받아 Unit을 반환하는 함수이다.
var sum = 0 List(1, 2, 3, 4, 5).foreach( x => sum += x ) sum // 15
리스트 거르기: filter, partition, find, takeWhile, dropWhile, span
xs filter p: p는 리스트의 원소 한개를 받아 Boolean으로 반환하는 함수이다. p 가 true인 원소만 갖는 새로운 리스트를 반환한다.
List(-1, 0, 1, 2).filter(_ > 0) // List(1, 2)
xs partition p: p는 리스트 원소 한 개를 받아 Boolean으로 반환하는 함수이다. true, false에 해당하는 리스트 두 개를 반환한다.
List(-1, 0, 1, 2).partition(_ > 0) //(List(1, 2),List(-1, 0))
xs find p: 주어진 함수 p를 만족하는 첫 번째 원소를 반환한다. p를 만족하는 x가 존재하면 Some(x)를 반환하고 그렇지 않으면 None을 반환한다.
List(1, 2, 3).find(_ > 0) // Some(1) List(1, 2, 3).find(_ < 0) // None
xs takeWhile p: p를 만족하는 가장 긴 접두사를 반환한다.
List(1, 2, 3, 4, 5).takeWhile(_ < 3) // List(1, 2) List(1, 2, 3, 4, 5).takeWhile(_ > 2) // List()
xs dropWhile p: p를 만족하는 가장 긴 접두사를 제거하고 나머지를 반환한다.
List(1, 2, 3, 4, 5).dropWhile(_ < 3) // List(3, 4, 5) List(1, 2, 3, 4, 5).dropWhile(_ > 2) // List(1, 2, 3, 4, 5)
xs span p: span은 takeWhile, dropWhile을 하나로 묶은 순서 쌍이다.
List(1, 2, 3, 4, 5).span(_ > 3) //(List(),List(1, 2, 3, 4, 5))
리스트 전체: forall, exists
xs forall p: 리스트의 모든 원소가 p를 만족할때 true이다.
xs exists p: 리스트의 원소 중 하나라도 p를 만족하면 true이다.
List(1, 2, 3, 4, 5) forall (_ < 10) List(1, 2, 3, 4, 5) exists (_ == 0)
리스트 폴드: foldLeft, foldRight
xs.foldLeft(z)(op): z를 시작값으로 하여 xs의 원소들을 op 연산자로 연산한다.
List(1, 2, 3, 4, 5).foldLeft(0)(_ + _) // 15 List("This", "is", "scala", "language").foldLeft("")(_ + " " + _) // 출력: " This is scala language" // 위의 표현식은 맨 앞에 공백을 하나 두개 되므로 아래와 같은 방법을 이용할 수 있음. val stringList = List("This", "is", "scala", "language") stringList.tail.foldLeft(stringList.head)(_ + " " + _)
리스트 정렬: sortWith
xs sortWith before: before가 모든 원소쌍에 대해서 true가 되는 리스트를 반환한다.
val massList = List(3, 1, 4, 2, 5) massList.sortWith(_ > _) val massStrings = List("a", "zc", "def", "gh") massStrings.sortWith(_.length > _.length)
8. List 객체의 메서드
List 클래스의 동반 객체에도 많은 메서드들이 정의되어 있다.
원소로부터 리스트 만들기: List.apply
List.apply(1, 2, 3) // List(1, 2, 3)
수의 범위로 리스트 만들기: List.range
List.range(from, until): from 부터 until - 1이 들어간 정수 리스트를 반환한다.
List.range(0, 3) // List(0, 1, 2)
List.range(from, until, step): from 부터 until -1 까지 step 만큼의 간격을 가진 리스트를 반환한다.
List.range(0, 6, 2) // List(0, 2, 4)
균일한 리스트 만들기: List.fill
List.fill(n)(e): e로 채워진 길이 n의 리스트를 만든다. n이 하나 이상 주어지면 다차원의 리스트가 된다.
List.fill(3)('a') // List(a, a, a) List.fill(2, 2)("two") // List(List(two, two), List(two, two))
함수 도표화: List.tabulate
List.tabulate: 제공된 함수와 리스트 인덱스를 이용하여 원소들을 채운다.
List.tabulate(4)(n => n * n) // List(0, 1, 4, 9) List.tabulate(2, 3)(_ * _) // List(List(0, 0, 0), List(0, 1, 2))
여러 리스트 연결하기: List.concat
concat 메서드는 여러 리스트를 연결한다.
List.concat(List(1), List(2), List(3, 4)) // List(1, 2, 3, 4)
9. 여러 리스트를 함께 처리하기
아래와 같은 예제가 있다.
List(1, 2, 3).zip(List(10, 20)).map{ case (x, y) => x * y } // List(10, 40)
두 리스트를 zip으로 묶어 map으로 처리하는 예제인데 여기서 zip은 단점이 있다. 중간 리스트를 생성하기 때문에 비용이 든다. 스칼라에는 lazyZip을 활용할 수 있다. lazyZip은 중간 리스트를 바로 만들지 않는다.
List(1, 2, 3).lazyZip(List(4, 5)).map(_ * _) // List(4, 10)
'스칼라' 카테고리의 다른 글
[스칼라] getter setter (0) 2022.02.23 [스칼라] 컬렉션 (0) 2022.02.23 [스칼라] 케이스 클래스와 패턴 매치 (0) 2022.02.21 [스칼라] 트레이트 (0) 2022.02.20 [스칼라] 스칼라 계층구조 (0) 2022.02.13