jam 블로그

러닝스칼라 7장 - 그 외의 컬렉션 본문

IT Book Study/러닝 스칼라

러닝스칼라 7장 - 그 외의 컬렉션

kid1412 2020. 9. 28. 23:35
728x90

불변의 컬렉션

  • List, Set, Map 은 불변의 컬렉션
  • 코드의 안정성을 높이고 버그를 방지하기 위해 데이터와 데이터 구조는 변경되거나 자신의 상태를 바꿀 수 없다.

가변 컬렉션 생성하기

  • collection.mutable은 자동으로 추가되지 않아서 반드시 포함해야한다.
//불변
collection.immutable.Buffer
collection.immutable.Set
collection.immutable.Map

//가변
collection.mutable.Buffer
collection.mutable.Set
collection.mutable.Map
  • 가변 컬렉션을 이용하면 추가나 변경을 할 수 있다.
  • toList , toSet, toMap 메소드를 이용해서 가변 컬렉션을 불변의 컬랙션으로 만들 수 있다.
  • toBuffer 메소드를 이용해서 불변의 컬렉션을 가변적인 컬렉션으로 변경할 수 있다.
  • 버퍼의 단점이 너무 광범위하다는 것인데 이것을 빌더(builder)로 해

컬렉션 빌더 사용하기

  • Builder는 Buffer를 단순화한 형태로, 할당된 컬렉션 타입을 생성하고 추가연산만 지원
scala> val b = Set.newBuilder[Char]
b = scala.collection.mutable.SetBuilder@3789ad3d

scala> b += 'h'
scala.collection.mutable.SetBuilder@3789ad3d

scala> b ++= List('e', 'l', 'l', 'o')
scala.collection.mutable.SetBuilder@3789ad3d

scala> val helloSet = b.result
helloSet: scala.collection.immutable.Set[Char] = Set(h, e, l, o)
  • 불변의 컬렉션을 전환하기 위해 단지 가변적인 컬렉션을 반복적으로 생성한다면 Builder
  • 빌더는 자신의 타입에 대응하는 불변의 타입이 무엇인지 알고 있음
  • 빌더의 장점?

배열

  • Array는 고정된 크기를 가지며, 내용을 변경할 수 있으며, 인덱스를 가지고 있다.
  • 자바 배열은 toString() 메소드를 재정의 하지 않기 때문에 타입 매개변수와 참조를 출력, 스칼라는 재정의
scala> val colors = Array("red" ,"green", "blue")

scala> colors(0) = "purple"
Array[String] = Array(purple, green, blue)

scala> println("vert purple : " + colors)
vert purple : [Ljava.lang.String;@6f76b33e

val files = new java.io.File(".").listFiles
Array(./.jupyter, ./Untitled.ipynb, ./.ipynb_checkpoints, ./Untitled1.ipynb, ./Untitled2.ipynb)

val scala = files map (_.getName) filter(_ endsWith "ipynb")
Array(Untitled.ipynb, Untitled1.ipynb, Untitled2.ipynb)
  • JVM 코드를 위해 필요한 경우가 아니라면 이 타입을 사용하는 것은 권하지 않는다 왜?

Seq와 시퀀스

  • Seq는 오든 시퀀스의 루트 타입으로 List , Vector도 포함
  • Seq는 인스턴스화 할 수 없지만 List를 생성하는 방법으로 Seq를 호출 할 수 있다.
scala> val inks = Seq('C','M','Y','K')
inks = List(C, M, Y, K)
  • Vector : 색인된 접근을 위해 Array에 의해 지원받는 리스트
  • List : n번쨰 항목에 접근하려면 헤드부터 탐색
  • String : Char 구성 요소를 가지는 시쿼스

스트림

  • Stream 타입은 하나 또는 그 이상의 시작 요소들과 재귀함수로 생성되는 지연(lazy) 컬랙션
  • 스트림의 구성 요소들은 나중에 추출될 떄를 대비하여 캐시에 저장되어 각 요소가 한번만 생성됨을 보장
  • Stream.Empty로 종료될 수 있고 무한히 커질수 있다.
  • List 와 마찬가지로 헤드와 테일로 구성된 재귀적인 데이터 구조
scala> def inc(i: Int) : Stream[Int] = Stream.cons(i,inc(i+1))
scala> def inc(head: Int): Stream[Int] = head #:: inc(head+1)
inc: (i : Int)Stream[Int]

scala> val s = inc(1)
s: Stream[Int] = Stream(1,?)

scala> val l = s.take(5).toList
List(1,2,3,4,5)

scala> s
Stream(1,2,3,4,5,?)

scala> def to(head:Char, end:Char):Stream[Char] = (head > end) match {
    case true => Stream.empty
    case false => head #:: to((head+1).toChar , end)
}
to: (head: Char, end: Char)Stream[Char]

scala> val hexChars = to('A','F').take(20).toList
hexChars = List(A, B, C, D, E, F)

모나딕

  • Iterable 연산 같은 변형 연산을 지원하지만 하나 이상의 요소는 포함할 수 없다.
  • 잠재적 값을 나타내며 추가 연산을 연결하거나 값을 추출하는 안전한 연산 제

Option 컬렉션

  • 크기가 1이 넘지 않는 컬렉션으로 단일값의존재 또는 부재를 나타냄
  • 널(null) 값의 안전한 대체제, NullPointException을 일으킬 가능성이 즐어듬
  • 타입-매개변수화된 컬랙션인 Some 과 빈 컬렉션인 None
  • 함수 결과를 처리하는데 type-safe 방식을 제공
scala> def divid(amt: Double, divisor:Double): Option[Double] = {
           if(divisor == 0) None
           else Option(amt/divisor)
}
divid: (amt: Double, divisor: Double)Option[Double]

scala> val legit = divid(5,2)
legit = Some(2.5)

scala> val illegit = divid(3,0)
illegit = None
  • headOption 과 find 연산
scala> val odds = List(1,3,5)

scala> val events = odds filter (_ % 2 ==0)
events = List()

scala> val firstEven = events.headOption
firstEven = None

scala> val words = List("risible", "scavenger", "gist")

scala> val uppercase = words find (w => w == w.toUpperCase)
Option[String] = None

scala> val lowercase = words find ( w => w == w.toLowerCase)
Option[String] = Some(risible)

Option으로부터 값 추출하기

  • get() 메소드도 있지만 안전하지 않다, None에 get 을 한다면 no such element 에러가 발생하기 때문이다.
  • 누란된 갑슬 처리하는 대체값이 있는 fold나 getorElse를 사용하자
Name Example 설명
fold nextOption.fold(-1)(x => x) Some인 경우 주어진 함수로부터 추출한 값을 None인 경우 시작값을 반환함, 그외 다른 fold나 reduce도 마찬가지
getOrElse nextOption getOrElse 5 Some의 값을 반환하거나 아니면 이름 매개변수의 결과를 반환
orElse nextOption orElse nextOption 실제로 값을 추출하지는 않지만 None 인 경우 값을 채우려함
매치 표현식 nextOption match { case Some(x) => x ; case None => -1 값이 있는 경우 매치표현식으로 그 값을 처리함
  • null 을 쓰는 것보다 더 안전한 방법, 그 이유는 모든 널 포인트 에러를 방지 못하기 때문

Try 컬렉션

  • util.Try 컬렉션은 에러 처리를 컬랙션 관리로 바꿔 놓는다.
  • 스칼라에서는 예외(Exception)을 발생시켜 에러를 일으킬 수 있다.
  • 예외를 발생시키기 위해서는 Exception 키워드와 함께 throw를 사용
scala> def loopAndFail (end:Int , failAt: Int ) : Int = {
        for ( i <- 1 to end ) {
           println(s"$i) " )
           if (i == failAt ) throw new Exception("Too many iterations")
        }
        end
      }
  • catch 보단 util.Try 만 사용하는 것을 추천, 그 이유는 에러를 처리하기에 더 안전하고 완전한 모나딕 접근법
  • util.Try 는 Success 와 Failure 을 가지고 있다.
scala> val t1 = util.Try( loadAndFail(2,3))
1)
2)
Success(2)

scala> val t2 = util.Try ( loadAndFail(4,2))
1)
2)
Failure(java.lang.Exception: Too many iterations)
  • Try 에러 처리 메소드
Name Example 설명
flatMap nextError flatMap { _ => nextError } Success인 경우 util.Try를 반환하는 함수를 호출함으로써 현재의 반환값을 새로운 내장된 반환값(또는 예외에 매핀함, 우리의 ‘nextError’ 데모함수는 입력값을 취하지 않기 때문에 우리는 현재 Success로 부터 사용하지 않는 입력값을 나타내는 언더스코어를 사용
foreach nextError foreach(x => println(“success!” + x)) Success인 경우 주어진 함수를 한 번 실행하고, Failure 일 때는 실행하지 않
getOrElse nextError getOrElse 0 Success에 내장된 값을 반환하거나,Failure인 경우 이름에 의한 매개변수의 결과를 반환함
orElse nextError orElse nextError flatMap의 반대되는 메소드, Failure인 경우 그 역시Try를 반환하는 함수를 호출함.orElse로 어쩌면 Failure를 Success로 전환 할 수 있음
toOption nextError.toOption Try를 Option으로 전환하여 Success는 Some으로 Failure는 None이 됨. 내장된 Exception을 잃게 된다는 단점이 있음
map nextError map (_ * 2) Success인 경우 새로운 값에 내장된 값을 매핑하는 함수를 호출함
매치표현식 nextError match { case util.Success(x) => x; case util.Failure(error) => -1 } Success를 (’x’에 저장된) 반환값으로 또는 Failure를 (’error’에 저장된) 예외로 처리하기 위해 매치 표현식을 사용함.
아무일도 하지않음 nextError 가장 쉬운 에러 처리 방식으로, 내가 개인적으로 선호하는 방식임. 이 방식으로 단순히 예외가 잡히거나,현재의 애플리케이션을 종료시킬 때까지 호출 스택을 타고 전파되도록 그대로 둠. 이 방식은 민감한 경우에 사용하면 문제가 크겠짐나, 발새한 예왼느 결코 무시되지 않음
scala> val input = " 123 "
input = " 123 "

scala> val result = util.Try(input.toInt) orElse util.Try(input.trim.toInt)
result = Success(123)

scala> result foreach { r => println(s"Parsed '$input' to $r!" )}
Parsed ' 123 ' to 123!

scala> val x = result match {
           case util.Success(x) => Some(x)
           case  util.Failure(ex) => {
               println(s"$input")
               None
           }
       }

x = Some(123)

퓨처 컬렉션

  • 백그라운드 작업을 개시하는 concurrent.Future
  • Option과 Try 와는 달리 즉시 사용하지 못할수 있다.
  • 퓨처를 함수로 호출하면 현행 스레드와는 별개의 스레드에서 그 함수를 실행시킨다.
scala> import concurrent.ExecutionConscala 3.Implicits.global
scala> val f = concurrent.Future{println("hi")}
Message: <console>:25: error: Cannot find an implicit ExecutionConscala 3.

scala>  val f = concurrent.Future{Thread.sleep(5000);println("hi")}
scala> println("waiting")
waiting
hi
  • 퓨처의 작업이 완료될때 실행할 콜백 함수를 설정 가능
  • 백그라운드 작업이 끝날때까지 main 스레드를 차단하고 기다리는 기능

비동기식 퓨처 연산

Name Example 설명
failbackTo nextFtr(1) failbackTo nextFtr(2) 두번째 퓨쳐를 첫번째 연결하고 새로운 종합적인 퓨처를 반환함. 첫 번째 퓨처가 성공적이지 않다면 두 번째 퓨처가 호출
flatMap nextFtr(1).flatMap(int => nextFtr()) 두번째 퓨처를 첫 번째에 연결하고 새로운 종합적인 퓨처를 반환함. 첫 번째가 성공적이라면 그 반환 값이 두 번째를 호출하는데 사용됨
map nextFtr(1) map (_ * 2) 주어진 함수를 퓨처에 연결하고 새로운 종합적인 퓨처를 반환함. 퓨처가 성공적이라면 그 반환 값이 해당 함수를 호출할 때 사용됨
onComplete nextFtr() onComplete { _ getOrElse 0 } 퓨처의 작업이 완료된 후 주어진 함수가 값 또는 예외를 포함한 Try를 이용하여 호출됨
onFailure nextFtr() onFailure { case _ => “Error!” } 퓨처의 작업이 예외를 발생시키면 주어진 함수는 그 예외를 가지고 호출됨
onSuccess nextFtr() onSuccess { case _ => s“Got $x” } 퓨처의 작업이 성공적으로 완료되었으면 주어진 함수는 그 반환값을 가지고 호출
Future.sequence concurrent.Future sequence List(nextFtre(1),nextFtr(5)) 주어진 시퀀스에서 퓨처를 벙행으로 실행하여 새로운 퓨처를 반환함. 시퀀스 내의 모든 퓨처가 성공하면 이들의 반환값의 리스트가 반환됨. 그렇지 않으면 그 시퀀스내에서 처음으로 발생한 예외가 반환됨
import concurrent.ExecutionConscala 3.Implicits.global
import concurrent.Future
import scala.io.Source
def cityTemp(name:String): Double = {
    val url = "http://api.openweathermap.org/data/2.5/weather"
    val cityUrl = s"$url?&APPID=72bf27b20bf867de6236aca3a257e0d1&q=$name"
    val json = io.Source.fromURL(cityUrl).mkString.trim
    val pattern = """.*"temp":([\d.]+).*""".r
    val pattern(temp) = json
    temp.toDouble
}

val cityTemps = Future sequence Seq(
    Future(cityTemp("Seoul")), Future(cityTemp("Jeju"))
)

cityTemps onSuccess {
  case Seq(x,y) if (x>y) => println(s"Seoul is warmer: $x K")
  case Seq(x,y) if (y>x) => println(s"Jeju is warmer: $x K")
}

동기식 퓨처 처리

  • 백그라운드 스레드가 완료되기를 기다리는 동안 메인 스레드를 차단
  • concurrent.Await.result() 사용
  • 주어진 시간보다 빠르게 처리되면 결과가 반환되지만 아니라면 java.util.concurrent.TimeoutException 발생
scala> def nextFtr( i: Int = 0 ) = Future {
    def rand(x:Int) = util.Random.nextInt(x)
    Thread.sleep(rand(5000))
    if(rand(3)>0) (i +1) else throw new Exception
}
nextFtr: (i: Int)scala.concurrent.Future[Int]

scala> import concurrent.duration._

scala> val amount = concurrent.Await.result(nextFtr(5), maxtime)
amount: Int = 6

scala> val amount = concurrent.Await.result(nextFtr(5), maxtime)
java.lang.Exception
Comments