관리 메뉴

HAMA 블로그

스칼라 강좌 (20) - 고차함수들 (zip,map,flatmap등) 본문

Scala

스칼라 강좌 (20) - 고차함수들 (zip,map,flatmap등)

[하마] 이승현 (wowlsh93@gmail.com) 2016.08.14 15:48


List 활용


*  List 는 스칼라에서 가장 많이 사용되는 데이터 구조일 겁니다.

* 스칼라의 리스트는 디폴트로 불변형입니다.

* 스칼라의 리스트 타입은 공변적입니다.


길이 구하기

val a = List (1,2,3) 

a.length  // 3 


*배열과 달리 리스트의 length 는 비교적 비싼 연산입니다. 

a.isEmpty 를 a.length == 0 으로 사용하지 마세요. 


양 끝에 접근하기

val a = List ('a','b','c','d')

a.head     // 'a'  처음

a.last       //  'd'  마지막

a.tail       // List('b', 'c','d')  처음 제외 나머지  

a.init      // List('a','b','c')    마지막 제외 나머지 


리스트 뒤집기 

val a = List ('a',b','c','d')

a.reverse    // List ('d','c','b','a')  * 주의할것은 새로운 리스트가 생긴다는 점이다. 리스트는 불변이니깐~ 


drop / take / splitAt

val a = List ('a',b','c','d')

a.take(2)    // List ('a','b') 
a.drop(2)   // List ('c',d')

a.splitAt(2)  // (List('a','b'), List('c','d'))


apply / indices

val a = List ('a',b','c','d')

a.apply(2)   // 'c'      * 배열에 비해 불리하다.

a.indices    // Range (0,1,2,3)   * 리스트에서 유효한 모든 인덱스의 리스트를 반환 


flatten

val a = List ( List(1,2), List(3), List(4,5) )

a.flatten    // List (1,2,3,4,5)   * 리스트의 리스트일 경우에 한 해 하나로 반듯하게 펼친다.


zip / zipWithIndex

val a = List (1,2,3)  

val b = List('a','b','c') 

a.zip(b)     // List( (1,'a'), (2,'b') , (3,'c') )   * 두 리스트를 순서쌍으로 묶는다.

b.zipWidhIndex   // List ( ('a',0), ('b',2), ('c', 3) )  * 리스트의 원소를 인덱스와 함께 순서쌍으로 묶는다.


toString / mkString

val a = List ('a','b','c','d')

a.toString   // List(a, b, c, d)  * 리스트 자체를 표준 문자열로 만든다. 

a.mkSring( "[" , "," , "]" )   // [a,b,c,d,e]    * 리스트의 앞 , 중간, 뒤에 들어갈 문자를 정해서 출력한다.



toArray / copyToArray

val a = List ('a','b','c','d')

val arr = a.toArray   // Array(a,b,c,d)   * array로 변환

arr.toList   // List (a,b,c,d)   * List 로 변환


List  클래스의 고차 메소드


map 

val a = List (1,2, 3)

a.map(_ + 1)   //   List (2,3,4) 


flatMap

val words = List ( "the" , "quick" , "brown" , "fox" )

words.map(_.toList)      // List (List (t,h,e) , List (q,u,i,  .........

words.flatMap(_.toList)   //  List (t,h,e,q,u,i ...........



map  과 flatMap의 차이

scala> val fruits = Seq("apple", "banana", "orange")
fruits: Seq[java.lang.String] = List(apple, banana, orange)

scala> fruits.map(_.toUpperCase)
res0: Seq[java.lang.String] = List(APPLE, BANANA, ORANGE)

scala> fruits.flatMap(_.toUpperCase)
res1: Seq[Char] = List(A, P, P, L, E, B, A, N, A, N, A, O, R, A, N, G, E)

map  과 flatMap의 차이2  (이 특성은 너무너무너무 중요하다. 별표3개)

def toInt(s: String): Option[Int] = {
    try {
        Some(Integer.parseInt(s.trim))
    } catch {
        // catch Exception to catch null 's'
        case e: Exception => None
    }
}

scala> val strings = Seq("1", "2", "foo", "3", "bar")
strings: Seq[java.lang.String] = List(1, 2, foo, 3, bar)

scala> strings.map(toInt)
res0: Seq[Option[Int]] = List(Some(1), Some(2), None, Some(3), None)

scala> strings.flatMap(toInt)
res1: Seq[Int] = List(1, 2, 3)

scala> strings.flatMap(toInt).sum
res2: Int = 6

map 은 타입을 감싼 타입을 그대로 내보내기 때문에 None 도 내보내지지만, flatMap 은 감싼 타입을 벗겨내기 때문에 None은 없어진다. 이 중요한 특성을 이용해서 예러(예외)가 없는 결과값을 연속적으로(함수합성)으로 사용하는게 가능해진다.  

flatMap의 추가 특징1

scala> val map = Map(1 -> "one", 2 -> "two", 3 -> "three")
map: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two, 3 -> three)

scala> 1 to map.size flatMap(map.get)
res0: scala.collection.immutable.IndexedSeq[java.lang.String] = Vector(one, two, three)


flatMap의 추가 특징2

flatMap 은 map 이랑 flatten 을 합해놓것과 같다. 예를들어 List[List[Int]] 를 List[Int] 로 바꿀려면 밑에 처럼 flatten 을 쓰면 된다.

  1. val ints = List(List(1,2,3), List(4,5))
  2. ints.flatten // => this returns List(1,2,3,4,5)

그렇다면 List[List[Int]] 를 List[String] 으로 바꿀려면 어떻해야 할까? flatMap 을 쓰면된다.

  1. scala> val ints = List(List(1,2,3), List(4,5))
  2. ints: List[List[Int]] = List(List(1, 2, 3), List(4, 5))

  3. scala> ints.flatMap(_.map(_.toString))
  4. res0: List[java.lang.String] = List(1, 2, 3, 4, 5)

flatMap은 모나드 이해라든지 함수형에서 매우 중요하므로 아래 2개의 글도 읽어볼것 


여러개의 Future를 반환하는 함수를 List[Future[Item]] 이런식으로 얻는게 아니라, 엮어서 1개의 Future 로 모두 계산될 수 있는 Future[List[Item]]을 얻을 수 있는 거 같은 ~~ 느낌적인 느낌?? 오시나요? 



foreach

val n = List ( 1,2,3,4 )

var sum = 0

n.foreach( sum += _)    //  sum 은 15   * 결과 자체는 Unit이다.


filter

val n = List ( 1,2,3,4 )

n.filter( _ % 2 == 0)    // List (2,4)    * 걸러서 버린다.


partition

val n = List ( 1,2,3,4 )

n.partition( _ % 2 == 0)    // ( List (2,4) , List(1,3) )   * 걸러서 나눈다.


find

val n = List ( 1,2,3,4 )

n.find( _ % 2 == 0)    // Some(2)    * 만족하는 첫번째 원소 반환한다


sortWith

val n = List ( 1,-3,4,2,6 )

n.sortWith ( _ < _ )     // List (-3,1,2,4,5)      *원소를 정렬한


Reducing / Folding / Scanning 

맵이 요소들에 어떤 변화를 가하여 전체 요소들을 개별적으로 바꾸어 주는 역할이라면 
이것은 어떤 요소롤 가득찬 자료구조의 합쳐서 (더하거나 빼거나 등등)  단일 결과를 얻고 싶을때 사용한다. 

reduceLeft

List(1,7,2,9).reduceLeft (_ - _) 

는  ((1 - 7 ) - 2 ) - 9  = -17   이다. 

(_ - _) 여기의 왼쪽 _  이 누적값이고 오른쪽 _ 은 각 요소들이다. 

reduceRight

List(1,7,2,9).reduceRight(_ - _) 는 컬렉션의 마지막에서 시작한다.
즉 1 - ( 7 - ( 2 - 9) ) = -13

(_ - _) 여기의 오른쪽 _  이 누적값이고 왼쪽 _ 은 각 요소들이다. 


foldLeft


folding  은 reducing 하고 비슷하며 , 단지 처음 시작할 원소를 따로 추가해준다.

val numbers = List(1,7,2,9)

scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n) res0: Int = 19

0은 시작 값이고 m은 값을 누적하는 변수 역할을 한다. n 은 물론 리스트의 요소이다. 
0 + 1 + 7 + 2 + 9  이고

(0 /: List(1,7,2,9)) (_+_) 이렇게 쓸 수도  있다. 


사실 이런 예제라면  그냥 sum 을 사용하면 된다.






0 Comments
댓글쓰기 폼