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 '김용환'
,