ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스칼라] 리스트
    스칼라 2022. 2. 22. 17:37
    
    // 
    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

    댓글

Designed by Tistory.