도구스개발

버로우즈–휠러 변환(BWT) 계산기

문자열을 한 칸씩 돌린 것을 모두 사전순으로 늘어놓고 마지막 열을 뽑아내는 과정을 표로 보여 줍니다. 길이는 그대로인데 같은 글자가 뭉치는 이유, 마지막 열만으로 원문이 복원되는 이유, Move-to-Front를 붙이면 무엇이 달라지는지까지 확인할 수 있습니다.

48글자까지. 회전을 전부 늘어놓아 보이는 것이 목적이라 짧게 넣는 편이 읽기 좋습니다.

변환 결과 (마지막 열)

BNN^AA|A

원문이 놓인 행 번호는 6번입니다. 되돌리려면 이 두 가지가 함께 있어야 합니다. 길이는 8글자 그대로이고, 줄어든 것은 「덩어리 수」뿐입니다.

원문 덩어리 수8개
변환 뒤 덩어리 수6개
덩어리가 줄어든 정도25% 감소
글자별 엔트로피 (원문 / 변환 뒤)2.156 / 2.156 비트
되돌리면^BANANA| (원문과 같음)
BWT는 한 바이트도 줄이지 않습니다. 길이도 글자 개수도 그대로라 글자별 엔트로피가 위 표처럼 정확히 같습니다. 「BWT로 압축했다」는 말은 틀렸고 「BWT로 압축이 잘 되게 만들었다」가 맞습니다. 바뀐 것은 덩어리 수뿐이고, 그 덕에 뒤에 붙는 MTF와 런길이 부호화가 잘 먹습니다.

회전을 사전순으로 늘어놓은 표

회전마지막
0ANANA|^BB
1ANA|^BANN
2A|^BANANN
3BANANA|^^
4NANA|^BAA
5NA|^BANAA
6^BANANA||
7|^BANANAA

파랗게 칠한 줄이 원문입니다. 맨 오른쪽 열을 위에서부터 읽은 것이 변환 결과이고, 맨 왼쪽 글자를 위에서부터 읽으면 AAABNN^| 마지막 열을 그냥 정렬한 것입니다. 모든 행이 같은 글자를 돌린 것이므로 열마다 글자 구성이 같기 때문입니다.

왜 같은 글자가 뭉치는가. 정렬은 각 행의 «뒤에 오는 글자들»을 기준으로 합니다. 그런데 어떤 행의 마지막 글자는 그 뒤에 오는 글자들의 바로 앞 글자입니다. 영어에서 "he" 앞에는 t가 자주 오므로 "he…"로 시작하는 행이 한 덩어리로 모이면 그 행들의 마지막 열에 t가 줄줄이 늘어섭니다. 문맥이 비슷한 자리의 앞 글자가 비슷하다는 성질을 정렬로 끌어모으는 셈입니다. 위 예시 중 「영문 문장」을 눌러 보면 실제로 그렇게 됩니다.

Move-to-Front를 붙이면

원문에 바로 MTF0이 0%, 엔트로피 1.906비트
BWT를 거친 뒤 MTF0이 25%, 엔트로피 2.25비트

1 2 0 3 3 0 4 1

MTF는 방금 쓴 글자를 목록 맨 앞으로 옮깁니다. 그래서 같은 글자가 이어지면 0이 줄줄이 나오고, 여기에 런길이 부호화를 붙이면 크게 줄어듭니다. bzip2가 쓰는 순서가 정확히 이것입니다 — BWT → MTF → RLE → 허프만. BWT를 거친 쪽의 0 비율이 더 높은 것이 이 순서를 쓰는 이유입니다.

마지막 열만 남겼는데 어떻게 되돌아오는가. 첫 열은 마지막 열을 정렬한 것이고, 같은 글자끼리는 첫 열과 마지막 열에서 순서가 같습니다. 마지막 열의 k번째 'A'와 첫 열의 k번째 'A'가 원문의 같은 자리를 가리킨다는 뜻입니다. 두 열의 'A'들이 모두 「그 뒤에 오는 문자열의 사전순」으로 줄 서 있어서 그렇습니다. 이 대응을 따라가면 원문을 뒤에서부터 한 글자씩 되짚을 수 있고, 어느 행이 원문이었는지만 함께 알면 됩니다.
그래서 변환 결과에 행 번호가 딸립니다. 끝을 알리는 특수문자를 붙이는 방식(위 예시의 「|」)을 쓰면 행 번호 없이도 되지만, 그러면 원문에 그 문자가 없어야 한다는 조건이 붙습니다. 실제 구현은 대개 행 번호 쪽을 씁니다 — 아무 데이터에나 쓸 수 있기 때문입니다.

사용 방법

  1. 1문자열을 넣으면 모든 회전이 사전순으로 늘어섭니다.
  2. 2맨 오른쪽 열을 위에서부터 읽은 것이 변환 결과입니다.
  3. 3파랗게 칠한 줄이 원문이며, 그 행 번호가 역변환에 필요합니다.
  4. 4덩어리 수가 얼마나 줄었는지, 글자별 엔트로피는 그대로인지 표에서 봅니다.
  5. 5Move-to-Front를 붙였을 때 0의 비율이 얼마나 오르는지 견줍니다.

자주 묻는 질문

문자열을 한 칸씩 돌린 것을 모두 만들어 사전순으로 늘어놓고 마지막 열만 뽑아내는 재배열입니다. 길이도 글자 개수도 그대로이지만 같은 글자가 뭉쳐 나오게 되어, 뒤에 붙는 압축이 훨씬 잘 먹습니다. bzip2가 BWT → MTF → 런길이 부호화 → 허프만 순서로 이 변환을 앞단에 씁니다.

되지 않습니다. BWT 자체는 한 바이트도 줄이지 않으며 글자별 엔트로피도 정확히 그대로입니다. 「BWT로 압축했다」는 말은 틀렸고 「BWT로 압축이 잘 되게 만들었다」가 맞습니다. 바뀌는 것은 같은 글자가 이어지는 덩어리의 수뿐이고, 그 덕에 뒤에 붙는 단계가 잘 먹는 것입니다.

정렬 기준이 각 행의 「뒤에 오는 글자들」인데, 그 행의 마지막 글자는 그 글자들의 바로 앞 글자이기 때문입니다. 영어에서 "he" 앞에는 t가 자주 오므로 "he…"로 시작하는 행이 한 덩어리로 모이면 그 행들의 마지막 열에 t가 줄줄이 늘어섭니다. 문맥이 비슷한 자리의 앞 글자가 비슷하다는 성질을 정렬로 끌어모으는 셈입니다.

첫 열이 마지막 열을 정렬한 것이고, 같은 글자끼리는 두 열에서 순서가 같기 때문입니다. 마지막 열의 k번째 A와 첫 열의 k번째 A가 원문의 같은 자리를 가리킵니다. 두 열의 A들이 모두 「그 뒤에 오는 문자열의 사전순」으로 줄 서 있어서 그렇습니다. 이 대응(LF 매핑)을 따라가면 원문을 뒤에서부터 한 글자씩 되짚을 수 있습니다.

어느 행이 원문이었는지 알아야 역변환을 시작할 수 있기 때문입니다. 끝을 알리는 특수문자를 문자열 끝에 붙이는 방식을 쓰면 행 번호 없이도 되지만, 그러면 원문에 그 문자가 없어야 한다는 조건이 붙습니다. 실제 구현은 아무 데이터에나 쓸 수 있는 행 번호 쪽을 대개 씁니다.

방금 쓴 글자를 목록 맨 앞으로 옮기고, 각 글자를 「그때 목록에서의 자리」로 적는 변환입니다. 같은 글자가 이어지면 0이 줄줄이 나오므로 뒤에 런길이 부호화를 붙이면 크게 줄어듭니다. BWT를 거친 뒤에 붙이면 0의 비율이 눈에 띄게 오르는데, bzip2가 두 변환을 이 순서로 붙이는 이유가 그것입니다.

전송되지 않습니다. 모든 계산은 브라우저 안에서 이뤄지고, 입력값은 이 기기에만 남습니다.

알아두면 좋은 점

  • 널리 실린 예제 "^BANANA|" → "BNN^AA|A"와 대조해 검증했습니다. 정렬된 회전 여덟 줄이 교과서 표와 글자 하나까지 같은 것, 원문이 6번 줄에 놓이는 것까지 고정했습니다. "banana" → "nnbaaa"도 함께 맞췄습니다.
  • 왕복 검산은 무작위 문자열 600벌(알파벳 2~8종, 한글·공백·특수문자 포함, 길이 1~24)을 변환한 뒤 되돌려 원문과 대조해 했습니다. 회전이 모두 같아지는 주기 입력(abab, abcabcabc)과 한 글자짜리도 따로 넣었습니다.
  • 첫 열이 마지막 열을 정렬한 것과 같다는 것, LF 매핑이 0부터 n−1까지의 순열이며 첫 열과 마지막 열의 같은 글자를 순서대로 잇는다는 것을 무작위 입력으로 확인했습니다.
  • 변환 전후로 길이와 글자 개수가 그대로이고 글자별 엔트로피가 정확히 같다는 것을 테스트로 고정했습니다. BWT가 압축이 아니라는 것을 가장 또렷이 보여 주는 값입니다.
  • 되풀이가 있는 글은 덩어리 수가 5분의 1 아래로 떨어지지만, 무작위 문자열은 평균 95% 이상 그대로라는 것도 재어 두었습니다. BWT는 되풀이를 찾아내는 변환이지 아무 데나 듣는 것이 아닙니다.
  • 문자열 비교는 UTF-16 코드단위가 아니라 코드포인트 차례로 못 박았습니다. 자바스크립트의 기본 비교를 그대로 쓰면 서로게이트 쌍이 섞였을 때 변환과 역변환의 기준이 어긋납니다.
  • 48글자까지 다룹니다. 회전을 전부 늘어놓아 보이는 것이 목적이라 그보다 길면 표가 읽히지 않습니다.

함께 보면 좋은 도구

마지막 검증: 2026년 9월 1일 · 결과는 참고용 추정치입니다.