
* 문제
당신에게 m x n 정수 그리드 accounts가 주어집니다.
accounts[i][j]는 i번째 고객이 j번째 은행에 가지고 있는 금액을 의미합니다.
가장 부유한 고객이 가지고 있는 재산을 반환하세요.
한 고객의 재산(wealth)은 그 고객이 가진 모든 은행 계좌 금액의 합입니다.
가장 부유한 고객은 재산이 가장 큰 고객입니다.
* 예시 1
입력: accounts = [[1,2,3],[3,2,1]]
출력: 6
설명:
1번째 고객의 재산 = 1 + 2 + 3 = 6
2번째 고객의 재산 = 3 + 2 + 1 = 6
두 고객 모두 재산이 6으로 동일하게 가장 많으므로 6을 반환합니다.
* 예시 2
입력: accounts = [[1,5],[7,3],[3,5]]
출력: 10
설명:
1번째 고객의 재산 = 6
2번째 고객의 재산 = 10
3번째 고객의 재산 = 8
가장 부유한 고객은 재산이 10인 2번째 고객입니다.
* 예시 3
입력: accounts = [[2,8,7],[7,1,3],[1,9,5]]
출력: 17
* 제한 사항
m == accounts.length
n == accounts[i].length
1 <= m, n <= 50
1 <= accounts[i][j] <= 100
리스트 안에 리스트를 반복해서 돌면서 모두 합산한 다음에 total 값을 만들어서 저장한 후에 큰 값을 reduce로 구한 후 반환하는 식으로 만들었다. 일단 생각나는대로 작성해보고 제출했더니 한 번에 성공해서 아주 햅삐~
class Solution {
int maximumWealth(List<List<int>> accounts) {
int maximumWealth = 0;
List<int> total = [];
int sum = 0;
for (var i = 0; i < accounts.length; i++) {
for (var j = 0; j < accounts[i].length; j++) {
sum += accounts[i][j];
}
total.add(sum);
sum = 0;
}
maximumWealth = total.reduce((a, b) => a > b ? a : b);
return maximumWealth;
}
}
* 문제
어떤 마을에는 1부터 n까지 번호가 매겨진 n명의 사람이 있다. 이들 중 한 명이 비밀리에 마을 판사라는 소문이 있다.
만약 마을 판사가 존재한다면 다음 조건을 만족한다:
마을 판사는 아무도 신뢰하지 않는다.
마을 판사를 제외한 모든 사람은 마을 판사를 신뢰한다.
위 두 조건을 만족하는 사람은 정확히 한 명이다.
trust 배열이 주어지고, trust[i] = [ai, bi]는 사람 ai가 사람 bi를 신뢰한다는 의미이다.
trust 배열에 존재하지 않는 신뢰 관계는 존재하지 않는 것으로 간주한다.
마을 판사가 존재하며 식별할 수 있다면 그 사람의 번호를 반환하고, 존재하지 않는다면 -1을 반환하라.
* 예시 1
입력: n = 2, trust = [[1,2]]
출력: 2
* 예시 2
입력: n = 3, trust = [[1,3],[2,3]]
출력: 3
* 예시 3
입력: n = 3, trust = [[1,3],[2,3],[3,1]]
출력: -1
* 제한 사항
1 <= n <= 1000
0 <= trust.length <= 10^4
trust[i].length == 2
모든 신뢰 관계 쌍은 유일하다.
ai != bi
1 <= ai, bi <= n
처음에는 신뢰 받는 리스트(판사 후보)와 신뢰하는 사람들(마을 사람들)을 나눠서 Set에 추가한 후에 차집합을 해서 남는 사람을 판사로 가정했다. 테스트에는 통과했지만.. 제출에는 오류가 생겨서 다른 방법을 생각해봤다.
class Solution {
int findJudge(int n, List<List<int>> trust) {
Set<int> trustPeople = {}; // 신뢰받는 사람 리스트 (판사 후보)
Set<int> flowerPeople = {}; // 신뢰하는 사람 리스트
for (var i = 0; i < trust.length; i++) {
for (var j = 0; j < trust[i].length; j++) {
if (j != trust[i].length - 1) {
flowerPeople.add(trust[i][j]);
} else {
trustPeople.add(trust[i][j]);
}
}
var result = trustPeople.difference(flowerPeople);
if (result.isEmpty) {
n = -1;
} else {
n = result.first;
}
}
return n;
}
}
다음 방법은 튜터님과 이야기해보면서 풀어본 방법인데 처음에 판사 후보를 임의로 정하고 (trust[0][1]) 그 후에 반복문으로 돌면서 신뢰받는 사람이라면 (trust[i][1]) 처음에 설정했던 값을 반환하고 아니라면 -1을 반환하게 코드를 짰다.
class Solution {
int findJudge(int n, List<List<int>> trust) {
int judge = trust[0][1];
for (var i = 1; i < trust.length; i++) {
for (var j = 0; j < trust[i].length; j++) {
if (judge == trust[i][1]) {
judge = trust[1][1];
} else {
judge = -1;
}
}
}
return judge;
}
}
그랬더니 나오는 요시끼.. [[1,3],[1,4],[2,3],[2,4],[4,3]] 아 그렇지.. 판사 후보가 매번 신뢰 받지 않을 수도 있다는 걸 깨달았다.. 그리고 판사는 마을 사람 모두에게 신뢰 받아야한다는 조건도 있었다..
class Solution {
int findJudge(int n, List<List<int>> trust) {
Set<int> trustPeople = {}; // 신뢰받는 사람 리스트 (판사 후보)
Set<int> flowerPeople = {}; // 마을 사람 리스트
List<int> trustNum = []; // 판사 후보가 신뢰 받는 횟수
int count = 0;
int finalNum = 0;
for (var i = 0; i < trust.length; i++) {
for (var j = 0; j < trust[i].length; j++) {
if (j == 0) {
flowerPeople.add(trust[i][0]); // 마을 사람 리스트
} else if (j == 1) {
trustPeople.add(trust[i][1]); // 판사 리스트
trustNum.add(trust[i][1]); // 판사 신뢰 받는 수
}
}
}
var result = trustPeople.difference(flowerPeople);
// result가 비어있지 않을 때
if (result.isNotEmpty) {
// 신뢰 받는 리스트를 돌면서 검사
for (var t in trustNum) {
if (t == result.first) {
count++;
}
}
if (count == n - 1) {
finalNum = result.first;
} else {
finalNum = -1;
}
} else {
finalNum = -1;
}
return finalNum;
}
}
그래서 다시 처음으로 돌아가서 판사 후보에서 마을 사람들을 뺀 후에 trustNum에서 판사 후보가 몇 번 나오는지 확인하고 (마을 사람 모두에게 신뢰 받아야하기 때문에) 그 값이 마을 사람 - 1 (n-1) 값과 같다면 그 사람이 판사니 숫자를 반환 아니라면 -1을 반환하게 코드를 짰다. 그랬더니..
n = 1, [[0]] 뭐 이딴 게 나왔다..ㅋㅋ 장난하나 마을에 사람이 1명이면 이걸 누가 마을이라고 불러!!! 아무튼 신뢰가 왔다갔다하는 list가 없지만 마을에는 판사가 1명이니 n = 1라면 1을 반환해야 한다.
else if ( n == 1) {
finalNum = 1;
}
이렇게 아래를 수정해줬더니 제출 성공했다..ㅋㅋ 머 생각할 것도 많고 머시기했던 문제였다.
행(Row)과 열(Column) 기반 레이아웃을 만들 때 사용
HTML table과 유사
행과 열로 정확한 레이아웃 가능
Table(
border: TableBorder.all(), // 테두리
columnWidths: {
0: FixedColumnWidth(100), // 첫 번째 열 고정 너비
1: FlexColumnWidth(), // 남은 공간 사용
},
defaultVerticalAlignment: TableCellVerticalAlignment.middle, // 세로 정렬
children: [
TableRow(children: [
Text('이름'),
Text('점수'),
]),
TableRow(children: [
Text('철수'),
Text('90'),
]),
],
)
| 속성 | 타입 | 설명 |
|---|---|---|
children | List<TableRow> | 테이블의 행들을 지정 |
border | TableBorder | 테이블 또는 셀 테두리 |
columnWidths | Map<int, TableColumnWidth> | 열별 너비 지정 |
defaultColumnWidth | TableColumnWidth | 열 너비 기본값 지정 |
defaultVerticalAlignment | TableCellVerticalAlignment | 행 높이에 맞춘 수직 정렬 |
| 타입 | 설명 |
|---|---|
FixedColumnWidth(double width) | 고정된 너비 |
FlexColumnWidth([double flex]) | 남은 공간을 비율로 나눔 (flex 값 사용 가능) |
FractionColumnWidth(double fraction) | 부모 너비의 비율로 지정 (0~1) |
| 속성 | 설명 |
|---|---|
children | 각 열에 들어갈 위젯 리스트 |
decoration | 행 배경색 등 스타일 |
key | 고유 키 지정 가능 |
주의: 모든 TableRow의 열 개수는 동일해야 함
SliverAppBar는 스크롤 영역과 연동되는 AppBar
스크롤하면 숨김, 축소, 고정, 확장 등의 동작을 제어할 수 있음
일반 AppBar는 고정형, SliverAppBar는 스크롤에 반응하는 유동형 AppBar
상단 영역이 스크롤에 따라 변해야 할 때 사용
위로 스크롤 시 AppBar 사라짐
아래로 스크롤 시 다시 나타남
이미지 큰 배너가 스크롤되며 축소되는 UI 필요 시 (예: 인스타 프로필 상단, 쇼핑몰 상세 페이지)
반드시 CustomScrollView, NestedScrollView 안에서만 사용 가능 단독 사용 불가
CustomScrollView(
slivers: [
SliverAppBar(...),
SliverList(...),
],
)
| 속성 | 설명 |
|---|---|
expandedHeight | 확장될 때 최대 높이 |
pinned | true면 스크롤해도 AppBar 상단에 고정 |
floating | 위로 조금만 스크롤해도 즉시 AppBar 나타남 |
snap | floating과 함께 사용 → 나타날 때 애니메이션처럼 ‘착’ 붙음 |
flexibleSpace | 확장/축소 영역(UI 넣는 공간) |
backgroundColor | 배경 색 |
title | 축소된 상태에서 보이는 제목 |
centerTitle | 제목 중앙 정렬 여부 |
elevation | 그림자 |
pinned
스크롤해도 AppBar가 완전히 사라지지 않고 최소 높이 형태로 고정
쇼핑몰/카카오톡 채팅 스크롤 시 상단 고정 효과
floating
스크롤 약간 올리면 AppBar가 즉시 다시 나타남
SNS 타임라인 UI에서 많이 사용
snap
floating:true와 함께만 사용
나타날 때 애니메이션처럼 ‘딱’ 붙음
| pinned | floating | snap | 동작 설명 |
|---|---|---|---|
| true | false | false | 스크롤 후 AppBar 줄어든 상태로 상단 고정 |
| false | true | false | 스크롤 살짝만 올라가도 AppBar 나타남 |
| false | true | true | 나타날 때 빠르게 달라붙는 효과 |
| true | true | true | 고정 + 스냅 + 즉시 나타남 |
랜덤 이미지
width / height 값을 설정하면 랜덤 이미지가 나옴
http://picsum.photos/1000/800
이번 심화 과제(성적 분석 프로그램)를 진행하면서 마주쳤던 문제들과 이를 해결해 나가는 과정을 정리해 보았다.
과제 요구사항 중 "파일 값을 StudentScore 객체로 변환하고 이를 List<StudentScore>에 저장합니다" 라는 부분이 처음에는 직관적으로 이해되지 않았다. 지금까지는 List<String>이나 List<int>처럼 기본 자료형만 리스트에 담아봤기 때문에 "내가 직접 만든 Class도 리스트의 타입이 될 수 있나?" 라는 의문이 들었다.
Dart의 List는 Generic을 지원하여 Widget, Class 등 모든 객체 타입을 담을 수 있다는 것을 알게 되었다. 이를 통해 단순히 이름(String)이나 점수(int)를 따로 관리하는 것이 아니라, StudentScore라는 객체 하나에 묶어서 관리하는 객체 지향적인 리스트 운용 방식을 이해하게 되었다.
// 이전 생각
List<String> names = ['홍길동', '김철수'];
List<int> scores = [90, 80];
// 바뀐 생각
List<StudentScore> students = [
StudentScore('홍길동', 90),
StudentScore('김철수', 80),
];
리스트에 데이터가 잘 들어갔는지 확인하기 위해 print(studentsList)를 실행했더니, 기대했던 데이터 대신 알아볼 수 없는 문구가 출력되었다.
[Instance of 'StudentScore', Instance of 'StudentScore']
이는 객체 자체를 출력할 때 Dart가 기본적으로 보여주는 형태이다. 튜터님께 조언을 구한 결과, 클래스 내부에서 toString() 메서드를 오버라이딩(@override) 해줘야 한다는 것을 알게 되었다.
class StudentScore {
// ... 기존 코드 ...
String toString() {
return "($name, $score)"; // 원하는 출력 형식 지정
}
}
이렇게 재정의해주니 print를 했을 때 비로소 (홍길동, 90) 처럼 사람이 읽을 수 있는 형태로 데이터가 출력되었다.
사용자가 입력한 이름이 리스트에 있는지 확인하는 로직을 짤 때, 처음에는 for문을 돌며 하나씩 비교했다. 그랬더니 찾는 이름이 아닌 나머지 요소들을 검사할 때마다 "유효하지 않은 값입니다" 라는 문구가 리스트 개수만큼 반복 출력되는 문제가 발생했다.
반복문 안에서 즉시 print를 하는 대신, "먼저 찾고(Find), 나중에 처리(Handle)" 하는 방식으로 로직을 변경했다. where를 사용해 일치하는 요소를 찾고, firstOrNull을 통해 값이 없으면 null을 반환하도록 처리하여 중복 검사를 없앴다.
추가로, 부분 포함(contains)이 아니라 정확히 일치하는 이름을 찾기 위해 비교 연산자(==)를 사용했다.
// 개선된 코드
var result = studentsList
.where((element) => element.name == input) // 이름이 같은 요소를 필터링
.firstOrNull; // 있으면 첫 번째 객체 반환, 없으면 null 반환
if (result != null) {
result.showInfo(); // 정보 출력
} else {
print("유효하지 않은 값입니다."); // 한 번만 출력
}
가장 점수가 높은 '우수 학생'을 뽑는 로직을 구현할 때 점수(score)는 비교하고 싶은데 결과로 반환하고 싶은 것은 이름과 점수가 모두 들어있는 객체 전체여서 비교 대상과 반환 대상이 달라서 초기에는 로직 구성이 헷갈렸다.
Dart의 reduce 함수는 두 요소(a와 b)를 비교하여 비교 결과 값을 다음 비교의 a로 전달하며 리스트를 하나의 값(객체)으로 줄여나간다. 핵심은 reduce 안에 들어가는 함수가 최종적으로 StudentScore 객체를 반환하는 것
void goodStudent() {
var studentsList = loadStudentData('lib/reports/students_list.txt');
// reduce가 'a'와 'b'를 받습니다.
// a와 b 모두 StudentScore 객체입니다.
StudentScore goodStudent = studentsList.reduce(
(a, b) => a.score > b.score ? a : b, // 점수는 비교하지만,
// 반환하는 것은 'a' 또는 'b' 객체
);
print("우수생: ${goodStudent.name} (점수: ${goodStudent.score})");
}
| 메서드 | 기능 |
|---|---|
contains(value) | 요소 포함 여부 |
add(value) | 요소 추가, 이미 있으면 false |
addAll(elements) | 여러 요소 추가 |
remove(value) | 요소 삭제 |
lookup(object) | 실제 저장된 동등 요소 반환 |
removeAll(elements) | 특정 전체 삭제 |
retainAll(elements) | 주어진 요소만 남김 |
removeWhere(test) | 조건에 맞는 요소 제거 |
retainWhere(test) | 조건에 맞는 요소만 유지 |
containsAll(other) | other의 모든 요소 포함 여부 |
intersection(other) | 교집합 |
union(other) | 합집합 |
difference(other) | 차집합 |
clear() | 전체 삭제 |
toSet() | 동일 요소를 가진 새 set 반환 |
두 집합에서 공통으로 존재하는 요소만 모은 집합
공통되지 않으면 포함되지 않음
final a = {1, 2, 3};
final b = {2, 3, 4};
final result = a.intersection(b);
print(result); // {2, 3}
교집합은 겹치는 부분만 추출
두 집합의 모든 요소를 합치되 중복은 제거
Set은 원래 중복을 허용하지 않음
final a = {1, 2, 3};
final b = {3, 4, 5};
final result = a.union(b);
print(result); // {1, 2, 3, 4, 5}
같은 요소가 있어도 하나만 남음
첫 번째 집합에서 두 번째 집합에 포함된 요소를 제거
즉, A - B
final a = {1, 2, 3, 4};
final b = {3, 4};
final result = a.difference(b);
print(result); // {1, 2}
b에 있는 3, 4가 제거되어 1, 2만 남음
오늘 과제랑 README.md, 트러블 슈팅을 다 작성해서 알고리즘 문제를 풀어봤다. 알고리즘 문제가 얼탱이 없지만 오늘 판사 문제가 썩 얼탱이 없어서..ㅋㅋ 푸는데 좀 애를 먹은 것 같다. 내일은 더 재밌는 문제를 풀어바야지