알고리즘 미니 프로젝트
정렬·탐색 감 잡는 미니 프로젝트: “전화번호부 검색기” 만들기
이번 편은 진짜로 손에 땀 나는 파트!
4-4에서 정렬과 탐색을 맛봤다면, 이번 4-5. 알고리즘 미니 프로젝트에서는 내가 직접 문제를 쪼개서 설계하고, 구현하고, 테스트까지 해보는 시간을 가져볼게요. 솔직히 말하면… 저도 예전에 이거 만들다가 “어? 내가 원하는 대로 안 찾아오네?” 하면서 한참 삽질했거든요. 그 실패담도 같이 풀어볼게요. 😄
오늘 만들 것: 전화번호부 검색기
기본 아이디어는 이거예요.
- 사람 이름과 전화번호가 들어있는 “전화번호부”가 있다
- 사용자가 이름을 입력하면
- 정렬된 구조를 기준으로 빠르게 찾아준다
- 못 찾으면 “없어요”라고 친절하게 말한다
여기서 중요한 건 “무작정 다 훑는 방식(선형 탐색)” 말고,
우리가 이전에 배운 이진 탐색을 쓰는 느낌이에요.
프로젝트 목표(딱 3개만)
- 전화번호부 데이터를 준비한다
- 이름 기준으로 정렬한다
- 정렬된 데이터에 대해 이진 탐색으로 검색한다
그리고 보너스로:
- 입력이 없거나 공백이면 처리
- 대소문자 차이(“kim” vs “Kim”)도 대충 대응
이 정도만 해줘도 꽤 “프로그램 같다” 느낌 나요.
전체 흐름(입력 → 처리 → 출력)
프로그램 흐름은 이렇게 생각하면 편해요.
- (시작) 데이터 준비
- (전처리) 이름으로 정렬
- (반복) 사용자 입력 받기
- (탐색) 이진 탐색으로 찾기
- (출력) 찾으면 전화번호 출력, 아니면 “없음” 출력
- (종료) 사용자가 끝내기 입력하면 종료
단계 1) 데이터 만들기
일단 예시 데이터를 만들어둘게요.
실제 전화번호부처럼 “이름-번호” 쌍이 여러 개 있는 형태요.
contacts = [
("Kim", "010-1234-5678"),
("Lee", "010-2222-3333"),
("Park", "010-7777-8888"),
("Choi", "010-9999-0000"),
("Jung", "010-4444-5555"),
]
음… 여기서 한 가지!
이진 탐색은 “정렬이 되어 있어야” 정상 동작해요.
그래서 다음 단계가 핵심!
단계 2) 정렬하기 (이름 기준)
이름으로 정렬합니다. (대소문자 때문에 검색이 꼬이면, 미리 통일해두는 게 좋아요)
contacts_sorted = sorted(contacts, key=lambda x: x[0].lower())
나름(?) 팁 하나:
탐색할 때도 입력을 같은 방식으로 lower()로 맞춰줘야 매칭이 깔끔해져요.
저는 예전에 “왜 없다고 하지?” 하고 한참 봤는데, 입력은 원래 대소문자였고 정렬 기준은 lower였더라구요… 아! 그때 진짜 어이가 없었어요. 😅
단계 3) 이진 탐색 구현하기
이제 “이진 탐색 함수”를 만들어요.
목표: 입력한 이름을 기준으로 전화번호부에서 찾아오기.
def binary_search_contacts(contacts, target_name):
target = target_name.lower()
left, right = 0, len(contacts) - 1
while left <= right:
mid = (left + right) // 2
mid_name = contacts[mid][0].lower()
if mid_name == target:
return contacts[mid][1] # 전화번호 반환
elif mid_name < target:
left = mid + 1
else:
right = mid - 1
return None
여기서 내가 중요하다고 느낀 포인트는 딱 하나예요.
contacts는 반드시 정렬된 상태- 비교는
lower()로 통일해서 대소문자 이슈를 제거
솔직히 말하면, 이 부분에서 조건 방향 하나만 뒤집혀도 “항상 못 찾는” 일이 생겨요.
저도 한번… “<”랑 “>”를 바꿔놓고 몇 분 동안 멍 때렸습니다. 그럴 때 진짜 웃기죠. 나만 그런 거 아니지? 어? 😄
단계 4) 사용자 입력 받고 실행하기
이제 “검색기”처럼 동작하게 만들 차례!
def run_search_app():
contacts_sorted = sorted(contacts, key=lambda x: x[0].lower())
print("전화번호부 검색기 시작! (이름을 입력하세요)")
print("종료하려면 'exit'를 입력하면 돼요.")
print("-" * 40)
while True:
name = input("찾을 이름: ").strip()
if name.lower() == "exit":
print("종료합니다. 안녕!")
break
if not name:
print("이름이 비어 있어요. 다시 입력해볼까요? 아, 그냥 공백이면 안 돼요! 😅")
continue
result = binary_search_contacts(contacts_sorted, name)
if result:
print(f"{name}의 전화번호는 {result} 입니다.")
else:
print(f"아! {name}은(는) 전화번호부에 없어요.")
print("-" * 40)
그리고 실행!
run_search_app()
여기까지 구현하면 뭐가 “배운 걸로” 느껴질까?
사실 이 프로젝트의 재미는, 이거예요:
- 정렬(sort)을 해두면
- 탐색(search)이 빠르고 깔끔해진다
- “코드 흐름”이 자연스럽게 연결된다
(데이터 준비 → 정렬 → 탐색 → 출력)
솔직히 말하면, 그냥 “정렬 해보고 끝” 이런 거보다
사용자가 입력해서 결과가 나오게 만들면, 알고리즘이 눈에 보이거든요.
실패담 2개(진짜 많이 겪는 것들)
- “정렬했는데도 못 찾는 문제”
- 거의 항상 원인은 정렬 기준과 탐색 비교 기준이 다름
- 예: 정렬은 lower()인데 탐색은 lower() 안 함
- “항상 왼쪽으로만 가는 것 같아요”
- 이진 탐색은
left/right업데이트가 살짝만 틀려도 망가져요 left = mid + 1,right = mid - 1이게 핵심이에요
나름 유머로 말하면:
이진 탐색은 “중간값과 사이좋게 지내는 기술”인데, 중간값을 잘못 취급하면 바로 삐집니다. 😆
다음 글 예고: 입력과 출력 다루기
자, 여기까지 오면 프로그램 뼈대는 충분히 생겼어요.
그런데 방금 코드는 “입력과 출력”을 그냥 사용했죠.
다음 5장에서는 그걸 더 제대로 정리해볼 거예요.
- 콘솔에서 입력을 어떤 방식으로 받을지
- 출력은 어떻게 다듬을지
- 사용자와 자연스럽게 대화하는 느낌을 어떻게 만들지
다음 글에서 “5-1. 콘솔 입력과 출력 다루기”로 이어집니다.
음… 여기서부터 진짜 “사람이 쓰는 프로그램” 느낌이 확 살아납니다. 준비됐죠? 😄




