
앞에서 조건문과 반복문을 학습하면서 재미있는 예제를 만들고 싶었기에 이미 콜렉션을 조금 다루었었다. 콜렉션 부분은 빠르게 훑고 지나가기~
List
순서가 있는 값들이 묶인 형태
List<(타입)> (변수 이름) = [요소, 요소, 요소 … ]
특징
각각의 요소는 모두 동일한 타입이어야 한다.
List 요소의 타입에 변동이 있는 경우
var로 선언하며,List<var>변수이름 가 아닌var 변수이름으로 선언한다.
여기가 또 좀 헷갈렸는데.
var는 타입이 아니라 키워드라는 점!!
반면 List는 타입이자 클래스라는 것!!
환장하네.
아니 그러면 Var라고 하던지-_- dart문법 좀 허접한 것 같다… 체계가 별론데..;;
** var는 왜 소문자가 맞는가? **
var는 타입이 아니라 키워드이기 때문이야.
소문자로 써도 되는 이유가 바로 이것.
if, else, for, while, return, class
그리고 var
이런 것들과 같은 "언어 키워드"라서 소문자가 규칙이야.
즉 var는 int 같은 타입이 아니라,
"야 Dart 컴파일러, 이 변수의 타입 좀 너가 결정해라~"
라는 명령 키워드야.
그래서 int와 같은 급처럼 보이지만
실제로는 완전히 다른 카테고리인 거지.
** 왜 var는 대문자로 안 했을까? **
키워드는 전부 소문자
타입(클래스)은 대문자 PascalCase
이 규칙을 지키려고 한 거야.염병, 그러면 String은??
… 그냥 외우라는 소리구나..
아무튼 var의 위상은 if, while등과 같은 클래스의 위상이다. 기억할 것.
void main () {
List<String> actors = ["박정민", "구교환", "주지훈"];
print(actors);
}
// [박정민, 구교환, 주지훈]
void main () {
var actors = ["박정민", "구교환", "주지훈"];
print(actors);
}
// [박정민, 구교환, 주지훈]
하- 염병, 뭐 하나 간단하게 확인하고 넘어가려고 해도 계속 뭐가 붙잡네.
내 의도는
박정민, 구교환, 주지훈 을 프린트하는 거였는데,
어째서 [박정민, 구교환, 주지훈] 이라고 출력되는 것이냐아아아아!!
Dart에서 어떤 객체를 print()에 넣으면 내부적으로 이런 일이 벌어진다:
print(actors);
→ actors.toString()
List 클래스는 toString()을 다음 규칙으로 정의해놨어:
[요소1, 요소2, 요소3]
즉,
리스트는 대괄호
요소는 콤마로 분리
공백 하나 넣고 나열
이 포맷은 “리스트라는 자료구조의 원형 그대로 보여주겠다”는 설계 철학 때문이야.
그래서 리스트를 print 하면 항상 대괄호 포함 전체 구조가 문자열로 변환되어 출력되는 거야.-_- 허접한 언어 같으니.
“박정민, 구교환, 주지훈”만 나오게 하려면?
방법 A: join() 사용
가장 간단하고 표준적인 방법:
print(actors.join(", "));이렇게 메서드 하나를 또 주워간다.
void main () {
var actors = ["박정민", "구교환", "주지훈"];
print(actors.join(", "));
}
// 박정민, 구교환, 주지훈아마도 .join(“, “)란 각 요소를 “, “로 연결해라는 메서드이겠지?
그렇다면 또 하나 해볼 수밖에.
void main () {
var actors = ["박정민", "구교환", "주지훈"];
print(actors.join(" ♥ "));
}
// 박정민 ♥ 구교환 ♥ 주지훈ㅋㅋㅋ got it.
잡소리지만, 인블로그가 생각보다 불편하네… 토글도 없고… ;-;
Index를 통해 각 요소에 접근할 수 있다.
→ List의 요소들을 순서대로 호출. 첫 번째 요소의 Index 값은 0임
void main () {
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
int count = 0;
while (count < winterFood.length) {
print("winterFood 리스트의 ${count + 1}번째 요소는 ${winterFood[count]}");
if (count == winterFood.length - 1) {
print("번호 끝!");
}
count++;
}
}
/*
winterFood 리스트의 1번째 요소는 귤
winterFood 리스트의 2번째 요소는 굴보쌈
winterFood 리스트의 3번째 요소는 붕어빵
winterFood 리스트의 4번째 요소는 대방어
winterFood 리스트의 5번째 요소는 과메기
winterFood 리스트의 6번째 요소는 양미리
winterFood 리스트의 7번째 요소는 군고구마
번호 끝!
*/Index를 통해 요소를 바꿀 수 있다.
예제1.
void main () {
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
print(winterFood[5]);
winterFood[5] = "동치미";
print(winterFood);
}
/*
양미리
[귤, 굴보쌈, 붕어빵, 대방어, 과메기, 동치미, 군고구마]
*/Index를 통해 요소를 바꿀 수 있다는 것은 VOD 강의를 통해 알게 된 내용이다.
예제를 또 만들어보다가 몇 가지 또 착오를 겪었다.
예제2. 나쁜 예
void main () {
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
String change = winterFood[5];
print(change);
change = "동치미";
print(change);
print(winterFood);
}
/*
양미리
동치미
[귤, 굴보쌈, 붕어빵, 대방어, 과메기, 양미리, 군고구마]
*/나의 의도는
winterFood[5]를 매번 치기 귀찮으니까change로 바꿔야지.change값을 바꾸면winterFood[5]값도 바뀌겠지?그럼 목록은 예제1과 같이
양미리가동치미로 바뀌겠지?
하는 것이었다.
그러나, 실행해본 바 change값은 동치미로 바뀌었지만
winterFood[5]의 값은 바뀌지 않았다.
좌항과 우항, 순서의 문제일까?
예제3.
void main () {
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
winterFood[5] = String change;
print(change);
change = "동치미";
print(change);
print(winterFood);
}
// 코드 오류로 실행조차 못함...예제3은 코드 오류로 실행조차 못했다.
Dart 문법에서 “타입 + 변수명”은 변수를 ‘선언할 때만’ 사용하는 문법이야.
String change는 “change라는 변수를 선언하겠다”라는 의미고,
이걸 winterFood[5] = 뒤에 넣는 건 문법적으로 성립할 수가 없어.
예를 들어 이런 문장은 불가능해:
x = int y;
x = String name;
변수를 선언하고 싶으면 반드시 코드의 한 줄을 완전히 차지해야 해.보아하니 나는 여전히 프로그래밍 언어와 수학적 연산을 헷갈리고 있는 듯하다.
흐음… 그러면 이건 어떨까?
void main () {
List<String> winterFood = ["귤", "굴보쌈", "붕어빵", "대방어", "과메기", "양미리", "군고구마"];
String change = "동치미";
winterFood[5] = change;
print(winterFood);
}
// [귤, 굴보쌈, 붕어빵, 대방어, 과메기, 동치미, 군고구마]I made it.
Simple is the best 라는 거다.
아무튼 예제를 만들다가 또 좀 샜는데,Index를 사용해서 List의 요소를 바꿀 수 있다는 것! 잘 알았다.
List와 관련한 메서드
.length: 요소의 개수
var actors = ['박정민', '구교환', '주지훈'];
print(actors.length); // 3
print(actors[actors.length - 1]); // 주지훈.isEmpty: 빈 배열인지 아닌지 판별
var actors = ['박정민', '구교환', '주지훈'];
print(actors.isEmpty); // false
print(actors.isNotEmpty); // true.indexOf(): 특정 요소의 index를 알 수 있음.var actors = ['박정민', '구교환', '주지훈']; print(actors.indexOf('구교환'); // 1 print(actors.indexOf('침착맨'); // -1
.add(),.addAll()을 통해 요소를 추가할 수 있음.
var actors = ['박정민', '구교환', '주지훈'];
actors.add('정해인');
print(actors); // [박정민, 구교환, 주지훈, 정해인]
var actors = ['박정민', '구교환', '주지훈', '정해인'];
actors.addAll(['이동욱', '공유']);
print(actors); // [박정민, 구교환, 주지훈, 정해인, 이동욱, 공유]remove(),removeAt()을 통해 요소를 삭제할 수 있음.
→ 이라고 강의에 나왔으나, 이하의 다양한 실험을 통해 나는 다음과 같이 다시 정리한다.
-remove(): 첫 번째로 발견한 요소만 삭제한다.
-removeAt(): 인덱스 기준 해당 값을 삭제한다.
이 정리를 완성하기까지 헤메였던 흐름은 아래와 같다.
trial 1. - 성공
var actors = ['박정민', '구교환', '주지훈', '정해인', '이동욱', '공유'];
actors.remove('정해인');
print(actors); // [박정민, 구교환, 주지훈, 이동욱, 공유].remove()를 통해 1개 요소를 삭제하는 법은 확인했다.
그러나 우리는 위에서 .addAll() 이라는 메서스가 있는 걸 봤다.
removeAll( )
.removeAll()도 가능한지 해본다. → 그러나 그런 건 없었다.
GPT에게 질문해보니
Set 에는 removeAll()이 있고,
List에서는 removeWhere()을 사용할 수 있단다.
왜 List에는 removeAll이 없을까?
여기서 사고 확장을 데려가보자.
관점 A. List는 순서 + 중복을 허용하는 자료구조
List에서 "어떤 요소를 몇 개 제거할 것인지"는 모호함
예: removeAll([3])이면
리스트 내 3이 2개인지 10개인지도 모르고 “전부 지우나?” “처음 것만 지우나?”라는 선택지가 생겨.
Dart 언어 설계는 이런 모호성을 피하고 **명시적 조건 기반 삭제(removeWhere)**를 선택한 거라고 볼 수 있어.
관점 B. Set은 중복이 없고 순서가 없기 때문에 removeAll이 논리적으로 명확
집합은 원래 차집합 개념이라 “이 목록에 있는 요소 전부 제거”라는 정의가 자연스럽다.
그래서 Set에는 removeAll()이 있고, List에는 없다.아주 명확하다
그렇다. 리스트는 순서를 중심으로 하고, 중복을 허용한다.
그러니 어떤 요소를 삭제하라고 하면 리스트 내의 요소를 모두 삭제하는 것인지, 첫 번째 것만 삭제하는 것인지 불분명해진다.
…음? 그러면 remove()자체도 성립하지 않아야 하는 거 아니야?
.remove( ) 와 .indexOf( )
trial2. - List에서 중복된 요소remove()를 하면 가장 앞에 있는 요소가 삭제된다.
void main () {
var actors = ['박정민', '구교환', '주지훈', '박정민', '이동욱', '박정민'];
actors.remove('박정민');
print(actors);
} // [구교환, 주지훈, 박정민, 이동욱, 박정민]오호라. List는 리스트 전체를 훑는 것이 아니라, 가장 먼저 만나는 요소에 대해 작용한다는 논리인 것 같군.
한 번 더 확인하기 위해 .indexOf를 사용해본다.
var actors = ['박정민', '구교환', '주지훈', '박정민', '이동욱', '박정민'];
print(actors.indexOf('박정민')); // 0.removeAt( ) : 해당 인덱스의 값을 삭제.
var actors = ['박정민', '구교환', '주지훈', '박정민', '이동욱', '박정민'];
actors.removeAt(3);
print(actors); // [박정민, 구교환, 주지훈, 이동욱, 박정민].removeWhere(value) : value 값을 모두 삭제.
List는 순서가 중요하다보니, 마치 그 순서의 자리들이 공간처럼 다루어지는 것이다. 그래서 매서드가 where, range등의 형태를 취하는 것 같다.
var actors = ['박정민', '구교환', '주지훈', '박정민', '이동욱', '박정민'];
actors.removeWhere((name) => name == '박정민');
print(actors); // [구교환, 주지훈, 이동욱]여기서 name은 정의되지 않았는데 어떻게 작동하는고 하면,name은 .removeWhere()이라는 함수의 파라미터로 선언되고 있는 셈이다.
removeWhere()의 정의는 아래와 같다.
bool test(String name) {
return name == '박정민';
}위에서 String name이라고 선언된 것이 축약형에서는 var name으로 들어가는 것인가?라고 생각했으나, GPT에게 물어보니 그건 또 아니란다.
removeWhere의 (name) → 어떻게 타입이 결정될까?
리스트가 이렇게 되어 있으니:
List<String> actors = [...]
removeWhere의 요구 타입은:
void removeWhere(bool Function(String element) test)
그래서 Dart은 추론한다:
(name) => ... → (String name) => ...
이 과정에서 var가 등장하지 않는다.그런 것이다. 위에서 이미 나는 List<String> actors를 통해 actors의 요소 타입을 선언해놓았기 때문에 .removeWhere( )에서 바로 변수타입을 알 수 있는 것이다.
.removeRange(start, end) : 인덱스 기준 특정 범위 삭제
var list = ['A', 'B', 'C', 'D', 'E'];
list.removeRange(1, 4);
print(list); // [A, E]
/* start는 포함하고 end는 포함하지 않는다.
이것은 C, Java, Python 등 대부분의 프로그래밍 언어의 전통이다.
이 규칙을 따를 경우 (start, end)구간의 요소 개수는 `end - start`로 직관적이고,
대부분의 언어가 슬라이싱에서 같은 규칙을 사용하는 만큼 효율적이다. */.clear() : 모든 요소 삭제
var actors = ['박정민', '구교환', '주지훈', '박정민', '이동욱', '박정민'];
actors.clear();
print(actors.isEmpty()); // true