데이터를 담고(입력, List/Map) -> 검증(유틸리티, if) -> 함수로 쪼개서 분류/연산(출력)하는 흐름을 이해하는 예제를 다시 풀어보자
실제 앱 프로젝트에서 가장 흔하게 쓰이는
"재고 관리(Inventory) 또는 장바구니" 예제
데이터의 유무 확인, 수량 증감, 총액 계산 등 실제 앱의 핵심 로직을 모두 포함하고 있음
🎯 목표
🧩 프로그램 동작 방식
메뉴를 반복해서 보여준다.
========== 가계부 ==========
1. 내역 추가 (수입/지출)
2. 전체 내역 보기
3. 요약 보기 (총 수입/총 지출/잔액)
4. 카테고리별 지출 합계 보기
📦 저장 구조 (클래스 없이! 난이도는 조금만 올림)
각 내역(Map)은 아래 키를 반드시 가진다:
※ 날짜는 실제 DateTime을 써도 되지만, 초급+라서 문자열로 저장해도 OK
1️⃣ 내역 추가 (수입/지출)
입력 흐름:
예시:
유형을 선택하세요 (1:수입, 2:지출): 2
제목을 입력하세요: 점심
금액을 입력하세요: 9000
카테고리를 입력하세요(예: 식비/교통/쇼핑): 식비
저장 완료!
2️⃣ 전체 내역 보기
저장된 모든 내역을 번호와 함께 출력한다.
예시 출력 포맷(자유):
1) [지출] 점심 - 9000원 (식비) / 2026-02-13 14:20
내역이 없으면:
"저장된 내역이 없습니다."
3️⃣ 요약 보기
예시:
총 수입: 3000000원
총 지출: 45000원
잔액: 2955000원
4️⃣ 카테고리별 지출 합계 보기 (난이도 쪼~끔 업!)
예시:
[카테고리별 지출]
식비: 18000원
교통: 2500원
쇼핑: 15000원
📌 구현 조건
📌 main 함수에는 흐름만 작성할 것
void main() {
final List<Map<String, dynamic>> ledgerEntryList = [];
runLedgerProgram(ledgerEntryList);
}
📌 프로그램 루프 함수
void runLedgerProgram(List<Map<String, dynamic>> ledgerEntryList) {
// TODO: 구현
throw UnimplementedError();
}
📌 메뉴 출력 함수
void printMenu() {
// TODO: 구현
throw UnimplementedError();
}
📌 내역 추가 함수
void addLedgerEntry(List<Map<String, dynamic>> ledgerEntryList) {
// TODO: 구현
throw UnimplementedError();
}
📌 전체 내역 출력 함수
void printAllEntries(List<Map<String, dynamic>> ledgerEntryList) {
// TODO: 구현
throw UnimplementedError();
}
📌 요약 출력 함수
void printSummary(List<Map<String, dynamic>> ledgerEntryList) {
// TODO: 구현
throw UnimplementedError();
}
📌 카테고리별 지출 합계 출력 함수
void printExpenseSummaryByCategory(List<Map<String, dynamic>> ledgerEntryList) {
// TODO: 구현
throw UnimplementedError();
}
📌 (유틸) 문자열 입력 받기
String readNonEmptyString({required String prompt}) {
// TODO: 구현
throw UnimplementedError();
}
📌 (유틸) 양수 정수 입력 받기
int readPositiveInt({required String prompt}) {
// TODO: 구현
throw UnimplementedError();
}
📌 (유틸) 현재 시간을 문자열로 만드는 함수
String buildNowTimestampString() {
// TODO: 구현
throw UnimplementedError();
}
📌 과제: 콘솔 기반 "스마트 키오스크(장바구니) 프로그램"
🎯 목표
상품 목록에서 메뉴를 골라 장바구니에 담는다.
장바구니에 담긴 상품의 수량을 조절하거나 삭제한다.
최종 주문 시 총액을 계산하고 할인 혜택을 적용한다.
🧩 프로그램 동작 방식
메뉴를 반복해서 보여줍니다.
Plaintext
========== 🍔 SMART KIOSK ==========
1. 메뉴 보기 (상품 목록)
2. 장바구니 담기
3. 장바구니 보기 및 수량 조절
4. 주문하기 (최종 결제)
프로그램 시작 시 아래 3개 이상의 상품을 List<Map<String, dynamic>>에 넣어둡니다.
키: 'id'(int), 'name'(String), 'price'(int)
2️⃣ 장바구니 (사용자 입력 데이터)
List<Map<String, dynamic>> cartList에 저장합니다.
각 아이템은 아래 키를 가집니다:
'name' : 상품명
'price' : 단가
'count' : 수량 (기본 1)
🛠 주요 기능 요구사항
1️⃣ 메뉴 보기
준비된 상품 목록을 번호와 함께 출력합니다.
2️⃣ 장바구니 담기
상품 번호를 입력받아 cartList에 추가합니다.
(중요) 이미 장바구니에 있는 상품이라면 새로 추가하지 않고, 기존 항목의 'count'(수량)만 1 증가시킵니다. (데이터 흐름 이해의 핵심!)
3️⃣ 장바구니 보기 및 수량 조절
현재 담긴 상품명, 단가, 수량, 소계(단가 * 수량)를 출력합니다.
추가로 "수량을 변경하시겠습니까? (y/n)"를 물어보고, 'y'인 경우 상품 번호와 새 수량을 입력받아 업데이트합니다.
수량을 0으로 입력하면 해당 항목을 삭제합니다.
4️⃣ 주문하기 (최종 결제)
모든 상품의 총액을 계산합니다.
할인 조건: 총액이 30,000원 이상이면 10% 할인을 적용합니다.
최종 결제 금액을 출력하고 장바구니를 비웁니다.
📌 구현 조건
모든 코드는 main.dart 파일 하나에 작성합니다.
클래스 사용 금지 (Map과 List의 구조에 익숙해지는 것이 목적입니다).
함수를 6개 이상으로 분리하세요.
int.tryParse를 사용하여 잘못된 입력(문자 등) 시 예외 처리를 반드시 포함하세요.
main 함수에는 프로그램 전체 흐름만 작성하세요.
💻 시작 코드 (스켈레톤)
이 구조를 복사해서 // TODO 부분을 채워보세요.
import 'dart:io';
void main() {
// 상품 목록 데이터 (초기 세팅)
final List<Map<String, dynamic>> products = [
{'id': 1, 'name': '불고기버거', 'price': 6500},
{'id': 2, 'name': '치즈버거', 'price': 5500},
{'id': 3, 'name': '감자튀김', 'price': 2500},
{'id': 4, 'name': '콜라', 'price': 2000},
];
// 장바구니 데이터
List<Map<String, dynamic>> cartList = [];
runKioskProgram(products, cartList);
}
📌 프로그램 전체 루프
void runKioskProgram(List<Map<String, dynamic>> products, List<Map<String, dynamic>> cartList) {
// TODO: while문과 switch-case를 사용하여 메뉴를 구성하세요.
}
📌 메뉴(상품 목록) 출력 함수
void printProductList(List<Map<String, dynamic>> products) {
// TODO: products 리스트를 순회하며 상품 정보를 출력하세요.
}
📌 장바구니 담기 함수
void addToCart(List<Map<String, dynamic>> products, List<Map<String, dynamic>> cartList) {
// TODO: 상품 번호를 입력받아 cartList를 업데이트하세요.
}
📌 장바구니 보기 및 수량 변경 함수
void showCart(List<Map<String, dynamic>> cartList) {
// TODO: 장바구니 목록을 보여주고 수량 수정 여부를 물어보세요.
}
📌 주문 및 결제 함수
void processOrder(List<Map<String, dynamic>> cartList) {
// TODO: 결제 로직을 구현하세요.
}
📌 (유틸) 숫자 입력 함수
int readIntInput(String prompt) {
// TODO: 사용자로부터 숫자를 안전하게 입력받으세요.
throw UnimplementedError();
}
💡 힌트 및 응원
수량 증가 로직: cartList를 for문으로 돌면서 사용자가 선택한 상품 이름이 이미 존재하는지 먼저 확인하는 것이 포인트입니다.
ToDo 앱과의 연결: ToDo 앱에서 '완료 여부(isDone)'를 바꾸는 것이나, 이 예제에서 '수량(count)'을 바꾸는 것이나 결국 "리스트 안의 특정 맵(Map) 데이터에 접근해 값을 수정하는 것"으로 원리는 같습니다.
상품리스트를 출력할 때 id의 값을 따로 주고 반복해서 출력할 방법을 생각하다가 2중으로 작성했다.
이중으로 반복문을 사용하면 상품이 4개일 경우 4*4 총 16번이 출력이 된게 된다!
상품 하나당 전체 리스트 길이만큼 출력이 되기 때문
for 반복문이 작동하는 방식의 이해도 부족
// 이미 있는 메뉴(상품 목록) 출력 함수
void printProductList(List<Map<String, dynamic>> products) {
// products 리스트를 순회하며 상품 정보를 출력하세요.
// 리스트 안의 상품 정보를 하나씩 꺼내서 출력을 해야됨
int id = 0;
for (var prd in products) {
for (var id = 0; id <= products.length; id++) {
// 리스트 순회하는 함수 사용?
print(
'${prd['id'] + 1}번: ${prd['name']} ${prd['price']}원, ${prd['count']}개',
);
}
}
}
이중 반복문을 삭제하고 코드를 더 간결하게 변경했다.
void printProductList(List<Map<String, dynamic>> products) {
for (var prd in products) {
print('${prd['id']}번: ${prd['name']} ${prd['price']}원');
}
}
흐름이해를 위해 구조를 어떻게 짜야할지 많이 참고해서 작성을 해보았다.
! if (selectProduct == null) {}을 for문 안쪽에서 작성 시 첫번째 상품만 검사하고 없다고 판단을 내리고 종료시킬 수 있다.
for문 밖으로 꺼내면 '!' 이 없이도 에러가 해결 될 수 있다.
// 장바구니 담기 함수. todo앱의 수정로직과 같음!
void addToCart(
List<Map<String, dynamic>> products,
List<Map<String, dynamic>> cartList,
) {
// 상품 번호를 입력받아 cartList를 업데이트하세요.
int productId = readIntInput('장바구니에 담을 상품번호를 입력하세요.');
// 1. 선택한 번호가 실제 상품목록 안에 있는지 확인
// 맵의 변수를 만들어주고
Map<String, dynamic>? selectProduct;
for (var prd in products) {
// 만약 입력한 상품번호가 목록의 제품 아이디와 같다면, 상품목록에 있는게 맞다.
if (prd['id'] == productId) {
selectProduct = prd;
break;
}
// 만약 입력한 상품번호가 목록의 제품 아이디에 없다면 존재하지 않음을 알려주고 종료한다.
if (selectProduct == null) {
print('존재하지 않는 상품정보 입니다. 다시 입력해주세요.');
return;
}
}
// 2. 장바구니에 이미 이 상품이 있는지 확인하기. 있으면 기존 항목의 count만 1증가시키기
bool isExist = false;
for (var item in cartList) {
// 위에서 selectProduct의 값이 null이 아닐 경우 넘어오는 것이기 때문에 절대 null이 아니라고 !를 붙여준다.
if (item['name'] == selectProduct!['name']) {
// 이미 있다면
item['count']++;
isExist = true;
print('${item['name']}의 수량이 추가되었습니다.');
break;
}
}
// 3. 장바구니에 없다면 추가하기
if (!isExist) {
cartList.add({
'name': selectProduct!['name'],
'price': selectProduct['price'],
'count': 1,
});
print('${selectProduct['name']}을 장바구니에 담았습니다.');
}
}
코드 수정
void addToCart(
List<Map<String, dynamic>> products,
List<Map<String, dynamic>> cartList,
) {
int productId = readIntInput('장바구니에 담을 상품번호를 입력하세요.');
Map<String, dynamic>? selectProduct;
for (var prd in products) {
if (prd['id'] == productId) {
selectProduct = prd;
break;
}
}
// 루프가 끝나고 한번만 검사한다!
if (selectProduct == null) {
print('존재하지 않는 상품정보 입니다. 다시 입력해주세요.');
return;
}
bool isExist = false;
for (var item in cartList) {
if (item['name'] == selectProduct['name']) {
item['count']++;
isExist = true;
print('${item['name']}의 수량이 추가되었습니다.');
break;
}
}
if (!isExist) {
cartList.add({
'name': selectProduct['name'],
'price': selectProduct['price'],
'count': 1,
});
print('${selectProduct['name']}을 장바구니에 담았습니다.');
}
}
기본 구조 로직의 흐름 '번호로 항목을 고르는 법'과 '수량 수정 로직'구조를 짜는데 아직 이해도가 부족해서 도움을 받기로 했다.
계산식 에러:
${item['price'] * ['count']}에서 ['count']는 리스트 형태라 숫자가 아닙니다. item['count']라고 써야 합니다.
비교 대상 오류:
if (itemNumber == cartList)는 "숫자 하나"와 "장바구니 리스트 전체"를 비교하는 것이라 항상 거짓이 됩니다. "리스트의 몇 번째(인덱스)인가"를 확인해야 합니다.
수정 로직:
사용자가 '1'번 상품을 고르면, 컴퓨터 기준으로는 0번 인덱스(1 - 1)를 수정하는 방식이 가장 편합니다.
// 장바구니 보기 및 수량 변경 함수
void showCart(List<Map<String, dynamic>> cartList) {
// 장바구니 목록을 번호로 고르고 수량 수정 여부를 물어보기
if (cartList.isEmpty) {
print('장바구니에 담긴 상품이 없습니다.');
return;
}
// 현재 장바구니에 담긴 정보를 출력
for (var item in cartList) {
print(
'${item['name']} ${item['price']} ${item['count']} = ${item['price'] * ['count']}원',
);
}
// 수량변경 y/n 선택
// y일 경우 상품 번호와 새 수량을 받아서 업데이트한다. 수량을 0으로 입력 시 해당 항목을 삭제
stdout.write('수량을 변경하시겠습니까? (y/n)');
String input = stdin.readLineSync() ?? '';
if (input == 'y' && input == 'yes') {
// 상품 번호를 입력해서 선택한 뒤, 변경수량을 또 입력??
int itemNumber = readIntInput('수량을 변경할 상품 번호와 새 수량을 입력해주세요.');
if (itemNumber == cartList) {
print('${itemNumber['count']}+${cartList['count']}');
}
} else if (input == 'n' && input == 'no') {
print('결제 항목으로 넘어갑니다.');
}
}
장바구니에 담긴 정보를 보여주려면 리스트의 길이만큼 출력하는게 편하다.
수량 변경에 대해 의사를 먼저 물어본 뒤 y일 경우 n일 경우 분기처리
인덱스 값을 가져와서 변경할 정보를 선택하고, 삭제할지(0) 수정할지
장바구니 안에 있을 경우 - 리스트 안에서 새로운 값을 더해줘서 수정해주기
! 리스트에서 제거할 때는 removeAt()을 사용, 추가할 때는 add(), 수정할 때는 입력한 값을 리스트에 새로 담아주면 됨.
void showCart(List<Map<String, dynamic>> cartList) {
// 장바구니 목록을 번호로 고르고 수량 수정 여부를 물어보기
if (cartList.isEmpty) {
print('장바구니에 담긴 상품이 없습니다.');
return;
}
// 현재 장바구니에 담긴 정보를 출력
for (var i = 0; i < cartList.length; i++) {
var item = cartList[i];
// i + 1 사용자에게 1번부터 보여주기. 총 합이 얼마인지
int subTotal = item['price'] * item['count'];
print(
'${i + 1}번 ${item['name']} | ${item['price']}원 | ${item['count']}개 | 소계: ${subTotal}원',
);
}
// 수량변경 y/n 선택
// y일 경우 상품 번호와 새 수량을 받아서 업데이트한다. 수량을 0으로 입력 시 해당 항목을 삭제
stdout.write('수량을 변경하시겠습니까? (y/n)');
String input = stdin.readLineSync()?.toLowerCase() ?? '';
if (input == 'y') {
// 상품 번호를 입력해서 선택.
int itemNum = readIntInput('수량을 변경할 상품 번호를 입력해주세요.');
// 컴퓨터가 이해하는 인덱스 번호로 변환한 것
int index = itemNum - 1;
// 입력한 번호가 장바구니 범위 안에 있는지 확인하기
if (index >= 0 && index < cartList.length) {
int newCount = readIntInput('새로운 수량을 입력하세요.(0 입력시 삭제)');
if (newCount <= 0) {
// Todo 삭제 로직과 동일함
print('${cartList[index]['name']} 항목이 삭제되었습니다.');
cartList.removeAt(index);
} else {
// 수정 로직 동일함.
// newCount의 값을 리스트 안에 있는 목록 안 카운트의 값에 담아준다.
cartList[index]['count'] = newCount;
print('수량이 변경되었습니다.');
}
} else {
print('잘못된 번호입니다.');
}
} else if (input == 'n') {
print('수량변경이 취소되었습니다.');
return;
} else {
print('잘못 입력하셨습니다. (y/n)중 선택해주세요.');
return;
}
}
데이터를 어떻게 처리하고 최종적으로 마무리하는지의 흐름
통계 로직의 기초
// 주문 및 결제 함수
void processOrder(List<Map<String, dynamic>> cartList) {
// 결제 로직을 구현하세요. 할인조건 총 액이 30,000원 이상이면 10% 할인 적용
// 최종 결제 금액 출력하고 장바구니를 비우기
// 장바구니가 비어있을 경우
if (cartList.isEmpty) {
print('결제할 상품이 없습니다.');
return;
}
int total = 0;
// 반복문으로 모든 상품 가격*수량 total에 더하기
for (var i = 0; i < cartList.length; i++) {
// 반복되는 카트리스트의 값을 아이템에 담아주고, 토탈값을 계산해준다.
var item = cartList[i];
total += (item['price'] as int) * (item['count'] as int);
}
print('최종합계: $total원 입니다.');
// 할인적용
if (total >= 30000) {
total = (total * 0.9).toInt();
}
print('3만원 이상 구매시 10% 할인되어서 최종금액: $total원 입니다.');
print('결제가 완료되었습니다. 이용해주셔서 감사합니다.');
// 장바구니 비우기
cartList.clear();
return;
}
ToDo 앱에 대입해본다면?
누적 합산:
"전체 할 일 중 완료된 할 일은 몇 개인가?" 계산할 때 사용.
조건 처리:
"할 일을 다 완료했으면 '축하합니다' 메시지 띄우기" 할 때 사용.
초기화:
"완료된 항목만 전체 삭제하기" 기능을 만들 때 사용.