일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- webhacking
- hacking
- flask
- c++
- BOF 원정대
- 딥러닝
- 백엔드
- 챗GPT
- backend
- 리눅스
- 러닝 스칼라
- deep learning
- 경제
- hackthissite
- Javascript
- php
- 러닝스칼라
- hackerschool
- 파이썬
- mysql
- Web
- Shellcode
- BOF
- 인공지능
- ChatGPT
- Python
- Scala
- Linux
- 웹해킹
- c
- Today
- Total
jam 블로그
러닝스칼라 6장 - 보편적인 컬렉션 본문
컬렉션(collection)
프레임워크는 배열, 리스트, 맵, 집합, 트리와 같이 주어진 타입을 가지는 하나 또는 그 이상의 값을 수집하는 데이터 구조를 제공한다. 1. 성능이 좋고, 객체지향적, 타입-매개변수화 - 자바 컬렉션 라이브러리 1. jvm이기 때문에 자바 컬렉션을 스칼라에서 활용 가능
스칼라는 여러 이유로 컬렉션을 재정의함 1. 고차함수 이용 (map, filter, reduce) 1. mutable/immutable 타입의 계층 구조
리스트, 집합, 그리고 맵
List 타입
: immutable 단반향 연결 리스트
scala> val numbers = List(32, 95, 24, 21, 17)
numbers: List[Int] = List(32, 95, 24, 21, 17)
scala> val colors = List("red", "green", "blue")
numbers: List[String] = List(red, green, blue)
scala> println(s"I have ${colors.size} colors: $colors")
I have 3 colors: List(red, greem, blue)
모든 컬렉션은 size
메소드롤 통해서 포함된 데이터의 개수를 반환 가능하다.
컬렉션은 초기화때 사용했던 데이터의 타입을 기억하여 타입-매개변수화 될 수 있다.
scala> val colors = List("red", "green", "blue")
numbers: List[String] = List(red, green, blue)
scala> colors.head
res0: String = red
scala> colors.tail
res1: List[String] = List(green, blue)
scala> colors(1)
res2: String = green
scala> colors(2)
res2: String = blue
head와 tail메소드를 이용하여 리스트의 첫번쨰 요소와 나머지 요소를 반환할 수 있다.<collect>(index)
를 통하여 0번째부터 컬렉션의 인덱스의 요소를 반환할 수 있다.
for 루프를 이용하여 컬렉션안의 요소를 순회 할 수 있다.
scala> val numbers = List(32, 95, 24, 21, 17)
numbers: List[Int] = List(32, 95, 24, 21, 17)
scala> val total=0; for(i <- numbers) {total += i}
total: Int = 189
고차함수 이용
스칼라의 컬렉션은 반복하고(iterate), 매핑하고(map,) 축소하기(reduce, fold)위하여 고차함수를 이용
scala> val colors = List("red", "green", "blue")
numbers: List[String] = List(red, green, blue)
scala> colors.foreach( (c: String) => println(c) )
red
green
blue
scala> val sizes = colors.map( (c: String) => c.size )
List[Int] = List(3, 5, 4)
scala> val numbers = List(32, 95, 24, 21, 17)
numbers: List[Int] = List(32, 95, 24, 21, 17)
scala> val total = numbers.reduce( (a: Int, b:Int) => a + b )
total: Int = 189
- foreach는 프로시져를 취하고, 해당 프로시져에 리스트의 요소를 주압
- map은 리스트의 요소를 다른 값이나 타입으로 전환하는 함수를 취함
- reduce는 리스트의 ㅇㅛ소를 단일 항목으로 결합하는 함수를 취함
Set 타입
: immutable, 순서 없음, List와 동일연산 지원
scala> val unique = Set(10, 20, 30, 20, 20, 10)
unique: scala.collection.immutable.Set[Int] = Set(10, 20, 30)
scala> val sum = unique.reduce( (a: String, b: Int) => a + b )
sum: Int = 60
Map 타입
: immutable, key-value, 해시맵, 딕셔너리, 연관배열등으로 알려짐
- key는 유일키로 value와 연결된다.
- Map을 생성 할때 kv쌍을 튜플로 기술하면 된다.
- 키와 값의 관계는 -> 연산자로 정의된다.
scala> val colorMap = Map("red" -> 0xFF0000, "green" -> OxFF00, "blue" -> 0xFF)
colorMap: scala.collection.immutable.Map[String, Int] = Map(red -> 16711680, green -> 65280, blue -> 255)
}
scala> val redRGB = colorMap("red")
redRGB: Int = 16711680
scala> val hasWhite = colorMap.contains("white")
hasWhite: Boolean = false
scala> for (pairs <- colorMap) { println(pairs) }
(red, 16711680)
(green, 65280)
(blue, 255)
리스트의 종류
- 일반적인 리스트 -
List(1,2,3)
- 중첩된 리스트 -
List(List(1,2), List(3), List(4,5))
- tuple 리스트 -
List(('A',1), ('B', 2))
- 등 등 일관성 있는 매개변수 추론이 되는 요소의 리스트르 생성가능
isEmpty
메서드를 이용하여 리스트의 값 소유여부를 측정 가능하다.
scala> val primes = List(2, 3, 5, 7, 11, 13)
primes: List[Int] = List(2, 3, 5, 7, 11, 13)
scala> var i = primes
i: List[Int] = List(2, 3, 5, 7, 11, 13)
scala> while(! i.isEmpty) { print(i.head + ", "); i = i.tail}
2, 3, 7, 11, 13
리스트의 마지막을 검출하기 위해 isEmpty를 사용할 수 있지만 다른 방법으로 Nil인스턴스를 활용할 수 있다.
모든 리스트는 그 종점으로 Nil으로 종료된다.
scala> val primes = List(2, 3, 5, 7, 11, 13)
primes: List[Int] = List(2, 3, 5, 7, 11, 13)
scala> var i = primes
i: List[Int] = List(2, 3, 5, 7, 11, 13)
scala> while(i != Nil) { print(i.head + ", "); i = i.tail}
2, 3, 7
Nil은 List[Nothing]의 싱글턴 인스턴스, Nothing은 모든 스칼라 리스트에 호환되기 때문에 종점으로 안전히 사용가능하다.
새로운 빈 리스트를 생성하면 새로 생긴 인스턴스 대신 Nil을 반ㄹ환한다.
Nil은 immutable 데이터 구조기때문에 근본적으로 빈 리스트 인스턴스와 차이가 없다.
scala> val l = List[Int] = List()
l: List[Int] = List()
scala> l == Nil
res0: Boolean = true
생성 연산자
Nil의 속성을 이용하여 생성 연산자를 이용한 리스트를 생성 할 수 있다.
오른쪽-결합형(right-associative)인 :: 연산자를 이용하여 리스트를 생성 가능하다.
오른쪽-결합형(right-associative) 표기법
- 스칼라에서 연산자가 :로 끝나면 오른쪽 결합형으로 그 연산자의 오른쪽의 객체에서 호출 된다.
scala> val first = Nil.::(1)
first: List[Int] = List(1)
scala> first.tail == Nil
res3: Boolean = true
리스트 산술 연산
산술은 추가하고, 제거하고, 나누고, 결합하고, 구성 요소를 변경하는 등의 작업을 의미한다.
immutable형이기 때문에 실제로는 변경 사항을 반영한 새로운 리스트를 반환한다.
Name | Example | 설명 |
---|---|---|
:: | 1 :: 2:: Nil | 리스트에 개별 요소를 덧붙임 |
::: | List(1 2) ::: List(2, 3) | 이 리스트 앞에 다른 리스트를 추가함 |
++ | List(1, 2) ++ Set(3, 4, 3) | 이 리스트에 다른 컬렉션을 덧붙임 |
== | List(1, 2) == List(1, 2) | 두 컬렉션의 요소를 비교 |
distinct | List(3, 5, 4, 3, 4).distinct | 중복 요소가 없는 리스트를 반환 |
drop | List(1 ,2 , 3 ,4) drop 2 | 주어진 n개의 요소를 앞에서 부터 제거 |
filter | List(23, 8, 14, 21) filter (_ > 18) | 참/거짓 함수를 참으로 만족한 요소를 반환 |
flatten | List(List(1,2), List(3,4)).flatten | 중첩된 리스트의 요소를 단일 리스트로 전환함 |
partition | List(1, 2, 3, 4, 5) partition (_ < 3) | 참/거짓 기준으로 리스트를 2개로 나눔 |
reverse | List(1,2,3).reverse | 요소의 순서가 거꾸로인 리스트 반환 |
slice | List(2,3,5,7) slice (1, 3) | 첫번째, 두번째-1 사이의 요소의 리스트를 반환 |
sortBy | List(“apple”, “to”) sortBy (_.size) | 주어진 함수의 값을 이용하여 요소를 정렬함 |
sorted | List(“apple”, “to”).sorted | 자연값 기준 정렬 |
splitAt | List(2, 3, 5, 7) splitAt 2 | 주어진 인덱스 기준으로 리스트를 2개로 나눔 |
take | List(2, 3, 5, 7, 11, 13) take 3 | 리스트에서 첫번쨰 n개의 요소를 추출 |
zip | List(1, 2) zip List(“a”, “b”) | 두 리스트의 요소들을 튜플 결합한 리스트를 반한 |
연산자 vs 점 표기법
- 매개변수가 없어 . 표기법이 요구되는 경우를 제외하면 개인의 선택문제이다.
- ::도 연산자이기 떄문에 순서가 역순이 되나 다음과 같은 수행도 가능하다.
scala> val numbers = 1 :: 2 :: 3 :: Nil
numbers: List[Int] = List(1, 2, 3)
리스트는 단일 연결리스트로 앞에서 연산하는 것이 이익이다.
::, drop, take는 앞에서 부터 연산되지만 간혹 전체 순회가 필요한 동반연산(corollary operation)이 필요할 수 있다.
:+(왼쪽 결합형 연산자, 책오타), dropRight, takeRight
scala> val appended = List(1, 2, 3, 4) :+ 5
appended: List[Int] = List(1, 2, 3, 4, 5)
scala> val suffix = appended takeRight 3
suffix: List[Int] = List(3, 4, 5)
scala> val middle = suffix dropRight 2
middle: List[Int] = List(3)
리스트 매핑
Map 메서드는 함수를 취하여 요소에 적용하고, 그 결과를 새로운 리스트에 수집한다.
Name | Example | 설명 |
---|---|---|
collect | List(0, 1, 0) collect { case => “ok” } | 각 요소에 case 적용 뒤 적용된 요소를 반환 |
flatMap | List(“milk,tea”) flatMap (_.split(“,”)) | 주어진 함수를 이용하여 요소에 적용한 뒤 flatten함 |
map | List(“milk,tea”) map (_.toUooerCase) | 주어진 함수를 이용하여 각 요소를 변환함 |
리스트 축소
리스트를 단일 값으로 축소한다. 스칼라 컬렉션은 수학적 축소 연산과 부울 축소 연산을 지원한다.
또한 스칼라는 fold로 알려진 일반적인 고차 연산을 지원하는데, 이를 사용하여 임의의 다른 형태의 리스트 축소 알고리즘을 생성할 수 있다.
수학적 축소 연산
Name | Example | 설명 |
---|---|---|
max | List(41, 59, 26).max | 리스트의 최댓값 추출 |
min | List(10.9, 32.5, 4.23, 5.67).min | 리스트의 최솟값 추출 |
product | List(5, 6, 7).product | 원소들의 곱을 반환 |
sum | List(11.3, 23.5, 7.2).sum | 원소들의 합을 반환 |
부울 축소 연산
Name | Example | 설명 |
---|---|---|
contains | List(34, 29, 18) contains 29 | 리스트가 해당 요소를 포함하는지 검사 |
endsWith | List(0, 4, 3) endsWith List(4, 3) | 리스트가 주어진 리스트로 끝나는지 검사 |
exists | List(24, 17, 32 exists (_ < 18) | 최소 하나의 요소가 주어진 조건 함수를 만족하는지 검사 |
forall | List(24, 17, 32 forall (_ < 18) | 모든 요소가 주어진 조건 함수를 만족하는지 검사 |
startsWith | List(0, 4, 3) startsWith List(0) | 리스트가 주어진 리스트로 시작하는지 검사 |
커링 활용
contains를 직접 구현한다고 가정해보자.
scala> def contains(x: Int, l: List[Int]): Boolean = {
| var a: Boolean = false
| for (i <- l) { if (!a) a = (i == x) }
| a
}
contains: (x:Int, l: List[Int])Boolean
scala> val included = contains(19, List(46, 19, 92))
included: Boolean = true
요소 순회와 조건 함수를 다음과 같이 분리하여 개선 할 수 있다.
scala> def boolReduce(l: List[Int], start: Boolean)(f: (Boolean, Int) => Boolean): Boolean = {
| var a = start
| for (i <- l) a = f(a, i)
| a
}
boolReduce: (l: List[Int], start: Boolean)(f: (Boolean, Int) => Boolean)Boolean
scala> val included = boolReduce(List(46, 19, 92), false) { (a, i) => if (a) a else (i == 19)}
included: Boolean = true
같은 방식으로 boolean 함수 뿐만이 아닌 reduce 연산에 일반화 시킬 수 있다.
scala> def reduceOp[A,B](l: List[A], start: B)(f: (B, A) => B): B = {
| var a = start
| for (i <- l) a = f(a, i)
| a
}
reduceOp: [A, B](l: List[A], start: B)(f: (B, A) => B)B
scala> val included = reduceOp(List(46, 19, 92), false) { (a, i) => if (a) a else (i == 19)}
included: Boolean = true
scala> val answer = reduceOp(List(11.3, 23.5, 7.2), 0.0)(_ + _)
answer: Double = 42.0
list-folding (fold 함수)
스칼라 컬렉션은 이미 다음과 같은 내장 연산을 제공하고 있다. 내장 연산들은 왼쪽에서 오른쪽, 오른쪽에서 왼쪽, 순서와 무관한 버전을 모두 제공할 만큼 유연하다.
Name | Example | 설명 |
---|---|---|
fold | List(4, 5, 6).fold(0)(_ + _) | 초기값을 기준으로 리스트 축소 |
foldLeft | List(4, 5, 6).foldLeft(0)(_ + _) | 초기값을 기준으로 왼쪽에서 오른쪽으로 리스트 축소 |
foldRight | List(4, 5, 6).foldRight(0)(_ + _) | 초기값을 기준으로 오른쪽에서 왼쪽으로 리스트 축소 |
reduce | List(4, 5, 6).reduce(_ + _) | 페어요소 별 리스트 축소 |
reduceLeft | List(4, 5, 6).reduceLeft(_ + _) | 첫번째 요소 기준으로 왼쪽에서 오른쪽으로 리스트 축소 |
reduceRight | List(4, 5, 6).reduceRight(_ + _) | 마지막 요소 기준으로 오른쪽에서 왼쪽으로 리스트 축소 |
scan | List(4, 5, 6).scan(0)(_ + _) | 초기값 기준으로 축소 함수를 취하여 각각의 누적 리스트를 반환 |
scanLeft | List(4, 5, 6).scanLeft(0)(_ + _) | 초기값 기준으로 축소 함수를 취하여 왼쪽 부터 오른쪽으로 수행 후 각각의 누적 리스트를 반환 |
scanRight | List(4, 5, 6).scanRight(0)(_ + _) | 초기값 기준으로 축소 함수를 취하여 오른쪽 부터 왼쪽으로 수행 후 각각의 누적 리스트를 반환 |
컬렉션 전환
컬렉션들은 서로 다른 타입의 컬렉션으로 전환 될 수 있다.
Name | Example | 설명 |
---|---|---|
mkString | List(24, 99, 104).mkString(“,”) | 주어진 구분자로 컬렉션을 String으로 전환 |
toBuffer | List(‘f’, ‘t’).toBuffer | immutable을 mutable로 전환 |
toList | Map(“a” -> 1, “b” -> 2).toList | 컬렉션을 List로 전환 |
toMap | Set(1 -> true, 3 -> true).toMap | 두 요소 튜블로 구성된 컬렉션을 Map으로 전환 |
toSet | List(2, 5, 3, 2).toSet | 컬렉션을 Set으로 전환 |
toString | List(2, 5, 5, 3, 2).toString | 컬렉션 타입을 포함하여 콜렉션을 문자열로 전환 |
자바와 스칼라 컬렉션 호환성
스칼라는 jvm으로 컴파일되고 그 위에서 동작한다.
즉 jdk와 상호작용뿐 아니라 어떤 자바 라이브러리도 추가 할 수 있어야한다는 것이 보편적인 요구사항이다.
그러나 자바 컬렉션과 스칼라 컬렉션의 타입은 기본적으로 호환 되지 않는다.
자바와 스칼라 컬렉션 사이의 전환을 하려면 다음 명령어를 추가해야한다.
import collection.JavaConverters._
collection.JavaConverters
의 메서드가 추가 되면 자바와 스칼라 컬렉션에 다음 연산을 사용할 수 있다.
Name | Example | 설명 |
---|---|---|
asJava | List(12, 29).asJava | 스칼라 컬렉션을 대응되는 자바 컬렉션으로 전환 |
asScala | new java.util.ArrayList(5).asScala | 자바 컬렉션을 대응되는 스칼라 컬렉션으로 전환 |
컬렉션으로 패턴 매칭하기
이전에 배웠던 단일값 패턴 매칭은 다음과 같다.
scala> val statuses = List(500, 404)
statuses: List[Int] = List(500, 404)
scala> msg = statuses.head match {
| case x if x < 500 => "okay"
| case _ => "whoah, an error"
}
msg: String = whoah, an error
하지만 컬렉션의 값 중 하나의 값밖에 검사할 수 없다. 다음은 개선 버전이다.
scala> msg = statuses match {
| case x if x contains(500) => "has error"
| case _ => "okay"
}
msg: String = has error
이와 다르게 값 바인딩을 통하여 패턴 가드에서 컬렉션의 일부 또는 모든 요소에 값 바인딩이 가능하다.
scala> msg = statuses match {
| case List(500, x) => s"Error followed by $x"
| case List(e, x) => s"$e was followed by $x"
}
msg: String = Error followed by 404
리스트는 head와 tail로 분해 가능하기 떄문에 다음과 같은 매칭도 가능하다.
scala> val head = List('r', 'g', 'b') match {
| case x :: xs => x
| case Nil => ' '
}
head: Char = r
튜플도 값 바인딩을 이용한 패턴 매칭을 지원한다. 패턴 매칭은 스칼라 표준 컬렉션 라이브러리의 평범한 연산이 아니라 함수형 언어의 핵심적 특징이다.
'IT Book Study > 러닝 스칼라' 카테고리의 다른 글
러닝스칼라 8장 - 클래스 (0) | 2020.09.28 |
---|---|
러닝스칼라 7장 - 그 외의 컬렉션 (0) | 2020.09.28 |
러닝스칼라 5장 - 일급 함수 (0) | 2020.09.28 |
러닝스칼라 4장 - 함수 (0) | 2020.09.28 |
러닝스칼라 3장 - 표현식과 조건문 (0) | 2020.09.28 |