2차원 회문 탐색

이 글은 블라드스톤 필루(Wladston Filho)의 글 「Finding Magic Word Squares」를 허락을 구하여 번역한 것이다.

‘회문’에 관해서는 들어본 적이 있을 것이다. 회문은 그 속의 글자들을(대소문자 구별이나 문장 부호는 빼고) 앞에서부터 읽든, 뒤에서부터 읽든, 똑같은 문장으로 읽을 수 있다. 다음 회문들을 한번 앞뒤로 읽어 보시라.

Live not on evil.
Race fast, safe car!
Never odd or even.

사토르 마방진

하지만 흔해 빠진 그냥 회문을 소개하려고 이 글을 쓰는 것은 아니다. 오늘 소개하려는 것은 두둥~ ‘2차원 회문’이라는 물건이다. 아래 그림은 ‘사토르 마방진’이라는 것으로, 고대 로마 시대에 만들어진 유명한 2차원 회문이다.

고대 로마의 사토르 마방진

그림: 고대 로마의 사토르 마방진

이 사토르 마방진은 라틴어 표현 sator arepo tenet opera rotas1을 써 둔 것이다. 이 마방진의 대단한 점은 위에서 아래, 아래에서 위, 좌에서 우, 우에서 좌 어떤 방향으로든 읽을 수가 있다는 것이다.

사토르 마방진 읽기

그림: 사토르 마방진 읽기

그야말로 궁극의 회문이다. 고대 로마인들은 이 마방진에 매료되어 이것을 집집마다, 신전마다, 온갖 비석에, 그리고 심지어 물그릇에까지 새겨두곤 했다. 완벽한 대칭 속에 잡귀를 쫓고 행운을 불러오는 힘이 있다고 믿었던 것이다.

로마인들이 이런 2차원 라틴어 회문을 찾은 것은 굉장한 우연이었거나, 아니면 엄청난 수고를 들여 이뤄낸 일이었을 것이다. 2차원 회문의 존재를 알게 된 후, 나는 혹시 영어 단어 중에는 5x5 크기의 2차원 회문이 없을지 계속 고민해왔다.

파이썬으로 찾아 보자

컴퓨터를 이용하면 쉽게 찾을 수 있지 않을까? 그래서 간단한 파이썬 프로그램을 짜 봤다. 처리과정을 소개해본다.

먼저, 다섯 글자로 된 모든 영어 단어가 필요하다. 인터넷에 공개된 단어 목록을 활용하면 된다.

import urllib.request
src = "http://raw.githubusercontent.com"
src += "/codenrg/sator-square/master/words-en.txt"
fp = urllib.request.urlopen(src)
words = [str(line, 'utf-8').rstrip() for line in fp]

이것을 실행하면 words에 모든 영어 단어의 리스트가 저장된다. 2차원 회문을 만들려면 다섯 글자로 된 단어만 필요하므로 그것들만 선별하자.

words = filter(lambda x: len(x) == 5, words)

단어를 2차원 회문에서 쓸 수 있으려면 그것을 뒤집은 단어도 2차원 회문 속에 있어야 한다. 예를 들어, ‘stop’이라는 단어는 후보가 될 수 있다. 그것을 뒤집은 ‘pots’도 올바른 영어 단어이니까. 하지만 ‘coder’는 그렇지가 못하다. ‘redoc’은 올바른 영어 단어가 아니기 때문이다.

뒤집었을 때 탈락하는 단어들을 걸러내자. 먼저 단어들을 모두 집합에 넣고, 각 단어마다 그것을 뒤집은 단어가 집합에 들어있는지 검사한다.

words = set(words)
rev = lambda w: "".join(reversed(list(w)))
words = [w for w in words if rev(w) in words]

이제 후보가 될 수 있는 단어들은 다 추렸다. 이것들을 조합해 2차원 회문을 찾아보는 일만 남았다.

squares = []
for w1 in words:
    w2_match = lambda x: x[0] == w1[1] and x[-1] == w1[-2]
    for w2 in filter(w2_match, words):
        w3_match = lambda x: x[0] == w1[2] and x[1] == w2[2]
        for w3 in filter(w3_match, words):
            if w3 == rev(w3):
                square = [w1, w2, w3, rev(w2), rev(w1)]
                squares.append(square)

for x in squares:
    print("%s\n%s\n%s\n%s\n%s\n\n" % tuple(x))

이렇게 뽑아낸 단어들을 이용해 2차원 회문을 세 개 발견할 수 있었다. 그 중 가장 그럴듯한 것을 소개한다.

P A R T S
A P A R T
R A D A R
T R A P A
S T R A P

다른 언어에서도 2차원 회문을 찾을 수 있는지 확인해봐주면 좋곘다. 코드에서 개선할 점도 환영한다. 깃허브(https://github.com/codenrg/sator-square)에서 풀 요청을 보내주면 된다.

한국어 2차원 회문

여기서부터는 내가 추가한 내용이다. -박연오

필로의 프로그램을 이용해 한국어로 2차원 회문을 만들 수 있는지 찾아 보자.

먼저, 한국어 단어 목록이 필요하다. 나는 KoNLPy (파이썬 한국어 NLP)에 포함된 ‘Hannanum 시스템 사전’을 사용했다. 이것은 다음과 같이 하여 다운로드할 수 있다.

import urllib.request
fp = urllib.request.urlopen('https://raw.githubusercontent.com/konlpy/konlpy/master/konlpy/java/data/kE/dic_system.txt')
words = [str(line, 'utf-8').rstrip() for line in fp]

다운로드한 사전을 확인해 보면 한글로 시작하지 않는 단어들이 있다. 이것들을 걸러내자.

def is_hangul(ch):
    return '가' <= ch <= '힣'

def starts_with_hangul(word):
    return is_hangul(word[0])

words = [word for word in words if starts_with_hangul(word)]

또, 사전에는 가치체계 ncn처럼 품사가 태그되어 있다. 품사를 제거하여 가치체계와 같이 단어만 남기자.

def remove_tag(word):
    return word.split()[0]

words = [remove_tag(word) for word in words]

이제 한국어 단어 목록이 준비됐다. 나머지는 필로가 작성한 코드에 단어 목록을 적용해 2차원 회문을 찾아보는 것뿐이다.

이렇게 해서 만든 한국어 2차원 회문 탐색 프로그램은 깃허브에 올려두었다. https://github.com/bakyeono/sator-square/blob/master/find-sator-squares-in-korean.py

하지만 아쉽게도 별 재미는 보지 못했다. 적어도 내가 사용한 사전에서는 다섯 글자로 된 2차원 회문은발견할 수 없었기 때문이다. 글자 제한을 조금 줄이면 더 나오지 않을까? 그래서 네 글자로 찾아 보니 “하하하하” 처럼 모두 같은 글자로 이루어진 단어들밖에 안 나왔다. 세 글자로 된 2차원 회문을 찾아보니 개수가 너무 많이 나오는데, 대부분 사람 이름이 섞여 있어 마음에 들지 않는다. 아래는 그 가운데 그나마 쓸만한 것을 몇 개 추린 것이다.

대구대
구로구
대구대

경성대
성장성
대성경

주사파
사회사
파사주

대전대
전격전
대전대

식용유
용불용
유용식

알파벳보다 글자수가 훨-씬- 많은 한글의 특성상 2차원 회문이 되기가 좀 더 어려운 것이 아닌가 싶다. 사전을 다른 것(인명이 포함되지 않고, 한국어 단어의 활용을 어느 정도 반영한 것)으로 하면 더 찾을 수 있을지도 모른다.

  1. 적당히 번역하면 “농부 아레포가 쟁기(바퀴)로 작업한다”라는 뜻이라고 한다. (https://en.wikipedia.org/wiki/Sator_Square)