# 컬렉션 조작

컬렉션 조작은 Dart에서 리스트(List), 셋(Set), 맵(Map)과 같은 컬렉션을 다루는 다양한 기법들을 의미한다. 이 챕터에서는 Dart에서 제공하는 컬렉션을 보다 효과적으로 다루기 위한 다양한 방법을 설명한다.

#### 리스트(List) 조작

리스트는 순서가 있는 컬렉션으로, Dart에서 가장 많이 사용되는 자료형 중 하나다. 리스트에서 데이터를 추가하거나 제거하는 작업은 매우 흔하며, Dart에서는 이를 지원하는 다양한 메소드가 제공된다.

**요소 추가**

리스트에 요소를 추가하는 방법으로는 `add()`와 `addAll()` 메소드가 있다. `add()`는 단일 요소를 리스트의 끝에 추가하고, `addAll()`은 여러 요소를 한 번에 추가할 수 있다.

```dart
List<int> numbers = [1, 2, 3];
numbers.add(4);   // 결과: [1, 2, 3, 4]
numbers.addAll([5, 6]); // 결과: [1, 2, 3, 4, 5, 6]
```

**요소 제거**

리스트에서 특정 요소를 제거할 때는 `remove()`와 `removeAt()` 메소드를 사용할 수 있다. `remove()`는 리스트에서 특정 값을 제거하고, `removeAt()`은 인덱스를 기반으로 요소를 제거한다.

```dart
List<int> numbers = [1, 2, 3, 4];
numbers.remove(3);   // 결과: [1, 2, 4]
numbers.removeAt(0); // 결과: [2, 4]
```

#### 리스트 병합 및 슬라이싱

**리스트 병합**

두 개 이상의 리스트를 병합하는 작업은 `addAll()` 메소드를 사용하거나, Dart에서 제공하는 `...`(스프레드 연산자)를 활용할 수 있다.

```dart
List<int> list1 = [1, 2, 3];
List<int> list2 = [4, 5, 6];
List<int> merged = [...list1, ...list2]; // 결과: [1, 2, 3, 4, 5, 6]
```

**리스트 슬라이싱**

리스트에서 특정 구간을 추출할 때는 `sublist()` 메소드를 사용할 수 있다. `sublist(startIndex, endIndex)` 형태로 사용되며, `startIndex`부터 `endIndex-1`까지의 값을 포함하는 서브리스트를 반환한다.

```dart
List<int> numbers = [1, 2, 3, 4, 5, 6];
List<int> slice = numbers.sublist(1, 4); // 결과: [2, 3, 4]
```

#### 리스트 필터링

리스트에서 조건에 맞는 요소만 추출할 때는 `where()` 메소드를 사용한다. 이 메소드는 함수형 프로그래밍의 개념을 따르며, 불리언 값을 반환하는 조건을 기반으로 리스트를 필터링한다.

```dart
List<int> numbers = [1, 2, 3, 4, 5, 6];
List<int> evenNumbers = numbers.where((num) => num % 2 == 0).toList(); // 결과: [2, 4, 6]
```

#### 리스트 정렬

리스트의 요소를 오름차순이나 내림차순으로 정렬하는 방법은 `sort()` 메소드를 사용한다. 기본적으로 `sort()` 메소드는 오름차순 정렬을 수행하지만, 커스텀 비교 함수를 제공하여 내림차순으로도 정렬할 수 있다.

```dart
List<int> numbers = [5, 1, 4, 2, 3];
numbers.sort(); // 결과: [1, 2, 3, 4, 5]

numbers.sort((a, b) => b.compareTo(a)); // 내림차순 정렬 결과: [5, 4, 3, 2, 1]
```

#### 리스트의 변환

리스트의 각 요소를 특정 방식으로 변환하는 작업은 `map()` 메소드를 사용하여 가능하다. 이 메소드는 리스트의 모든 요소에 변환 로직을 적용한 새로운 리스트를 반환한다.

```dart
List<int> numbers = [1, 2, 3, 4, 5];
List<int> squaredNumbers = numbers.map((num) => num * num).toList(); // 결과: [1, 4, 9, 16, 25]
```

#### 리스트의 모든 요소에 함수 적용

리스트의 모든 요소에 대해 함수나 연산을 적용하는 방법으로는 `forEach()`를 사용할 수 있다. 이는 주로 리스트 내의 각 요소를 순회하며 부수적인 작업을 할 때 사용된다.

```dart
List<int> numbers = [1, 2, 3];
numbers.forEach((num) => print(num)); // 1, 2, 3 출력
```

#### 리스트의 탐색

리스트에서 특정 조건에 맞는 요소를 찾는 방법으로는 `firstWhere()`, `lastWhere()`, `indexOf()` 등이 있다.

**첫 번째 일치하는 요소 찾기**

`firstWhere()` 메소드는 리스트에서 주어진 조건을 만족하는 첫 번째 요소를 반환한다. 만약 조건을 만족하는 요소가 없을 경우, 기본값을 설정하거나 예외를 발생시킬 수 있다.

```dart
List<int> numbers = [1, 2, 3, 4, 5];
int firstEven = numbers.firstWhere((num) => num % 2 == 0); // 결과: 2
```

**마지막 일치하는 요소 찾기**

`lastWhere()` 메소드는 `firstWhere()`와 동일하지만, 리스트에서 마지막으로 조건을 만족하는 요소를 반환한다.

```dart
int lastEven = numbers.lastWhere((num) => num % 2 == 0); // 결과: 4
```

**특정 요소의 인덱스 찾기**

`indexOf()` 메소드는 리스트에서 특정 값의 첫 번째 인덱스를 반환한다. 만약 값이 리스트에 없으면 `-1`을 반환한다.

```dart
List<int> numbers = [1, 2, 3, 4, 5];
int index = numbers.indexOf(3); // 결과: 2
```

#### 리스트의 중복 제거

리스트의 중복 요소를 제거하는 방법으로는 `toSet()` 메소드를 사용할 수 있다. 이 메소드는 리스트를 `Set`으로 변환하여 중복을 제거한 후, 다시 리스트로 변환한다.

```dart
List<int> numbers = [1, 2, 2, 3, 3, 4];
List<int> uniqueNumbers = numbers.toSet().toList(); // 결과: [1, 2, 3, 4]
```

#### 리스트의 길이와 빈 리스트 확인

리스트의 길이를 확인할 때는 `length` 속성을 사용한다. 또한, 리스트가 비어 있는지 확인할 때는 `isEmpty` 또는 `isNotEmpty` 속성을 사용할 수 있다.

```dart
List<int> numbers = [1, 2, 3];
int length = numbers.length;  // 결과: 3
bool isEmpty = numbers.isEmpty; // 결과: false
```

#### 리스트 초기화

리스트를 고정된 길이로 초기화하거나 특정 값으로 초기화할 때는 `List.filled()` 메소드를 사용한다. 이는 리스트를 특정 크기와 값으로 초기화하는 데 유용하다.

```dart
List<int> zeros = List.filled(5, 0);  // 결과: [0, 0, 0, 0, 0]
```

#### 리스트의 부분 업데이트

리스트의 특정 구간을 다른 값으로 업데이트하는 방법으로는 `setRange()` 메소드를 사용할 수 있다. 이 메소드는 리스트의 일부를 지정한 값으로 덮어쓴다.

```dart
List<int> numbers = [1, 2, 3, 4, 5];
numbers.setRange(1, 3, [10, 20]); // 결과: [1, 10, 20, 4, 5]
```

#### 리스트의 빈 자리 삽입

리스트에 특정 위치에 요소를 삽입할 때는 `insert()`와 `insertAll()` 메소드를 사용할 수 있다. `insert()`는 단일 요소를 삽입하고, `insertAll()`은 여러 요소를 삽입한다.

```dart
List<int> numbers = [1, 2, 4, 5];
numbers.insert(2, 3); // 결과: [1, 2, 3, 4, 5]

List<int> numbers2 = [1, 2, 5];
numbers2.insertAll(2, [3, 4]); // 결과: [1, 2, 3, 4, 5]
```

#### 리스트 병합 및 변환 과정의 복잡도 분석

리스트를 병합하거나 변환할 때의 복잡도를 수학적으로 분석할 수 있다. 리스트에 요소를 하나씩 추가하는 것은 시간 복잡도가 $O(1)$이지만, 리스트를 전체적으로 병합하는 경우에는 복잡도가 $O(n + m)$이 될 수 있다. 여기서 $n$은 첫 번째 리스트의 길이, $m$은 두 번째 리스트의 길이이다.

**시간 복잡도 분석**

리스트 병합에서 시간 복잡도는 다음과 같다:

$$
T\_{\text{merge}}(n, m) = O(n + m)
$$

리스트 슬라이싱과 필터링에서의 복잡도는 리스트의 크기 $n$에 비례하여 다음과 같이 표현된다:

$$
T\_{\text{sublist}}(n) = O(n)
$$

$$
T\_{\text{filter}}(n) = O(n)
$$

이 복잡도는 리스트의 크기와 변환 과정에서 얼마나 많은 계산이 필요한지를 나타낸다.

#### 리스트의 재정렬 및 복잡도 분석

리스트를 정렬하는 작업은 Dart의 `sort()` 메소드를 사용하며, 기본적으로 퀵소트(QuickSort) 알고리즘이 적용된다. 이는 최선의 경우 $O(n \log n)$의 시간 복잡도를 갖지만, 최악의 경우 $O(n^2)$이 될 수 있다. 하지만 Dart는 최악의 경우에도 $O(n \log n)$ 성능을 보장하는 알고리즘을 사용한다.

**시간 복잡도**

리스트를 오름차순 또는 내림차순으로 정렬하는 경우 시간 복잡도는 다음과 같다:

$$
T\_{\text{sort}}(n) = O(n \log n)
$$

이 복잡도는 리스트에 포함된 요소 수에 따라 결정되며, 매우 큰 리스트를 처리할 때 성능 최적화의 중요한 부분이 된다.

#### Set 조작

Set은 중복을 허용하지 않는 컬렉션이며, Dart에서 리스트와 유사한 방식으로 사용할 수 있지만, 순서가 보장되지 않는다. Set의 주요 특징은 중복 요소를 허용하지 않는다는 점이다.

**요소 추가 및 제거**

Set에 요소를 추가하는 것은 리스트와 동일하게 `add()` 메소드를 사용한다. 하지만, 중복된 값은 허용되지 않는다.

```dart
Set<int> numbers = {1, 2, 3};
numbers.add(3);  // 결과: {1, 2, 3}
```

`remove()` 메소드는 리스트와 동일하게 특정 요소를 제거한다.

```dart
numbers.remove(2);  // 결과: {1, 3}
```

**Set 연산**

Set을 활용하면 교집합, 합집합, 차집합 등의 수학적 집합 연산을 간편하게 수행할 수 있다. 이러한 연산은 수학적 표현으로도 설명할 수 있다.

**합집합**

두 Set의 합집합은 Dart에서 `union()` 메소드를 사용하여 구현할 수 있다. 이 연산은 두 Set에 포함된 모든 요소를 반환하며, 중복된 요소는 제거된다.

$$
A \cup B = {x \mid x \in A \text{ 또는 } x \in B}
$$

```dart
Set<int> set1 = {1, 2, 3};
Set<int> set2 = {3, 4, 5};
Set<int> unionSet = set1.union(set2);  // 결과: {1, 2, 3, 4, 5}
```

**교집합**

교집합은 두 Set에서 공통된 요소만 반환하는 연산으로, `intersection()` 메소드를 사용한다.

$$
A \cap B = {x \mid x \in A \text{ 그리고 } x \in B}
$$

```dart
Set<int> intersectionSet = set1.intersection(set2);  // 결과: {3}
```

**차집합**

차집합은 첫 번째 Set에서 두 번째 Set에 포함되지 않은 요소만 반환하는 연산이다. Dart에서는 `difference()` 메소드를 사용한다.

$$
A - B = {x \mid x \in A \text{ 이지만 } x \notin B}
$$

```dart
Set<int> differenceSet = set1.difference(set2);  // 결과: {1, 2}
```

**시간 복잡도 분석**

Set의 주요 연산들의 시간 복잡도는 다음과 같다:

* 합집합: $O(n + m)$
* 교집합: $O(\min(n, m))$
* 차집합: $O(n)$

여기서 $n$과 $m$은 두 Set의 크기를 나타낸다.

#### Map 조작

Map은 키와 값의 쌍으로 이루어진 컬렉션이다. Dart에서는 Map을 사용하여 키를 기반으로 값을 빠르게 조회하고, 추가 및 삭제 작업을 수행할 수 있다.

**요소 추가 및 업데이트**

Map에 새로운 요소를 추가하거나 기존 요소를 업데이트할 때는 `[]` 연산자를 사용한다.

```dart
Map<String, int> scores = {'John': 90, 'Jane': 85};
scores['Alice'] = 95;  // 새로운 요소 추가
scores['John'] = 92;   // 기존 요소 업데이트
```

**요소 제거**

Map에서 요소를 제거할 때는 `remove()` 메소드를 사용한다. 키를 기반으로 값을 삭제할 수 있다.

```dart
scores.remove('Jane');  // 결과: {'John': 92, 'Alice': 95}
```

**요소 탐색**

Map에서 특정 키에 대한 값을 조회할 때는 `[]` 연산자를 사용할 수 있다. 존재하지 않는 키를 조회하면 `null`을 반환한다.

```dart
int? johnScore = scores['John'];  // 결과: 92
```

**키와 값 목록**

Map의 키와 값만을 따로 추출하여 리스트 형태로 가져오고자 할 때는 `keys`와 `values` 속성을 사용할 수 있다.

```dart
Iterable<String> names = scores.keys;  // 결과: ('John', 'Alice')
Iterable<int> scoresList = scores.values;  // 결과: (92, 95)
```

#### Map의 고급 조작

Map은 키-값 쌍을 기반으로 데이터를 저장하고 조작하는 컬렉션으로, 다양한 방법으로 탐색, 수정, 업데이트가 가능하다. 아래에서는 Dart의 Map에서 제공하는 고급 기능들을 살펴본다.

**키 존재 여부 확인**

Map에서 특정 키가 존재하는지 확인하려면 `containsKey()` 메소드를 사용할 수 있다. 이 메소드는 해당 키가 존재하면 `true`를 반환하고, 그렇지 않으면 `false`를 반환한다.

```dart
Map<String, int> scores = {'John': 92, 'Alice': 95};
bool hasJohn = scores.containsKey('John');  // 결과: true
bool hasBob = scores.containsKey('Bob');    // 결과: false
```

**값 존재 여부 확인**

특정 값이 Map에 존재하는지 확인할 때는 `containsValue()` 메소드를 사용한다.

```dart
bool hasScore95 = scores.containsValue(95);  // 결과: true
```

**Map 병합**

Dart에서 두 개 이상의 Map을 병합할 수 있다. 이 작업은 `addAll()` 메소드를 사용하여 수행할 수 있으며, 만약 동일한 키가 존재할 경우 마지막으로 추가된 Map의 값이 우선된다.

```dart
Map<String, int> map1 = {'John': 90};
Map<String, int> map2 = {'Alice': 95, 'John': 92};
map1.addAll(map2);  // 결과: {'John': 92, 'Alice': 95}
```

**Map의 기본값 설정**

Dart에서는 존재하지 않는 키를 조회할 때 기본값을 설정할 수 있다. 이 작업은 `putIfAbsent()` 메소드를 통해 가능하다. 키가 존재하지 않으면 기본값이 추가되며, 이미 존재하는 경우는 무시된다.

```dart
scores.putIfAbsent('Bob', () => 85);  // 결과: {'John': 92, 'Alice': 95, 'Bob': 85}
```

**Map의 순회**

Map의 모든 키와 값을 순회할 때는 `forEach()` 메소드를 사용한다. 이 메소드는 각 키-값 쌍에 대해 제공된 함수를 호출한다.

```dart
scores.forEach((key, value) {
  print('$key:$value');
});
// 출력 결과: 
// John: 92
// Alice: 95
// Bob: 85
```

#### Map 변환 및 필터링

**Map 변환**

Map의 모든 값을 특정 방식으로 변환하려면 `map()` 메소드를 사용할 수 있다. 이 메소드는 새로운 Map을 반환하며, 기존의 키-값 쌍에 변환 로직을 적용한다.

```dart
Map<String, int> updatedScores = scores.map((key, value) => MapEntry(key, value + 5));  
// 결과: {'John': 97, 'Alice': 100, 'Bob': 90}
```

**Map 필터링**

Map에서 조건에 맞는 요소만 추출하려면 `where()` 메소드를 사용할 수 있다. 이 메소드는 주어진 조건을 만족하는 키-값 쌍을 필터링하여 새로운 Map을 생성한다.

```dart
Map<String, int> highScores = scores.where((key, value) => value > 90);  
// 결과: {'John': 92, 'Alice': 95}
```

#### Map에서의 복잡도 분석

Map에서의 주요 연산들의 시간 복잡도는 HashMap 구조를 기준으로 다음과 같이 분석된다.

* 요소 추가: $O(1)$
* 요소 제거: $O(1)$
* 키 또는 값 존재 여부 확인: $O(1)$
* Map 순회: $O(n)$

여기서 $n$은 Map에 포함된 키-값 쌍의 개수이다. 이러한 복잡도는 일반적인 HashMap 구조의 시간 복잡도와 유사하다.

**Map의 메모리 효율성**

Dart의 Map은 메모리 효율성 면에서도 적절한 구조를 제공한다. 키-값 쌍을 효율적으로 저장하기 위해 해시 테이블이 사용되며, 충돌 처리를 위한 메커니즘도 내장되어 있다.

#### Map의 키와 값 변환

Map의 키 또는 값을 변환하는 작업은 `map()` 메소드와 달리, 특정적으로 키만 또는 값만 변경하는 방법으로도 가능하다. Dart에서는 각각의 키와 값을 변환한 후 새로운 Map을 반환하는 방식으로 이러한 작업을 처리할 수 있다.

**값 변환**

Map의 값을 특정 방식으로 변환하려면 `mapValues()` 패턴을 적용할 수 있다. Dart는 직접적인 `mapValues()` 메소드를 제공하지 않지만, `map()` 메소드를 활용하여 동일한 기능을 수행할 수 있다.

```dart
Map<String, int> updatedScores = scores.map((key, value) => MapEntry(key, value * 2));  
// 결과: {'John': 184, 'Alice': 190, 'Bob': 170}
```

**키 변환**

키를 변환하는 작업은 조금 더 복잡할 수 있는데, 주로 키에 대한 변경이 이루어질 때 새로운 Map을 반환하여야 한다. Dart는 `map()` 메소드를 사용하여 키를 변환할 수 있으며, 기존의 키-값 쌍에 변환 로직을 적용할 수 있다.

```dart
Map<String, int> updatedKeys = scores.map((key, value) => MapEntry('$key_score', value));  
// 결과: {'John_score': 92, 'Alice_score': 95, 'Bob_score': 85}
```

#### Map의 깊은 복사와 얕은 복사

Dart에서 Map을 복사할 때는 얕은 복사(shallow copy)와 깊은 복사(deep copy)를 구분해야 한다. 얕은 복사는 단순히 참조를 복사하는 반면, 깊은 복사는 각 요소의 값을 모두 새롭게 복사하여 완전히 독립적인 Map을 생성한다.

**얕은 복사**

얕은 복사는 기본적으로 `addAll()` 메소드를 사용하여 이루어진다. 이 경우 복사된 Map은 원본 Map의 참조를 그대로 사용하게 된다.

```dart
Map<String, int> shallowCopy = {};
shallowCopy.addAll(scores);
// shallowCopy는 scores와 동일한 참조를 가짐
```

**깊은 복사**

깊은 복사는 모든 키-값 쌍을 새롭게 할당하여 새로운 Map을 생성하는 방법이다. 이를 위해서는 모든 값을 명시적으로 복사해야 한다.

```dart
Map<String, int> deepCopy = scores.map((key, value) => MapEntry(key, value));
// deepCopy는 scores와 독립적인 복사본
```

#### Map의 기본 값 설정

Dart에서는 Map에서 특정 키에 값이 없을 때 기본값을 설정하는 방법으로 `putIfAbsent()` 메소드를 사용할 수 있다. 이 메소드는 해당 키가 이미 존재하면 아무 작업도 하지 않고, 키가 존재하지 않으면 기본값을 삽입한다.

```dart
Map<String, int> scores = {'John': 92, 'Alice': 95};
scores.putIfAbsent('Bob', () => 85);  // Bob이 없을 경우 85 추가
```

이 방법은 데이터가 부족할 때 기본값을 설정하는 유용한 방식이다.

#### Map의 키 리스트와 값 리스트

Map의 키와 값을 각각 리스트 형태로 추출하고 싶을 때는 `keys`와 `values` 속성을 사용할 수 있다. 이 속성들은 `Iterable` 타입을 반환하며, `toList()` 메소드를 사용하여 리스트로 변환할 수 있다.

```dart
List<String> keyList = scores.keys.toList();   // 결과: ['John', 'Alice', 'Bob']
List<int> valueList = scores.values.toList();  // 결과: [92, 95, 85]
```

#### Map의 구조 시각화

Map의 기본 구조와 데이터를 시각적으로 나타낼 수 있다. Dart에서의 Map은 키-값 쌍으로 구성되며, 이를 시각적으로 표현하면 다음과 같다:

{% @mermaid/diagram content="graph TD;
A\["Map"] --> B\["John: 92"];
A --> C\["Alice: 95"];
A --> D\["Bob: 85"];" %}

이 다이어그램은 Map의 기본 구조를 시각적으로 보여주며, 각 키-값 쌍이 어떻게 연결되어 있는지를 나타낸다.

#### Map의 병합과 필터링의 복잡도 분석

Map에서 병합 및 필터링 작업은 다음과 같은 시간 복잡도를 갖는다:

* 병합: 두 Map을 병합하는 경우 시간 복잡도는 $O(n + m)$, 여기서 $n$과 $m$은 각각 병합할 Map의 크기이다.
* 필터링: Map에서 조건을 만족하는 요소를 필터링할 때의 시간 복잡도는 $O(n)$, 여기서 $n$은 Map의 크기이다.

이러한 복잡도는 Map의 크기에 따라 성능이 좌우되며, 매우 큰 Map을 처리할 때 성능 최적화가 중요한 요소가 된다.

#### Map의 고급 기능과 활용 사례

Dart의 Map은 다양한 데이터 구조와 복잡한 데이터 저장소를 처리하는 데 매우 유용하다. 특히 JSON과 같은 데이터를 다룰 때 Dart의 Map은 매우 직관적이고 강력한 도구로 활용된다.

**JSON과 Map**

JSON 데이터를 Dart에서 다룰 때 Map을 사용하여 쉽게 파싱하고 조작할 수 있다. Dart는 `dart:convert` 라이브러리를 통해 JSON 데이터를 Map으로 변환할 수 있다.

```dart
import 'dart:convert';

String jsonData = '{"John": 92, "Alice": 95, "Bob": 85}';
Map<String, int> scores = jsonDecode(jsonData);

print(scores);  // 결과: {John: 92, Alice: 95, Bob: 85}
```
