컬렉션과 반복문
Dart는 데이터를 저장하고 조작하기 위한 다양한 컬렉션 타입과 반복문을 제공합니다. 이 장에서는 Dart의 컬렉션 타입(List, Set, Map)과 이를 처리하기 위한 다양한 반복문 및 반복 연산을 알아보겠습니다.
컬렉션 타입
Section titled “컬렉션 타입”1. List (리스트)
Section titled “1. List (리스트)”List는 순서가 있는 항목의 집합으로, 다른 언어의 배열과 유사합니다.
리스트 생성
Section titled “리스트 생성”// 리터럴을 사용한 생성var fruits = ['사과', '바나나', '오렌지'];
// 타입을 지정한 리스트List<String> names = ['홍길동', '김철수', '이영희'];
// 빈 리스트 생성var emptyList = <int>[];
// 생성자를 이용한 리스트 생성var fixedList = List<int>.filled(5, 0); // [0, 0, 0, 0, 0]var growableList = List<int>.empty(growable: true);var generatedList = List<int>.generate(5, (i) => i * i); // [0, 1, 4, 9, 16]
리스트 접근 및 조작
Section titled “리스트 접근 및 조작”var fruits = ['사과', '바나나', '오렌지', '딸기', '포도'];
// 인덱스로 접근print(fruits[0]); // 사과
// 길이 확인print(fruits.length); // 5
// 첫 번째와 마지막 항목print(fruits.first); // 사과print(fruits.last); // 포도
// 추가fruits.add('키위');print(fruits); // [사과, 바나나, 오렌지, 딸기, 포도, 키위]
// 여러 항목 추가fruits.addAll(['멜론', '수박']);print(fruits); // [사과, 바나나, 오렌지, 딸기, 포도, 키위, 멜론, 수박]
// 삭제fruits.remove('바나나');print(fruits); // [사과, 오렌지, 딸기, 포도, 키위, 멜론, 수박]
// 인덱스로 삭제fruits.removeAt(1);print(fruits); // [사과, 딸기, 포도, 키위, 멜론, 수박]
// 조건으로 삭제fruits.removeWhere((fruit) => fruit.length <= 2);print(fruits); // [사과, 딸기, 포도, 키위, 멜론, 수박]
// 정렬fruits.sort();print(fruits); // [딸기, 멜론, 사과, 수박, 포도, 키위]
// 인덱스 찾기print(fruits.indexOf('포도')); // 4
// 존재 여부 확인print(fruits.contains('사과')); // trueprint(fruits.contains('바나나')); // false
리스트 변환
Section titled “리스트 변환”var numbers = [1, 2, 3, 4, 5];
// 매핑 (각 요소 변환)var doubled = numbers.map((n) => n * 2).toList();print(doubled); // [2, 4, 6, 8, 10]
// 필터링 (조건에 맞는 요소만 선택)var evenNumbers = numbers.where((n) => n.isEven).toList();print(evenNumbers); // [2, 4]
// fold (누적 연산)var sum = numbers.fold<int>(0, (prev, curr) => prev + curr);print(sum); // 15
// reduce (항목들을 하나로 결합)var product = numbers.reduce((a, b) => a * b);print(product); // 120
// 평탄화 (중첩 리스트를 단일 리스트로)var nested = [[1, 2], [3, 4], [5]];var flattened = nested.expand((list) => list).toList();print(flattened); // [1, 2, 3, 4, 5]
리스트 슬라이싱과 세부 조작
Section titled “리스트 슬라이싱과 세부 조작”var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 범위 추출 (sublist)var slice = numbers.sublist(2, 5);print(slice); // [3, 4, 5]
// 리스트 복사var copy = List<int>.from(numbers);print(copy); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 모든 요소 바꾸기numbers.replaceRange(0, 3, [99, 98, 97]);print(numbers); // [99, 98, 97, 4, 5, 6, 7, 8, 9, 10]
// 뒤집기var reversed = numbers.reversed.toList();print(reversed); // [10, 9, 8, 7, 6, 5, 4, 97, 98, 99]
// 리스트의 특정 부분 채우기numbers.fillRange(5, 8, 0);print(numbers); // [99, 98, 97, 4, 5, 0, 0, 0, 9, 10]
2. Set (집합)
Section titled “2. Set (집합)”Set은 중복되지 않는 항목의 컬렉션입니다.
// 리터럴을 사용한 생성var fruits = {'사과', '바나나', '오렌지'};
// 타입을 지정한 집합Set<String> names = {'홍길동', '김철수', '이영희'};
// 빈 집합 생성var emptySet = <int>{};
// 리스트에서 생성 (중복 제거됨)var numbers = Set<int>.from([1, 2, 2, 3, 3, 3, 4, 5, 5]);print(numbers); // {1, 2, 3, 4, 5}
var set1 = {1, 2, 3, 4, 5};var set2 = {4, 5, 6, 7, 8};
// 합집합var union = set1.union(set2);print(union); // {1, 2, 3, 4, 5, 6, 7, 8}
// 교집합var intersection = set1.intersection(set2);print(intersection); // {4, 5}
// 차집합var difference = set1.difference(set2);print(difference); // {1, 2, 3}
// 요소 추가set1.add(6);print(set1); // {1, 2, 3, 4, 5, 6}
// 여러 요소 추가set1.addAll({7, 8, 9});print(set1); // {1, 2, 3, 4, 5, 6, 7, 8, 9}
// 요소 삭제set1.remove(9);print(set1); // {1, 2, 3, 4, 5, 6, 7, 8}
// 존재 여부 확인print(set1.contains(5)); // trueprint(set1.contains(10)); // false
// 부분집합 확인print({1, 2}.isSubsetOf(set1)); // trueprint(set1.isSupersetOf({1, 2})); // true
집합 변환 및 조작
Section titled “집합 변환 및 조작”var numbers = {1, 2, 3, 4, 5};
// 매핑 (집합으로 변환)var doubled = numbers.map((n) => n * 2).toSet();print(doubled); // {2, 4, 6, 8, 10}
// 필터링var evenNumbers = numbers.where((n) => n.isEven).toSet();print(evenNumbers); // {2, 4}
// 리스트로 변환var numbersList = numbers.toList();print(numbersList); // [1, 2, 3, 4, 5] (순서는 보장되지 않음)
3. Map (맵)
Section titled “3. Map (맵)”Map은 키-값 쌍의 컬렉션으로, 키를 사용하여 값을 검색할 수 있습니다.
// 리터럴을 사용한 생성var person = { 'name': '홍길동', 'age': 30, 'isStudent': false};
// 타입을 지정한 맵Map<String, int> scores = { '수학': 90, '영어': 85, '과학': 95};
// 빈 맵 생성var emptyMap = <String, dynamic>{};
// 생성자를 이용한 맵 생성var map1 = Map<String, int>();var map2 = Map.from({'a': 1, 'b': 2});var map3 = Map.of({'x': 10, 'y': 20});
맵 접근 및 조작
Section titled “맵 접근 및 조작”var person = { 'name': '홍길동', 'age': 30, 'isStudent': false};
// 값 접근print(person['name']); // 홍길동
// 키 확인print(person.containsKey('age')); // trueprint(person.containsKey('email')); // false
// 값 확인print(person.containsValue(30)); // trueprint(person.containsValue('김철수')); // false
// 키 목록과 값 목록print(person.keys.toList()); // [name, age, isStudent]print(person.values.toList()); // [홍길동, 30, false]
// 항목 추가/업데이트person['email'] = 'hong@example.com';person['age'] = 31;print(person); // {name: 홍길동, age: 31, isStudent: false, email: hong@example.com}
// 항목 삭제person.remove('isStudent');print(person); // {name: 홍길동, age: 31, email: hong@example.com}
// 여러 항목 추가person.addAll({ 'address': '서울시', 'phone': '010-1234-5678'});print(person);// {name: 홍길동, age: 31, email: hong@example.com, address: 서울시, phone: 010-1234-5678}
// 조건부 추가person.putIfAbsent('gender', () => '남성');print(person);// {name: 홍길동, age: 31, email: hong@example.com, address: 서울시, phone: 010-1234-5678, gender: 남성}
// 이미 있는 키에는 추가되지 않음person.putIfAbsent('gender', () => '여성');print(person['gender']); // 남성 (변경되지 않음)
맵 변환 및 조작
Section titled “맵 변환 및 조작”var scores = { '수학': 90, '영어': 85, '과학': 95, '국어': 80};
// 매핑 변환var scaledScores = scores.map((k, v) => MapEntry(k, v * 1.1));print(scaledScores);// {수학: 99.0, 영어: 93.5, 과학: 104.5, 국어: 88.0}
// 필터링var highScores = scores.entries .where((entry) => entry.value >= 90) .fold(<String, int>{}, (map, entry) { map[entry.key] = entry.value; return map;});print(highScores); // {수학: 90, 과학: 95}
// forEach로 처리scores.forEach((key, value) { print('$key: $value');});// 수학: 90// 영어: 85// 과학: 95// 국어: 80
Dart는 컬렉션과 다른 이터러블(iterable) 객체를 처리하기 위한 다양한 반복문을 제공합니다.
1. for 반복문
Section titled “1. for 반복문”기본 for 반복문
Section titled “기본 for 반복문”// 기본 for 루프for (int i = 0; i < 5; i++) { print(i); // 0, 1, 2, 3, 4}
// 복잡한 조건과 증가식for (int i = 10; i > 0; i -= 2) { print(i); // 10, 8, 6, 4, 2}
for-in 반복문
Section titled “for-in 반복문”var fruits = ['사과', '바나나', '오렌지'];
// 컬렉션 항목 반복for (var fruit in fruits) { print(fruit); // 사과, 바나나, 오렌지}
// 인덱스가 필요한 경우for (int i = 0; i < fruits.length; i++) { print('${i + 1}번째 과일: ${fruits[i]}');}// 1번째 과일: 사과// 2번째 과일: 바나나// 3번째 과일: 오렌지
2. while과 do-while 반복문
Section titled “2. while과 do-while 반복문”// while 반복문int count = 0;while (count < 5) { print(count); count++;}// 0, 1, 2, 3, 4
// do-while 반복문 (최소 한 번은 실행됨)int num = 5;do { print(num); num--;} while (num > 0);// 5, 4, 3, 2, 1
3. forEach 메서드
Section titled “3. forEach 메서드”var numbers = [1, 2, 3, 4, 5];
// 리스트의 forEachnumbers.forEach((number) { print(number * 2); // 2, 4, 6, 8, 10});
// 맵의 forEachvar scores = {'수학': 90, '영어': 85, '과학': 95};scores.forEach((subject, score) { print('$subject: $score점'); // 수학: 90점 // 영어: 85점 // 과학: 95점});
4. 반복 제어
Section titled “4. 반복 제어”// break로 반복 중단for (int i = 0; i < 10; i++) { if (i == 5) break; print(i); // 0, 1, 2, 3, 4}
// continue로 현재 반복 건너뛰기for (int i = 0; i < 5; i++) { if (i == 2) continue; print(i); // 0, 1, 3, 4}
// 레이블을 사용한 중첩 반복문 제어outerLoop: for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (i == 1 && j == 1) { break outerLoop; // 바깥 반복문까지 중단 } print('$i, $j'); // 0, 0 // 0, 1 // 0, 2 // 1, 0 }}
컬렉션 처리 기법
Section titled “컬렉션 처리 기법”1. 컬렉션 for와 if
Section titled “1. 컬렉션 for와 if”Dart에서는 컬렉션 리터럴 내에 for 루프와 if 조건을 삽입할 수 있습니다.
// 컬렉션 forvar numbers = [1, 2, 3];var doubled = [ 0, for (var n in numbers) n * 2, 4];print(doubled); // [0, 2, 4, 6, 4]
// 컬렉션 ifbool includeZ = true;var letters = ['a', 'b', if (includeZ) 'z'];print(letters); // [a, b, z]
// 복합 사용var items = [ 'home', if (userLoggedIn) 'profile', for (var item in defaultItems) item, if (isAdmin) 'admin'];
2. 스프레드 연산자
Section titled “2. 스프레드 연산자”스프레드 연산자(...
)를 사용하여 컬렉션을 다른 컬렉션에 삽입할 수 있습니다.
var list1 = [1, 2, 3];var list2 = [0, ...list1, 4, 5];print(list2); // [0, 1, 2, 3, 4, 5]
// null 조건부 스프레드 연산자 (...?)List<int>? nullableList;var combined = [0, ...?nullableList, 1];print(combined); // [0, 1]
// 맵에 사용var map1 = {'a': 1, 'b': 2};var map2 = {'c': 3, ...map1};print(map2); // {c: 3, a: 1, b: 2}
// 집합에 사용var set1 = {1, 2, 3};var set2 = {0, ...set1, 4};print(set2); // {0, 1, 2, 3, 4}
3. 제너레이터 함수
Section titled “3. 제너레이터 함수”제너레이터 함수는 값의 시퀀스를 생성할 수 있습니다. Dart는 두 가지 유형의 제너레이터 함수를 지원합니다.
동기 제너레이터 (sync*)
Section titled “동기 제너레이터 (sync*)”sync*
는 Iterable 객체를 생성합니다:
Iterable<int> getNumbers(int n) sync* { for (int i = 0; i < n; i++) { yield i; }}
void main() { for (var num in getNumbers(5)) { print(num); // 0, 1, 2, 3, 4 }}
비동기 제너레이터 (async*)
Section titled “비동기 제너레이터 (async*)”async*
는 Stream 객체를 생성합니다:
Stream<int> countStream(int n) async* { for (int i = 1; i <= n; i++) { await Future.delayed(Duration(seconds: 1)); yield i; }}
void main() async { await for (var num in countStream(5)) { print(num); // 1초마다 1, 2, 3, 4, 5 출력 }}
4. 고급 컬렉션 변환 기법
Section titled “4. 고급 컬렉션 변환 기법”var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 연속 변환var result = numbers .where((n) => n % 2 == 0) // 짝수만 선택 .map((n) => n * n) // 제곱 .takeWhile((n) => n <= 36) // 36 이하인 동안만 .fold(0, (sum, n) => sum + n); // 합계 계산
print(result); // 4 + 16 + 36 = 56
// 그룹화var fruits = ['사과', '바나나', '체리', '블루베리', '아보카도'];var byFirstLetter = fruits.fold<Map<String, List<String>>>( {}, (map, fruit) { var firstLetter = fruit[0]; map[firstLetter] = (map[firstLetter] ?? [])..add(fruit); return map; },);
print(byFirstLetter);// {사: [사과], 바: [바나나], 체: [체리], 블: [블루베리], 아: [아보카도]}
Dart 2.3 이상의 컬렉션 관련 기능
Section titled “Dart 2.3 이상의 컬렉션 관련 기능”1. cascade notation (연쇄 표기법)
Section titled “1. cascade notation (연쇄 표기법)”연쇄 표기법(..
)을 사용하면 동일한 객체에서 연속적인 작업을 수행할 수 있습니다:
var list = [1, 2, 3] ..add(4) ..addAll([5, 6]) ..remove(2) ..sort();
print(list); // [1, 3, 4, 5, 6]
// 중첩 객체에도 사용 가능var map = {'user': {'name': '홍길동', 'age': 30}} ..['user']['email'] = 'hong@example.com' ..['user']['age'] = 31 ..['active'] = true;
print(map);// {user: {name: 홍길동, age: 31, email: hong@example.com}, active: true}
2. null-aware 연산자와 컬렉션
Section titled “2. null-aware 연산자와 컬렉션”null-aware 연산자를 사용하면 null 처리가 더 간결해집니다:
// ?. 연산자 (null인 경우 실행하지 않음)List<String>? nullableList;nullableList?.add('항목'); // nullableList가 null이면 아무 일도 일어나지 않음
// ?? 연산자 (null인 경우 기본값 제공)var list = nullableList ?? [];list.add('항목');print(list); // [항목]
// ??= 연산자 (null인 경우에만 값 할당)Map<String, int>? scoresMap;scoresMap ??= {};scoresMap['수학'] = 90;print(scoresMap); // {수학: 90}
1. 데이터 변환 파이프라인
Section titled “1. 데이터 변환 파이프라인”class Student { final String name; final int age; final Map<String, int> scores;
Student(this.name, this.age, this.scores);}
void main() { final students = [ Student('홍길동', 20, {'수학': 90, '영어': 85, '과학': 95}), Student('김철수', 22, {'수학': 75, '영어': 90, '과학': 85}), Student('이영희', 21, {'수학': 85, '영어': 92, '과학': 88}), Student('박민수', 23, {'수학': 95, '영어': 80, '과학': 92}), ];
// 각 학생의 평균 점수 계산 final averageScores = students.map((student) { final total = student.scores.values.fold<int>(0, (sum, score) => sum + score); final average = total / student.scores.length; return {'name': student.name, 'average': average}; }).toList();
print('평균 점수:'); for (var item in averageScores) { print('${item['name']}: ${item['average']}'); }
// 평균 90점 이상인 학생 final highPerformers = students.where((student) { final total = student.scores.values.fold<int>(0, (sum, score) => sum + score); final average = total / student.scores.length; return average >= 90; }).map((student) => student.name).toList();
print('\n우수 학생: $highPerformers');
// 과목별 최고 점수 및 학생 final subjects = {'수학', '영어', '과학'};
final topScoresBySubject = subjects.fold<Map<String, Map<String, dynamic>>>( {}, (map, subject) { var topStudent = students.reduce((a, b) => (a.scores[subject] ?? 0) > (b.scores[subject] ?? 0) ? a : b);
map[subject] = { 'student': topStudent.name, 'score': topStudent.scores[subject] }; return map; }, );
print('\n과목별 최고 점수:'); topScoresBySubject.forEach((subject, data) { print('$subject: ${data['student']} (${data['score']}점)'); });}
2. 복잡한 필터링과 정렬
Section titled “2. 복잡한 필터링과 정렬”class Product { final String id; final String name; final double price; final List<String> categories; final bool inStock;
Product(this.id, this.name, this.price, this.categories, this.inStock);}
void main() { final products = [ Product('p1', '스마트폰', 850000, ['전자제품', '통신기기'], true), Product('p2', '노트북', 1200000, ['전자제품', '컴퓨터'], false), Product('p3', '헤드폰', 120000, ['전자제품', '오디오'], true), Product('p4', '키보드', 98000, ['전자제품', '컴퓨터', '주변기기'], true), Product('p5', '마우스', 45000, ['전자제품', '컴퓨터', '주변기기'], true), Product('p6', '모니터', 550000, ['전자제품', '컴퓨터', '주변기기'], false), ];
// 1. 재고가 있는 제품만 필터링 final inStockProducts = products.where((p) => p.inStock).toList();
// 2. 컴퓨터 관련 제품만 필터링 final computerProducts = products .where((p) => p.categories.contains('컴퓨터')) .toList();
// 3. 가격 기준 정렬 (오름차순) final sortedByPrice = List<Product>.from(products) ..sort((a, b) => a.price.compareTo(b.price));
// 4. 복합 필터: 재고 있는 주변기기 중 가격이 100,000원 미만인 제품 final affordablePeripherals = products .where((p) => p.inStock && p.categories.contains('주변기기') && p.price < 100000) .toList();
// 5. 카테고리별 제품 그룹화 final productsByCategory = <String, List<Product>>{};
for (var product in products) { for (var category in product.categories) { productsByCategory[category] ??= []; productsByCategory[category]!.add(product); } }
// 결과 출력 print('재고 있는 제품: ${inStockProducts.map((p) => p.name).toList()}'); print('컴퓨터 관련 제품: ${computerProducts.map((p) => p.name).toList()}'); print('저렴한 주변기기: ${affordablePeripherals.map((p) => p.name).toList()}');
print('\n카테고리별 제품:'); productsByCategory.forEach((category, categoryProducts) { print('$category: ${categoryProducts.map((p) => p.name).toList()}'); });}
Dart의 컬렉션과 반복문은 강력하고 유연한 도구를 제공하여 데이터를 효율적으로 처리할 수 있게 해줍니다. List, Set, Map과 같은 기본 컬렉션 타입은 다양한 메서드를 제공하며, 컬렉션 for, 스프레드 연산자, 제너레이터 함수 등의 기능은 복잡한 데이터 처리 작업을 간결하게 표현할 수 있게 해줍니다.
다음 장에서는 Dart의 예외 처리에 대해 알아보겠습니다.