← 모두의 툴

사람 눈엔 회문인데 알고리즘은 아니라는 경우 — 코드포인트 vs 그래프임 함정

가이드 · 2026.08.21 최종 확인

"racecar"처럼 알파벳으로만 된 문자열의 회문 판정은 어떤 도구를 써도 결과가 똑같습니다. 문제는 이모지, 특히 국기나 피부색이 합성된 이모지가 섞였을 때입니다. 사람 눈에는 분명 좌우 대칭인데 도구는 "회문 아님"이라고 답하거나, 반대로 눈으로 봤을 땐 비대칭인데 "회문"이라고 답하는 경우가 생깁니다. 원인은 문자열을 "몇 글자"로 셀지에 대한 정의가 두 가지이기 때문입니다.

1. 유니코드 문자열의 세 가지 단위

유니코드 문자열을 쪼개는 방법은 크게 세 층위로 나뉩니다. 가장 얕은 단위는 UTF-16 코드유닛(자바스크립트의 옛 .length·[i] 인덱싱 기준)이고, 그 위가 코드포인트(자바스크립트의 [...str] 스프레드나 for...of가 순회하는 단위), 가장 사람 감각에 가까운 것이 그래프임 클러스터(Intl.Segmenter가 만들어주는, 화면에 "한 글자"로 보이는 단위)입니다. 알파벳·한글 완성형처럼 흔한 문자는 세 단위가 사실상 일치하지만, 국기 이모지나 ZWJ(Zero Width Joiner)로 결합된 이모지는 코드포인트 여러 개가 그래프임 하나를 이룹니다.

2. 국기 이모지가 실제로 어떻게 구성되는가

🇰🇷(대한민국 국기)는 사실 하나의 문자가 아니라 지역표시문자(Regional Indicator Symbol) 코드포인트 두 개, U+1F1F0(🇰)과 U+1F1F7(🇷)의 조합입니다. 사람 눈에는 국기 그림 하나지만 코드포인트 단위로 세면 2개입니다. 👨‍👩‍👧(가족 이모지)는 더 극단적으로, 사람 성인·여성 성인·여자아이 이모지 세 개를 ZWJ(U+200D) 두 개로 이어붙인 것이라 코드포인트로는 5개 이상입니다. [...str]은 이 결합 규칙을 모르기 때문에 각 코드포인트를 독립된 "글자"로 취급하고 순서를 뒤집습니다.

3. 코드포인트 단위 반전이 만드는 오판

회문 검사 로직의 핵심은 "정규화한 문자열을 뒤집어 원본과 비교"하는 것입니다. 회문(팰린드롬) 검사기[...n].reverse().join('')로 반전하는데, 이는 코드포인트 단위 반전입니다. 국기 하나가 코드포인트 2개(A, B 순서)로 구성돼 있다면, 이 반전 로직은 국기 전체를 뒤로 보내는 게 아니라 A와 B의 순서를 각각 따로 뒤집어 버립니다. 서로 다른 두 국기(예: 🇰🇷🇯🇵)가 이어진 문자열을 반전하면 지역표시문자 코드포인트 4개가 뒤섞여 실제로 존재하지 않는 국기 조합이 만들어지거나, 원래 대칭이던 국기 배열이 코드포인트 레벨에서는 비대칭으로 판정되는 경우가 생깁니다.

예시: "🇰🇷🇰🇷" (같은 국기 두 번 반복, 그래프임 2개·코드포인트 4개)를 코드포인트 단위로 반전하면 코드포인트 순서만 뒤집혀 결과 문자열은 원본과 동일하게 유지됩니다(대칭 배열이라 우연히 문제가 드러나지 않음). 하지만 서로 다른 국기 "🇰🇷🇯🇵"을 반전하면 코드포인트 4개가 [🇯, 🇵, 🇰, 🇷] 순서로 재배열되어, 렌더링 엔진이 이를 인접한 지역표시문자 쌍으로 다시 묶으면서 원래 없던 조합("🇯🇰" + "🇵🇷" 같은 임의 조합)으로 보일 수 있습니다. 그래프임 단위(Intl.Segmenter)로 반전했다면 [🇯🇵, 🇰🇷] 순서로만 뒤집혀 이런 재조합 문제가 생기지 않습니다.

4. 이 도구는 지금 어느 방식을 쓰는가

실제 palindrome-checker.html 코드를 확인한 결과, 코드포인트 단위([...str])를 사용하고 있으며 Intl.Segmenter는 쓰지 않습니다. 도구 자체의 FAQ와 SEO 설명에도 "여러 코드 포인트로 이루어진 결합 이모지는 예상과 다른 결과가 나올 수 있습니다"라고 명시돼 있어, 이 한계를 도구 제작 단계에서부터 인지하고 있는 구조입니다. 일반 텍스트(한글·영문·숫자·구두점)만 다룰 때는 코드포인트 단위와 그래프임 단위 결과가 완전히 같으므로 실사용에서는 대부분 문제가 되지 않습니다. 다만 국기·ZWJ 이모지가 섞인 문자열의 정확한 구성을 확인하고 싶다면 유니코드 인스펙터로 코드포인트를 하나씩 뜯어보는 것이 더 정확합니다.

5. 실무에서 마주치는 상황

이모지가 섞인 텍스트의 글자 수를 셀 때도 같은 함정이 발생합니다. 이모지 카운터텍스트 통계 도구 역시 코드포인트 단위로 세는지 그래프임 단위로 세는지에 따라 "이 문장은 이모지가 3개다" vs "5개다"처럼 결과가 갈릴 수 있습니다. SNS 글자 수 제한처럼 정확한 카운트가 중요한 작업에서는 도구가 어떤 단위를 쓰는지 먼저 확인하는 습관이 필요합니다.

자주 묻는 질문

Q. 한글이나 영문만 검사할 때도 이 문제가 생기나요?

아니요. 한글 완성형·영문·숫자·일반 구두점은 코드포인트 하나가 그래프임 하나와 정확히 일치하므로 코드포인트 단위 반전과 그래프임 단위 반전의 결과가 완전히 같습니다. 문제는 국기, ZWJ로 결합된 이모지, 피부색 변형자가 붙은 이모지처럼 여러 코드포인트가 한 글자로 렌더링되는 경우에만 발생합니다.

Q. Intl.Segmenter를 쓰면 이 문제가 완전히 사라지나요?

대부분의 경우 해결됩니다. Intl.Segmenter의 grapheme 단위 분할은 유니코드 텍스트 세그멘테이션 표준(UAX #29)을 따르므로 ZWJ 결합·지역표시문자 쌍을 사람이 보는 것과 같은 단위로 묶어줍니다. 다만 이는 비교적 최신 브라우저 API라 구형 브라우저에서는 지원되지 않을 수 있습니다.

Q. 이 도구를 실제 업무(텍스트 처리 자동화 등)에 써도 되나요?

일반 텍스트(한글·영문·숫자·기호)를 다루는 대부분의 용도에는 문제없이 정확합니다. 다만 이모지가 많이 섞인 사용자 생성 콘텐츠(SNS 게시글 등)를 다루는 자동화 파이프라인이라면, 위에서 설명한 코드포인트 단위 한계를 감안해 별도 검증 로직을 추가하는 것을 권장합니다.

Q. 대소문자·공백·구두점 무시 옵션은 이 문제와 관련 있나요?

아니요. 별개의 기능입니다. 대소문자·공백·구두점 처리는 정규화 단계에서 toLowerCase()와 유니코드 속성 정규식으로 이루어지고, 코드포인트 vs 그래프임 문제는 그 다음 단계인 반전(reverse()) 방식에서 발생합니다. 두 단계는 서로 독립적으로 동작합니다.