이 챕터를 완료하면 다음을 할 수 있습니다:
Traversable
├─ Iterable
│ ├─ Seq (순서 있음)
│ │ ├─ List
│ │ ├─ Vector
│ │ ├─ Array
│ │ └─ Range
│ ├─ Set (중복 없음)
│ │ ├─ HashSet
│ │ └─ TreeSet
│ └─ Map (키-값 쌍)
│ ├─ HashMap
│ └─ TreeMap
// 불변 컬렉션 (기본값, 권장)
import scala.collection.immutable._
val list1 = List(1, 2, 3)
val list2 = list1 :+ 4 // 새 리스트 반환
println(list1) // List(1, 2, 3) - 원본 불변
println(list2) // List(1, 2, 3, 4)
// 가변 컬렉션 (필요 시에만)
import scala.collection.mutable
val buffer = mutable.ListBuffer(1, 2, 3)
buffer += 4 // 원본 수정
println(buffer) // ListBuffer(1, 2, 3, 4)
Java 비교:
// Java: 기본적으로 가변
List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3));
list1.add(4); // 원본 수정
System.out.println(list1); // [1, 2, 3, 4]
// Java: 불변 컬렉션 (Java 9+)
List<Integer> immutable = List.of(1, 2, 3);
// immutable.add(4); // UnsupportedOperationException
// 방법 1: List 생성자
val list1 = List(1, 2, 3, 4, 5)
// 방법 2: cons 연산자 (::)
val list2 = 1 :: 2 :: 3 :: Nil // Nil = 빈 리스트
println(list2) // List(1, 2, 3)
// 방법 3: 범위
val list3 = (1 to 5).toList
val list4 = (1 until 5).toList // 5 미포함
println(list3) // List(1, 2, 3, 4, 5)
println(list4) // List(1, 2, 3, 4)
// 빈 리스트
val empty = List.empty[Int]
val empty2 = Nil
Java 비교:
// Java
List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> list2 = List.of(1, 2, 3); // Java 9+
List<Integer> list3 = new ArrayList<>();
val list = List(1, 2, 3, 4, 5)
// 접근
println(list.head) // 1 (첫 번째 요소)
println(list.tail) // List(2, 3, 4, 5) (첫 번째 제외)
println(list(2)) // 3 (인덱스 접근)
println(list.last) // 5 (마지막 요소)
// 추가 (새 리스트 반환)
val list2 = 0 :: list // List(0, 1, 2, 3, 4, 5) - 앞에 추가
val list3 = list :+ 6 // List(1, 2, 3, 4, 5, 6) - 뒤에 추가
val list4 = list ++ List(6, 7) // List(1, 2, 3, 4, 5, 6, 7) - 리스트 연결
// 삭제/필터
val list5 = list.drop(2) // List(3, 4, 5) - 앞 2개 제거
val list6 = list.dropRight(2) // List(1, 2, 3) - 뒤 2개 제거
val list7 = list.filter(_ > 2) // List(3, 4, 5) - 조건 필터
// 크기 및 검사
println(list.length) // 5
println(list.isEmpty) // false
println(list.contains(3)) // true
Java 비교:
// Java
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list.get(0)); // 1
System.out.println(list.size()); // 5
System.out.println(list.isEmpty()); // false
System.out.println(list.contains(3)); // true
// Java: 추가/삭제는 가변 리스트 필요
List<Integer> mutable = new ArrayList<>(list);
mutable.add(6);
mutable.remove(0);
val numbers = List(1, 2, 3, 4, 5)
// map: 각 요소 변환
val doubled = numbers.map(_ * 2)
println(doubled) // List(2, 4, 6, 8, 10)
// filter: 조건 필터
val evens = numbers.filter(_ % 2 == 0)
println(evens) // List(2, 4)
// flatMap: 변환 + 평탄화
val nested = numbers.flatMap(n => List(n, n * 10))
println(nested) // List(1, 10, 2, 20, 3, 30, 4, 40, 5, 50)
// fold: 누적 연산
val sum = numbers.foldLeft(0)(_ + _)
println(sum) // 15
val product = numbers.foldLeft(1)(_ * _)
println(product) // 120
// reduce: fold의 간소화 버전 (초기값 없음)
val sum2 = numbers.reduce(_ + _)
println(sum2) // 15
// foreach: 부수 효과 (반환값 없음)
numbers.foreach(n => println(s"Number: $n"))
Java 비교 (Java 8+ Stream):
// Java Stream API
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubled = numbers.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
numbers.forEach(n -> System.out.println("Number: " + n));
// Set 생성
val set1 = Set(1, 2, 3, 4, 5)
val set2 = Set(4, 5, 6, 7, 8)
// 중복 자동 제거
val set3 = Set(1, 1, 2, 2, 3)
println(set3) // Set(1, 2, 3)
// 기본 연산
println(set1.contains(3)) // true
println(set1(3)) // true (contains와 동일)
// 추가/삭제 (새 Set 반환)
val set4 = set1 + 6 // Set(1, 2, 3, 4, 5, 6)
val set5 = set1 - 3 // Set(1, 2, 4, 5)
// 집합 연산
val union = set1 | set2 // 합집합: Set(1, 2, 3, 4, 5, 6, 7, 8)
val intersection = set1 & set2 // 교집합: Set(4, 5)
val diff = set1 &~ set2 // 차집합: Set(1, 2, 3)
println(union)
println(intersection)
println(diff)
Java 비교:
// Java
Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));
System.out.println(set1.contains(3)); // true
// 가변 Set
set1.add(6);
set1.remove(3);
// 집합 연산 (수동 구현)
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
Set<Integer> diff = new HashSet<>(set1);
diff.removeAll(set2);
// TreeSet: 자동 정렬
val treeSet = scala.collection.immutable.TreeSet(5, 1, 3, 2, 4)
println(treeSet) // TreeSet(1, 2, 3, 4, 5)
// 범위 조회
println(treeSet.range(2, 5)) // TreeSet(2, 3, 4) - 2 이상 5 미만
// Map 생성
val map1 = Map("a" -> 1, "b" -> 2, "c" -> 3)
val map2 = Map(("a", 1), ("b", 2), ("c", 3)) // 동일한 표현
// 조회
println(map1("a")) // 1
println(map1.get("a")) // Some(1) - Option 타입
println(map1.get("z")) // None
println(map1.getOrElse("z", 0)) // 0 - 기본값
// 추가/업데이트 (새 Map 반환)
val map3 = map1 + ("d" -> 4) // 추가
val map4 = map1 + ("a" -> 10) // 업데이트
val map5 = map1 ++ Map("d" -> 4, "e" -> 5) // 병합
// 삭제
val map6 = map1 - "b"
println(map6) // Map(a -> 1, c -> 3)
// 키/값 확인
println(map1.contains("a")) // true
println(map1.keys) // Set(a, b, c)
println(map1.values) // Iterable(1, 2, 3)
Java 비교:
// Java
Map<String, Integer> map1 = new HashMap<>();
map1.put("a", 1);
map1.put("b", 2);
map1.put("c", 3);
// Java 9+
Map<String, Integer> map2 = Map.of("a", 1, "b", 2, "c", 3);
System.out.println(map1.get("a")); // 1
System.out.println(map1.getOrDefault("z", 0)); // 0
System.out.println(map1.containsKey("a")); // true
System.out.println(map1.keySet()); // [a, b, c]
System.out.println(map1.values()); // [1, 2, 3]
val scores = Map("Alice" -> 90, "Bob" -> 85, "Charlie" -> 95)
// foreach로 순회
scores.foreach { case (name, score) =>
println(s"$name: $score")
}
// map으로 변환
val adjusted = scores.map { case (name, score) => (name, score + 5) }
println(adjusted) // Map(Alice -> 95, Bob -> 90, Charlie -> 100)
// filter
val highScores = scores.filter { case (_, score) => score >= 90 }
println(highScores) // Map(Alice -> 90, Charlie -> 95)
// mapValues (값만 변환)
val doubled = scores.mapValues(_ * 2)
println(doubled) // Map(Alice -> 180, Bob -> 170, Charlie -> 190)
Java 비교 (Java 8+):
// Java Stream
Map<String, Integer> scores = Map.of("Alice", 90, "Bob", 85, "Charlie", 95);
scores.forEach((name, score) ->
System.out.println(name + ": " + score)
);
Map<String, Integer> adjusted = scores.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue() + 5
));
Map<String, Integer> highScores = scores.entrySet().stream()
.filter(e -> e.getValue() >= 90)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// Vector: 랜덤 액세스에 최적화 (List는 순차 액세스에 최적화)
val vec = Vector(1, 2, 3, 4, 5)
// List와 동일한 API
println(vec.head) // 1
println(vec.tail) // Vector(2, 3, 4, 5)
println(vec(2)) // 3 - O(log n) 성능
println(vec.updated(2, 10)) // Vector(1, 2, 10, 4, 5)
// 성능 비교
val largeList = (1 to 1000000).toList
val largeVec = (1 to 1000000).toVector
// List(999999) - 느림 (O(n))
// largeList(999999)
// Vector(999999) - 빠름 (O(log n))
// largeVec(999999)
사용 권장사항:
// Array: Java 배열과 동일 (가변)
val arr = Array(1, 2, 3, 4, 5)
// 인덱스 접근 및 수정
println(arr(0)) // 1
arr(0) = 10 // 원본 수정
println(arr(0)) // 10
// Array와 List 변환
val list = arr.toList
val arr2 = list.toArray
// 다차원 배열
val matrix = Array.ofDim[Int](3, 3)
matrix(0)(0) = 1
matrix(1)(1) = 5
matrix(2)(2) = 9
Java 비교:
// Java 배열
int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr[0]); // 1
arr[0] = 10;
// 다차원 배열
int[][] matrix = new int[3][3];
matrix[0][0] = 1;
val numbers = List(1, 2, 3, 4, 5)
// for-yield: 새 컬렉션 생성
val doubled = for (n <- numbers) yield n * 2
println(doubled) // List(2, 4, 6, 8, 10)
// for-loop: 부수 효과 (yield 없음)
for (n <- numbers) {
println(n)
}
// 필터링
val evens = for {
n <- numbers
if n % 2 == 0
} yield n
println(evens) // List(2, 4)
// 여러 조건
val result = for {
n <- numbers
if n > 2
if n < 5
} yield n * 10
println(result) // List(30, 40)
Java 비교:
// Java: 전통적 for-loop
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubled = new ArrayList<>();
for (int n : numbers) {
doubled.add(n * 2);
}
// Java 8+ Stream
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
val list1 = List(1, 2, 3)
val list2 = List(10, 20)
// 중첩 루프
val pairs = for {
x <- list1
y <- list2
} yield (x, y)
println(pairs)
// List((1,10), (1,20), (2,10), (2,20), (3,10), (3,20))
// flatMap + map과 동일
val pairs2 = list1.flatMap(x => list2.map(y => (x, y)))
println(pairs2) // 동일한 결과
val scores = Map("Alice" -> 90, "Bob" -> 85, "Charlie" -> 95)
// Map 순회
for ((name, score) <- scores) {
println(s"$name: $score")
}
// Map 변환
val adjusted = for ((name, score) <- scores) yield {
(name, score + 5)
}
println(adjusted) // Map(Alice -> 95, Bob -> 90, Charlie -> 100)
// 필터 + 변환
val highScores = for {
(name, score) <- scores
if score >= 90
} yield (name, score)
println(highScores) // Map(Alice -> 90, Charlie -> 95)
case class Student(name: String, age: Int, score: Int)
val csvData = List(
"Alice,20,90",
"Bob,21,85",
"Charlie,22,95",
"David,20,88"
)
// CSV 파싱
val students = csvData.map { line =>
val parts = line.split(",")
Student(parts(0), parts(1).toInt, parts(2).toInt)
}
// 통계 계산
val avgScore = students.map(_.score).sum.toDouble / students.size
println(f"Average score: $avgScore%.2f") // Average score: 89.50
// 필터링
val topStudents = students.filter(_.score >= 90)
topStudents.foreach(s => println(s"${s.name}: ${s.score}"))
// Alice: 90
// Charlie: 95
// 그룹화
val byAge = students.groupBy(_.age)
println(byAge)
// Map(20 -> List(Student(Alice,20,90), Student(David,20,88)),
// 21 -> List(Student(Bob,21,85)),
// 22 -> List(Student(Charlie,22,95)))
val text = "Scala is great Scala is powerful Scala is concise"
// 단어 분리
val words = text.toLowerCase.split("\\s+").toList
// 빈도수 계산
val wordCount = words.groupBy(identity).mapValues(_.size)
println(wordCount)
// Map(scala -> 3, is -> 3, great -> 1, powerful -> 1, concise -> 1)
// 정렬
val sortedByCount = wordCount.toList.sortBy(-_._2)
sortedByCount.foreach { case (word, count) =>
println(s"$word: $count")
}
// scala: 3
// is: 3
// great: 1
// powerful: 1
// concise: 1
작업 | List | Vector | Array | Set | Map |
---|---|---|---|---|---|
앞에 추가 | O(1) | O(log n) | O(n) | - | - |
뒤에 추가 | O(n) | O(log n) | O(1) | O(1) | O(1) |
인덱스 접근 | O(n) | O(log n) | O(1) | - | - |
검색 | O(n) | O(n) | O(n) | O(1) | O(1) |
중복 허용 | ✅ | ✅ | ✅ | ❌ | ❌(키) |
순서 유지 | ✅ | ✅ | ✅ | ❌ | ❌ |
// 불변: 함수형 프로그래밍, 스레드 안전
val immutableList = List(1, 2, 3)
val newList = immutableList :+ 4 // 새 리스트 생성
// 가변: 성능 최적화, 많은 수정 필요 시
val mutableBuffer = scala.collection.mutable.ListBuffer(1, 2, 3)
mutableBuffer += 4 // 원본 수정
권장사항:
다음 요구사항을 만족하는 프로그램을 작성하세요:
Student
case class 정의 (name, scores: List[Int])힌트:
case class Student(name: String, scores: List[Int]) {
def average: Double = ???
}
object StudentAnalyzer {
def topN(students: List[Student], n: Int): List[Student] = ???
def subjectAverages(students: List[Student]): List[Double] = ???
}
Map을 사용하여 전화번호부를 구현하세요:
class PhoneBook {
private var contacts: Map[String, String] = Map.empty
def add(name: String, phone: String): Unit = ???
def remove(name: String): Unit = ???
def lookup(name: String): Option[String] = ???
def all: List[(String, String)] = ???
}
이 챕터에서 배운 내용:
✅ Scala 컬렉션 계층 구조 (Seq, Set, Map) ✅ 불변 vs 가변 컬렉션 ✅ List, Vector, Array의 특징과 성능 ✅ Set과 Map의 기본 연산 ✅ 고차 함수 (map, filter, fold, reduce) ✅ for-comprehension을 활용한 컬렉션 조작 ✅ 실전 데이터 처리 예제
Java와 주요 차이점:
Part 1 완료! 다음 Part 2에서는 고급 함수형 프로그래밍을 다룹니다.
// Scala 3: extension methods로 컬렉션 확장
extension [T](list: List[T])
def second: T = list.tail.head
println(List(1, 2, 3).second) // 2
// Scala 3: 더 간결한 for-comprehension
val result = for
x <- List(1, 2, 3)
y <- List(10, 20)
if x > 1
yield x * y