Map을 다루는 예시이다.

val m = Map(1 -> "a", 2 -> "b", 3 -> "c")


transform메소드를 호출한다. transfrom의 정의문은 다음과 같다. 매개변수는 (A,B)  => C이다.


def transform[C, That](f: (A, B) => C)


다음을 실행한다.

println(m.transform((k,v) => v.toUpperCase))


결과는 다음과 같다. value가 모두 대문자가 되었다


Map(1 -> A, 2 -> B, 3 -> C)






이제, Map의 map 메소드를 호출한다. map 메소드의 원형은 다음과 같다. 매개변수는 A=>B 이다. 



def map[B, That](f: A => B)



함수의 원형에 따라 m.map을 호출한다. (Map.map 호출이라 굉장히 어색하긴 하지만..)

println(m.map(x => x + "y"))


결과를 보면, 다음처럼 y가 튜플 뒤에 붙었다.


List((1,a)y, (2,b)y, (3,c)y)




이제 괄호문과 case를 사용해본다. 그러면 (k,v)로 확인해볼 수 있다. value를 대문자로 바꿔 리스트로 리턴하라는 명령이다. 


println(m.map {case (k,v) => v.toUpperCase})


결과는 다음과 같다.


List(A, B, C)




이제는 키의 값만 바꿔 1을 더해, 키 리스트를 얻는다.



println
(m.map {case (k,v) => k + 1})

결과는 다음과 같다.

List(2, 3, 4)





key 값 연산과 value 값 연산을 합쳐, 이제는 Map의 key의 값에 숫자를 더하고, value를 대문자로 변환하는 map을 만든다.


println(m.map {case (k,v) => (k + 5, v.toUpperCase)})

결과는 다음과 같다.



Map(6 -> A, 7 -> B, 8 -> C)






이제 mapValues를 사용한다. mapValues의 메소드 원형은 다음과 같다. 매개변 수가 B => C 이다. 



override def mapValues[C](f: B => C): Map[A, C]


간단하게 테스트해본다. map의 value에 연산을 하고 Map을 리턴한다.

println(m.mapValues(_ capitalize))

println(m.mapValues(_ + "xxx"))


결과는 다음과 같다. 


Map(1 -> A, 2 -> B, 3 -> C)

Map(1 -> axxx, 2 -> bxxx, 3 -> cxxx)












* 중요한 포인트는 이제 시작이다. 



Map.transform과 Map.mapValues에는 비슷해 보이지만, 차이점이 있다. 예시를 테스트하면서 메소드 원형을 살펴본 이유가 그런 배경이다. Map[A, C]를 리턴한다는 점에서 동일하다.  요약하면 다음과 같을 것 같다.



def map[B](f: ((A, B)) ⇒ B): Map[B]

def map[B](f: (A) ⇒ B): Set[B]

def map[B](f: (A) ⇒ B): Seq[B]

def transform[C](f: (A, B) => C) : Map[A, C]

def mapValues[C](f: (B) ? C): Map[A, C]






Map.transform 구현 코드는 다음과 같다. 결과를 리턴한다



def transform[C, That](f: (A, B) => C)
        (implicit bf: CanBuildFrom[This, (A, C), That]): That = {
val b = bf(repr)
for ((key, value) <- this) b += ((key, f(key, value)))
b.result()
}




Map.mapValues의 원형은 다음과 같다.



override def mapValues[C](f: B => C): Map[A, C] = 
                    new MappedValues(f) with DefaultMap[A, C]


MapValues 클래스의 원형은 다음처럼 되어 있다.  self를 호출하고 map 메소드에 f를 적용한다. 또 한 번의 연산이 발생한다. 


protected class MappedValues[C](f: B => C) extends AbstractMap[A, C] with DefaultMap[A, C] {
override def foreach[U](g: ((A, C)) => U): Unit = for ((k, v) <- self) g((k, f(v)))
def iterator = for ((k, v) <- self.iterator) yield (k, f(v))
override def size = self.size
override def contains(key: A) = self.contains(key)
def get(key: A) = self.get(key).map(f)
}



좀 더 자세한 테스트를 위해서 mutable Map을 통해 다음 코드를 실행한다. 


val newMap = collection.mutable.Map(1 -> 1, 2 -> 2)
println(newMap)
println(newMap eq newMap.mapValues(_+1))
println(newMap)
println()

println(newMap)
println(newMap eq newMap.transform((k, v) => v + 1))
println(newMap)
println()


결과는 다음과 같다. 원래 Map 값이 바뀌었다.


Map(2 -> 2, 1 -> 1)

false

Map(2 -> 2, 1 -> 1)


Map(2 -> 2, 1 -> 1)

true

Map(2 -> 3, 1 -> 2)



(참고로 Immutable Map에서 mapValues나 transform 메소드를 호출할 때 원본 Map은 바뀌지 않았다!!)



api 철학이 여기에서 보이는데, mapValues를 실행하고 난 뒤, 원래 Map은 바뀌지 않고 코드에 있는 대로 뷰만 리턴한다. 하지만, tranform을 실행하면 원래 Map을 바꾼다. 이상하다고 볼 수 있지만, java 언어에서도 가끔씩 볼 수 있는 api라서, 철학 차이 정도로 생각하고 있다. 


상황에 따라서는 분명 데이터와 속도 이슈가 좀 차이가 나기 때문에 이슈라 생각할 수도 있을 것 같다. 



이런 이슈로, mapValues 메소드를 호출하면 MapView라는 클래스를 만들자는 의견이 나왔고 scala 2.13.0 RC1부터 반영될 것 같다.


https://issues.scala-lang.org/browse/SI-4776





[참고]

http://stackoverflow.com/questions/25635803/difference-between-mapvalues-and-transform-in-map

http://blog.bruchez.name/2013/02/mapmap-vs-mapmapvalues.html





'scala' 카테고리의 다른 글

[scala] Array 예시  (0) 2016.09.12
[scala] zip, unzip 예시  (0) 2016.09.12
[scala] groupBy, distinct, sortWith, sortBy 예시  (0) 2016.09.09
[scala] grouped와 sliding  (0) 2016.09.09
[scala] main 메소드 argument 처리  (0) 2016.09.08
Posted by '김용환'
,



groupBy 메소드는 Traversable 트레이트의 자식 클래스로, 그루핑할 때 사용한다. 



예시로 빈도 별로 뽑고 싶다면 다음처럼 코드를 실행한다.


val names = List("Jackson", "Peter", "Jax", "Karl", "Jackson",
                 "Jackson", "Jax")
println(names.groupBy(x => x.toString))



람다식 대신 _를 활용할 수 있다.

val names = List("Jackson", "Peter", "Jax", "Karl",
"Jackson", "Jackson", "Jax")
println(names.groupBy(_.toString))



결과는 다음과 같다.


Map(Karl -> List(Karl), Jax -> List(Jax, Jax), Jackson -> List(Jackson, Jackson, Jackson), Peter -> List(Peter))



엘리먼트의 length 별로 그룹핑할 수 있다. groupBy의 결과 타입은 Map이다. 


val names = List("Jackson", "Peter", "Jax", "Karl",
"Jackson", "Jackson", "Jax")
println(names.groupBy(_.length))

결과는 다음과 같다.


Map(5 -> List(Peter), 4 -> List(Karl), 7 -> List(Jackson, Jackson, Jackson), 3 -> List(Jax, Jax))






같은 단어라면 보이지 않도록 distinct 메소드를 사용한다.

val names = List("Jackson", "Peter", "Jax", "Karl",
"Jackson", "Jackson", "Jax")

val unsorted = names.distinct.groupBy(_.length)
println(unsorted)


결과는 다음처럼 깔끔해졌다.


Map(5 -> List(Peter), 4 -> List(Karl), 7 -> List(Jackson), 3 -> List(Jax))





하지만 length 별로 sorting이 되어 있지 않다. 이를 sort해보자.

이를 위해서는 바로 변환이 안된다. 먼저 Seq(ArrayBuffer)로 변환한 후, 다시 map으로 만든다. 



Map을 Seq로 변환한후 sort한다. sort할 때 sortBy를 써서 1 번째 값(length)로 sort가 되게 한다.

sortBy 말고도 sortWith도 사용할 수 있다. sortWith는 자바와 compare 메소드와 비슷하다. 


그리고, _*를 사용해서 Seq를 ListMap으로 변환한다. 

val names = List("Jackson", "Peter", "Jax", "Karl",
"Jackson", "Jackson", "Jax")
val unsorted = names.distinct.groupBy(_.length)
println(unsorted)

//val sorted = unsorted.toSeq.sortBy(_._1)
val sorted = unsorted.toSeq.sortWith(_._1 < _._1)
println(sorted)

val map = ListMap(sorted:_*)
println(map)



결과는 다음과 같다.


Map(5 -> List(Peter), 4 -> List(Karl), 7 -> List(Jackson), 3 -> List(Jax))

ArrayBuffer((3,List(Jax)), (4,List(Karl)), (5,List(Peter)), (7,List(Jackson)))

Map(3 -> List(Jax), 4 -> List(Karl), 5 -> List(Peter), 7 -> List(Jackson))












마법의 키 sorted:_*가 궁금할 것이다.

sortBy, sortWith의 리턴 타입은 튜플을 엘리먼트로 갖는 Seq[(String, Int)]이다.


실행해보면, 아래와 같은 type mismatch 에러가 발생한다.


val
map = ListMap(sorted)

Error:(26, 23) type mismatch;

 found   : Seq[(Int, List[String])]

 required: (?, ?)

    val map = ListMap(sorted)





따라서, ListMap의 생성자인 apply 메소드를 쫓아가면, GenMapFactory의 apply 메소드를 만나는데, 튜플의 가변 인자:(A, B)*를 받도록 되어 있다. 그래서 _*로 만들어 낸 것이다.


def apply[A, B](elems: (A, B)*): CC[A, B] = (newBuilder[A, B] ++= elems).result()


특이하긴 하지만, scala 공식 문서에도 나와 있는 내용이기도 하다. 

http://docs.scala-lang.org/tutorials/FAQ/finding-symbols.html

f(xs: _*) // Sequence xs is passed as multiple parameters to





다시 groupBy로 넘어와서 case를 쓰는 예시를 사용한다.


val list = List(1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9)

val g = list.distinct.groupBy {
case num if (num % 2 == 0) => "짝수"
case _ => "홀수"
}


println(g)


결과는 다음과 같다. 


Map(짝수 -> List(2, 4, 6, 8), 홀수 -> List(1, 3, 5, 7, 9))



이번에는 숫자 리스트를 groupBy해서 숫자 엘리먼트별로  몇 개가 존재하는지 살펴본다. 모든 map 값에 _.size를 적용해서 그 키에 대한 새로운 맵을 구성한다. 
val list = List(1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9)
val g = list.groupBy(x => x).mapValues(_.size)

println(g)
결과는 다음과 같다.

Map(5 -> 1, 1 -> 2, 6 -> 1, 9 -> 3, 2 -> 1, 7 -> 1, 3 -> 1, 8 -> 1, 4 -> 1)



sort를 해보고 싶다면, toSeq.sortBy(_._1):_*를 붙이면 된다.
val list = List(1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9)
val g = ListMap(list.groupBy(x => x).mapValues(_.size).toSeq.sortBy(_._1):_*)
println(g)

결과는 다음과 같다.



Map(1 -> 2, 2 -> 1, 3 -> 1, 4 -> 1, 5 -> 1, 6 -> 1, 7 -> 1, 8 -> 1, 9 -> 3)



Posted by '김용환'
,

[scala] grouped와 sliding

scala 2016. 9. 9. 11:39





grouped 메소드는 콜렉션 엘리먼트를 일정의 크기로 나누고,

sliding 메소드는 고정 블럭 블록(fixed size block) 나눠, sliding window가 된다. 

둘다 리턴 클래스는  scala.collection.Iterator이다. 



아래 예시에서 grouped는 예상하는 것처럼 3개식 나누면, List(1,2,3), List(4,5) 이렇게 나눠진다.

그러나 sliding은 List(1,2,3)과 List(2,3,4)로 나눈다.

val list = List(1,2,3,4,5)
val g = list.grouped(3)
println(g.next())
println(g.next())

val s = list.sliding(3)
println(s.next)
println(s.next)


결과는 다음과 같다.


List(1, 2, 3)

List(4, 5)


List(1, 2, 3)

List(2, 3, 4)




다른 예시를 봐야 더 이해가 된다.

grouped는 3 개씩 칼처럼 나누지만, sliding은 순서대로 3개씩 묶어준다. 


val list = List(1,2,3,4,5,6,7)
val g = list.grouped(3)
println(g.next())
println(g.next())
println(g.next())
println

val s = list.sliding(3)
println(s.next)
println(s.next)
println(s.next)
println(s.next)
println(s.next)

결과는 다음과 같다.


List(1, 2, 3)

List(4, 5, 6)

List(7)


List(1, 2, 3)

List(2, 3, 4)

List(3, 4, 5)

List(4, 5, 6)

List(5, 6, 7)






Posted by '김용환'
,