중복이라는 문제
모델은 철자가 들쭉날쭉해. 한 달 동안 접시가 쌓이면 피파는 같은 요리를 "Gambas al ajillo"라고도, "gambas al ajillo!"라고도, "Gambas-al-Ajillo"라고도 쓸 수 있어. 철자마다 새 클래스가 생기면 한 요리의 접시들이 여러 행으로 흩어져. 배지가 문제인 건 아니야. 집에서 만든 접시 하나면 클래스는 바로 "직접 요리"로 읽히거든. 문제는 접시가 어디 떨어지느냐야. 행마다 페이지엔 자기 몫의 접시만 보이고, 피파가 전에 제안한 형제는 아빠가 그 요리를 먹은 뒤에도 피파의 추정으로 남을 수 있어. 아빠 접시가 쌍둥이 행으로 갔으니까. 트리는 쪼개진 채로 거짓말을 하게 돼.
Sizzle엔 방어선이 두 겹 있고, 순서대로 돌아.
첫째, 모델한테 id를 쥐여 줘
읽기 입력엔 매번 store.catalog()가 만든 아빠의 트리가 데이터로 들어가. 원리마다 id, 이름, 요약, 상태가 있고, 요리나 변형마다 id, 종류, 이름, gloss, 요리권, 상태가 있어. 루브릭은 맞는 게 있으면 ref나 dish_ref에 그 id를 넣고 원래 이름을 지키라고 해.
엔진은 그 id를 그냥 믿지 않아. _resolve_principle은 ref가 정말 있는 클래스고 종류가 principle일 때만 받아. _resolve_dish는 dish_ref가 요리나 변형일 때만 받고. 피파가 잘못 기억한 id나, 원리 자리에 들어간 요리 id는 무시되고 이름 맞추기로 넘어가.
둘째, 이름을 정규화해
쓸 만한 id가 없으면 엔진은 norm으로 이름을 비교해. backend/store.py에 있는 6줄짜리 함수야. 단계는 이래.
- NFKD가 악센트 붙은 글자를 기본 글자랑 결합 부호로 쪼개.
- 결합 부호를 전부 버려. 악센트가 여기서 빠져.
- NFKC가 남은 걸 다시 붙이고, 전각 글자 같은 호환 문자를 보통 글자로 바꿔.
casefold()는lower()보다 세게 소문자로 만들어. 그래서 "Straße"랑 "strasse"가 같아져.- 글자나 숫자가 아닌 문자가 이어진 곳은 밑줄까지 포함해서 공백 하나가 되고, 양 끝은 잘려.
origin/main 코드로 이름 몇 개를 직접 돌려 봤어. 전부 같은 키 하나로 모이는 것들이야.
| 쓴 이름 | 맞추기 키 |
|---|---|
Gambas-al-Ajillo | gambas al ajillo |
gambas_al_ajillo | gambas al ajillo |
Crème brûlée | creme brulee |
paella (valenciana) | paella valenciana |
KIMCHI (전각) | kimchi |
김치-찌개 | 김치 찌개 |
따로 남는 것도 있어. 김치찌개랑 김치 찌개는 안 맞아. norm은 구분자를 공백 하나로 바꾸기만 하지, 지우진 않거든. Spaghetti aglio e olio랑 Aglio e olio도 달라. 비슷한 말 찾기나 부분 일치는 아예 없어. 문장 부호만으로 된 이름은 빈 키가 되고, find_class는 빈 키엔 아무것도 안 돌려줘. 이름 없는 걸 몽땅 같다고 묶지 않는 거지.
한글은 멀쩡히 통과해. NFKD가 음절을 자모로 쪼개긴 하는데, 자모는 결합 부호가 아니라서 버려지는 게 없어. 그리고 NFKC가 음절을 다시 붙여 놔.
이름이 둘이면 기회도 둘
요리엔 이름이 둘 있고, 찾을 때 둘 다 써. find_dish는 먼저 읽기의 원어 이름으로 찾고, 안 되면 한국어 이름으로 찾아. find_class는 후보마다 그 키를 모든 요리와 변형의 name 칸, gloss 칸 양쪽과 비교해. 그래서 읽기가 원어 이름 자리에 한국어 이름을 써도, gloss가 그거랑 맞는 클래스를 찾아. 변형도 같이 뒤지니까, 읽기가 아빠가 붙인 이름 그대로 부르면 아빠 버전의 접시는 그 변형에 바로 이어져. 원리는 find_principle로 이름만 맞춰.
이 레슨의 엔진 테스트는 짧아. 같은 요리 두 접시를 읽히고 나면, 그 요리 페이지엔 클래스 하나에 딱 그 두 접시가 걸려 있어.
키는 셋, 하는 일도 셋
Sizzle엔 이름 키가 셋 있고, 셋이 모든 걸 똑같이 보진 않아.
- 엔진의
norm은 트리를 맞추는 데 써. 악센트랑 문장 부호까지 정리해. integrate안의_key는 형제가 댄 원리 이름을 같은 읽기에서 앞서 나온 원리에 맞추는 데 써. 띄어쓰기랑 대소문자만 통일해.- 웹의
nameKey랑 폰의TreeIndex.key는 읽기 안의 이름을 클래스 페이지 링크로 바꾸는 데 써. NFKC, 소문자, 공백 정리까지만 하고 악센트는 남겨.
그래서 눈에 보이는 이음매가 하나 생겨. 트리에 "Crème brûlée"가 있고 읽기가 "creme brulee"라고 쓰면, 엔진은 접시를 맞는 클래스에 이어. 웹 접시 화면의 In the tree 목록, 곧 트리에 있는 요리 목록에도 떠. 그런데 읽기 안의 요리 제목은 한국어 이름이 우연히 맞지 않는 한 그냥 글자로 남아. 기록은 맞아. 편의용 링크 하나가 빠진 것뿐이야.