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})
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 |