같은 작업을 여러 프로그래밍 언어로 짜보기

by SL

간단한 문제도 지금 어떤 프로그래밍 언어로 작업하고 있는지에 따라서 조금씩 다르게 접근하고 생각하게 되는 것 같다. 예를 들어, 인용 관계를 뒤집는 문제를 생각해보자. a ~ e의 다섯 논문이 아래와 같이 aa, bb, cc를 인용하고 있을 때, aa, bb, cc에 대해서 자신을 인용한 논문을 찾으려면 어떻게 할까?

a -> aa, bb, cc
b -> bb
c -> aa, bb
d -> bb, cc
e -> cc

우선 모국어격인 파이썬.

src_dst_list = {'a': ['aa', 'bb', 'cc'], 'b': ['bb'], 'c': ['aa', 'bb'], 'd': ['bb', 'cc'], 'e': ['cc']}

dst_src = {}
for src, dst_list in src_dst_list.items():
for dst in dst_list:
if dst not in dst_src:
dst_src[dst] = {}
dst_src[dst][src] = 1

사전 타입의 변수를 만들고, src와 dst_list에 대해 루프를 돌면서 명시적으로 새로운 사전 변수를 생성하고 업데이트한다. 너무나 자주 나오는 패턴의 문제인지라 그냥 기계적으로 이렇게 짠다.

반면, R로 작업할 때는 사전이 아니라 데이터프레임, 즉 테이블 형식의 자료구조를 가정하고 생각하게 된다.

src_dst_list <- data.frame(src=c('a', 'b', 'c', 'd', 'e'), dst_list=c('aa bb cc', 'bb', 'aa bb', 'bb cc', 'cc'), stringsAsFactors=F) dst_src <- data.frame( dst=unlist(sapply(src_dst_list$dst_list, function(x) { strsplit(x, ' ') })), src=src_dst_list[rep(1:nrow(src_dst_list), sapply(strsplit(src_dst_list$dst_list, ' '), length)), 'src'], row.names=NULL, stringsAsFactors=F) dst_src[order(dst_src$dst), ]

위의 코드를 설명하면

  1. dst_list에 있는 "aa bb cc", "bb", "aa bb", "bb cc", "cc"를 풀어헤쳐서 [aa bb cc bb aa bb bb cc cc]라는 벡터를 만든다. (이것이 dst_src 데이터프레임에서 dst라는 이름의 컬럼이 된다.)
  2. src에 있는 "a", "b", "c", "d", "e"는 각각에 대응하는 dst_list의 길이만큼 개수를 늘여준다. ("a"의 dst_list는 "aa bb cc", 즉 3개이므로 [a a a]가 된다. 결과적으로 src는 [a a a b c c d d e]가 된다.
  3. dst와 src를 가지고 새로운 데이터프레임을 만들면 아래와 같다.

이 방법이 최선인지는 모르겠지만, (SAS를 쓸 때와 마찬가지로) R에서는 하나의 관계가 하나의 행으로 표현되도록 생각하는 게 익숙하다. 여차하면 group_by나 join 같은 작업을 걸기도 쉽고 말이다.

마지막으로, 틈날 때 조금씩 배우고 있는 프로그래밍 언어 클로저(Clojure ) 코드를 보자. 여기의 코드를 참고했다. (실은 참고가 아니라 그냥 복붙했다.)

(def src_dst_list {:a [:aa :bb :cc], :b [:bb], :c [:aa :bb], :d [:bb :cc], :e [:cc]})

(reduce #(merge-with into %1 %2)
(map #(zipmap %2 (repeat (count %2) [%1])) (keys src_dst_list) (vals src_dst_list)))

  • 명시적으로 루프가 보이지는 않지만, src와 dst_list의 요소 각각에 대해서 함수를 호출하고 (map)
  • 명시적으로 변수가 보이지는 않지만, dst와 src를 매핑시켜서 맵을 생성한다. (zipmap)
  • 이 과정에서 (R에서 했던 것처럼) src의 개수를 dst_list의 길이에 따라서 뻥튀기한다. (repeat)
  • 그렇게 나누어서 작업한 결과를 하나로 합쳐준다. (reduce, merge-with)

파이썬과 R에서 쓴 방법을 섞어놓은 듯한 느낌이 들기도 한다. 아직은 이 언어의 용례에 익숙치 않아서 한 줄 짜리 코드도 한번에 파악하지 못하고 천천히 뜯어보아야 하지만, 계속 보면 익숙지겠거니 한다. 새로운 언어를 익히면 -실용적인 이득도 물론 중요하지만- 문제를 대하는 다른 관점을 배울 수 있다는 게 참 매력적인 것 같다.