[scala] zip, unzip 예시

scala 2016. 9. 12. 11:57


zip 메소드는 리스트를 순환하면서 리스트 튜플을 만든다. (내부적으로 iterator 객체를 포함할 때만 동작한다)


zipWithIndex는 따로 List를 주지 않아도 자동으로 0부터 시작하는 list와 합치도록 해준다. 


Stream from 0 은 0부터 시작하는 리스트를 리턴한다. 


unzip은 만어진 리스트 튜플을 다시 리스트 객체로 쪼개주는 역할을 한다. unzip._1은 다시 원래의 list로 리턴하는 메소드이다. 



val list = List(1,2,3,4,5)
println(list.zipWithIndex)
println(list.zip(Stream from 0))
println(list.zip(Stream from 0).unzip._1)


결과는 다음과 같다.


List((1,0), (2,1), (3,2), (4,3), (5,4))

List((1,0), (2,1), (3,2), (4,3), (5,4))

List(1, 2, 3, 4, 5)



문자열도 동일하게 동작한다. 

scala에서는 메소드르 사용할 때 꼭 내부 함수를 의미하는 .나 괄호를 쓰지 않아도 되는 경우가 있다. 마치 수학 연산 같은 느낌을 주기 위해 다음과 같이 List zip List 형태로 쓸 수 있다.


zip, unzip, toMap 메소드 예시를 실행해 본다.

val zipped = List("A", "B") zip List("C", "D")
println(zipped)
println(zipped.unzip)
println(zipped.toMap)


결과는 다음과 같다.


List((A,C), (B,D))

(List(A, B),List(C, D))

Map(A -> C, B -> D)




만약 엘리먼트가 하나가 더 큰 상황에서 zip메소드를 호출하면 긴 엘리먼트 쪽 데이터는 zip 메소드에서 사용하지 않는다.


val zips = List("A", "B") zip List("C")
println(zips)


결과는 다음과 같다.


List((A,C))




두 개 뿐 아니라 세 개의 튜플을 가지는 리스트 객체를 생성할 수도 있다. 


println((List(1,2,3,4,5), List(0,1,2,3,4)).zipped.toList)
println((List(1,2,3,4,5), List(0,1,2,3,4), List(10,11,12,13,14)).zipped.toList)


결과는 다음과 같다. 

List((1,0), (2,1), (3,2), (4,3), (5,4))

List((1,0,10), (2,1,11), (3,2,12), (4,3,13), (5,4,14))


Posted by '김용환'
,




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



scala에서 main 메소드에서 아규먼트를 처리하고 싶을 때를 위한 예시이다. 


Array[String] 타입이라서 collection api를 사용할 수 있다. 


3개의 변수에 한 번에 할당할 수 있는 expression도 제공한다.

def main(args: Array[String]): Unit = {
args.foreach(arg => println(arg))

println(args(0))
println(args(1))
println(args(2))

val (first, second, third) = (args(0).toInt, args(1).toInt, args(2))
println(first)
println(second)
println(third)
println(third.getClass)

println(args.toList)


}






1

2

test

1

2

test

1

2

test

class java.lang.String
List(1, 2, test)


Posted by '김용환'
,

scala 에서 import 문을 쓸 때, java.lang.* 과 같이 쓸 수 있다. 



import java.lang._



한 번에 필요한 클래스만 import 문으로 쓸 수 있다.


import java.lang.{Float, Double, Integer}




특이한 것은 =>을 이용해서 나름 import rename 기능을 쓸 수 있다.


import java.lang.{Long => JLong}
val scalaLong : Long = 1L
println(scalaLong.getClass.getCanonicalName)

val javaLong : JLong = 1L
println(javaLong.getClass.getCanonicalName)


결과는 다음과 같다. scala long과 java long을 나눠 쉽게 쓸 수 있다.


long

java.lang.Long




또한, 여러 줄에 걸쳐 rename할 필요 없이 한 번에 사용할 수 있다.


import java.util.{Map ⇒ JMap, List ⇒ JList}





자바의 static import 기능은 짧게 static 없이 사용한다.


import java.lang.Math._



rename 기능과 static import을 합쳐서 커스텀 메소드도 만들어 낼 수 있다. 


import java.lang.Class.{ forName => classForName }
classForName("com/google/model/Member")






Posted by '김용환'
,

trait는 자바의 interface와 비슷하지만, 내부 구현상 상속을 받으면서 abstract class 역할도 담당한다. 

sealed trait라는 것이 있어서 trait와 비교했다.



model.scala 라는 파일을 다음처럼 생성했다. 



model.scala

sealed trait Order {
def id: Int
def price: Double
}

trait Member {
def id: Int
def name: String
}

case class MemberOrder(id: Int, price: Double) extends Order

case class MemberObject(id: Int, name: String) extends Member


trait는 해당 파일 또는 다른 파일에서 사용할 수 있지만, sealed trait는 c의 static 처럼 해당 파일에서만 사용할 수 있다. 에러가 발생하지 않는다. 





test.scala 파일

case class AnotherMemberObject(id: Int, name: String) extends Member
// case class AnotherMemberOrder(id: Int, name: Double) extends Order // 에러 발생


Order는 sealed trait이기 때문에 외부 파일에서는 상속받을 수 없다. 


Error:(12, 62) illegal inheritance from sealed trait Order

case class AnotherMemberOrder(id: Int, name: Double) extends Order







 scala 내부에서 사용할 trait 또는 class를 sealed modifier를 사용하고, 외부로 오픈할 클래스를 final case class로 연동하기도 한다.




<List>

sealed trait List[+A] {

}

final case class ::[A](head: A, tl: List[A]) extends List[A]

final case class Nil extends List[Nothing]




<Option>

sealed abstract class Option[+A] extends Product with Serializable {

}


final case class Some[+A](x: A) extends Option[A]



<Try>


sealed abstract class Try[+T] {

}

final case class Success[+T](value: T) extends Try[T] 

final case class Failure[+T](exception: Throwable) extends Try[T]




Posted by '김용환'
,




scala 콜렉션의 map은 변형의 의미를 가진다. collection map을 의미하지 않는다. ;;;


1. map 예시이다. 


예시 1- range의 모든 값에 10을 곱하기

예시 2-List[Int]를 List[String]으로 변환하기

예시 3-List[Int]에 내용 변경하기

예시 4-List[String]에 내용 변경하기

println((1 to 10).map(_*10))

val ints = List(3, 10, 15)
println(ints.getClass)
println(ints)

val strings = ints.map(_.toString())
println(strings)

val variants = ints.map {
x => x * 2 + 1
}
println(variants)

val variants2 = strings.map {
x => x * 2
}
println(variants2)



결과


Vector(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)

class scala.collection.immutable.$colon$colon

List(3, 10, 15)

List(3, 10, 15)

List(7, 21, 31)

List(33, 1010, 1515)




2.  flatten 예시이다.



flatten은 콜렉션을 펼쳐놓는 메소드이다.


예시 1 - List의 List를 List로 변환 

예시 2 - Array의 Array를 List로 변환

예시 3 - List의 List를 List로 변환하면서 동일 값은 하나로 모은다. 

val lists = List(List(1), List(2))
println(lists.flatten)

val arrays = Array(Array(3), Array(4))
println(arrays.flatten.toList)

val colleagues = List(List("Kyle", "Jonathan", "Daisy"), List("Kyle", "Ethan"))
println(colleagues.flatten)
println(colleagues.flatten.distinct)


결과는 다음과 같다.


List(1, 2)

List(3, 4)

List(Kyle, Jonathan, Daisy, Kyle, Ethan)

List(Kyle, Jonathan, Daisy, Ethan)




참고로 None을 flatten을 할 때, none은 다 날려준다.

val list = List(Some(1), None, None, Some(2))
println(list.flatten)

결과

List(1, 2)





collection map에도 flatten 메소드를 적용할 수 있다.


val maps = Map("A" -> 1, "B" -> 2)
println(maps.keys.flatten)


결과는 다음과 같다.


Set(A, B)




참고로, values에 대해서 flatten 메소드를 호출하면 어떻게 될까?


val maps = Map("A" -> 1, "B" -> 2)
println(maps.values) println(maps.values.flatten) // 에러 발생


결과이다. 

MapLike(1, 2)


Error:(20, 25) No implicit view available from Int => scala.collection.GenTraversableOnce[B].

    println(maps.values.flatten)



에러가 발생한다. 이유는 MapLike 클래스라서 flatten을 사용할 수 없다. List로 바꿔서 가든지. 그냥 가든지 해야 한다.



아래 코드는 잘 동작한다. 

val maps = Map("A" -> 1, "B" -> 2)
println(maps.values.toList)





collection map의 values에도 사용할 수 있다.

val maps = Map("A" -> List(1), "B" -> List(2))
println(maps.values.flatten)

결과는 다음과 같다. 


List(1, 2)






3. flatmap 예시이다.


flatmap은 map에 flatten을 합성한 메소드이다.


val lists = List(List(1), List(2))
println(lists.flatMap(x => x.map(_*2)))


결과는 다음과 같다. 


List(2, 4)



flatmap은 map와 flatten을 합친 모양인데, 아래와 같이 테스트해서 동작여부를 볼 수 있다. 먼저 map으로 데이터를 변형한 후, flatten을 사용한다.

println(lists.map(x => x.map(_*2)))
println(lists.map(x => x.map(_*2)).flatten)

결과는 동일하다.


List(2, 4)



Posted by '김용환'
,



scala의 println은 여러 줄을 출력할 수 있는 기능이 있다. 다른 언어에서는 """ 비슷한 multi line 개념이 있어서 참 좋았다. scala에 이 기능이 있어서 편리하다. 


예시 1 - """ """ 만 쓰면 그대로 공백이 나온다.

예시 2 -""" """에 stripMargin 메소드를 사용해서 공백을 정리한다.  (토큰은 디폴트로 | 이다.)

예시 3-stripMargin 메소드에 사용자 정의 토큰을 정리할 수 있다.

예시 4- new line을 empty string으로 치환할 수 있다.

예시 5 - "" 안에 expression을 사용할 수 있다. 


println {
s"""A is
not B"""
}
println

println {
s"""A is
|not B"""
.stripMargin
}
println

println {
s"""A is
@not B"""
.stripMargin('@')
}
println

println {
s"""A is
@not B"""
.stripMargin('@').replaceAll("\n", " ")
}
println

val commands = List("put", "get", "delete")
val number = 3
println {
s"""
|${commands.size}
|${commands.size / number} """
.stripMargin
}



결과



A is

          not B


A is

not B


A is

not B


A is not B



3

1        




Posted by '김용환'
,


튜플과 _1, _2와 match를 이용한 예시이다. 


튜플은 다양하게 생성할 수 있고, 내부 요소를 _1, _2로 만들 수 있다. 튜플의 개수에 따라 실제로 생성되는 클래스가 동적으로 생성된다. 2개의 요소를 가진 튜플은 scala.Tuple2 타입이다. 

tuple을 통해 타입과 값에 대한 match를 실행해봤고, 이를 더 응용해 메소드에서 match를 쓸 수 있도록 테스트했다.

val student = "Kyle" -> 22
// val student = ("Kyle", 22)
println(student._1, student._2)
println(student.getClass)

val result = student._1 match {
case "ABC" => "What?"
case s: String => "String"
}
println(result)
println(testMatch(student._1))



testMatch 코드는 다음과 같다. string의 값을 case문으로 비교한다. 

def testMatch(str : String) = str match {
case "Kyle" => "KK"
case _ => "Aha"
}



결과는 다음과 같다. 


(Kyle,22)

class scala.Tuple2

String

KK


Posted by '김용환'
,