R에서 Hashmap 사용하기

대부분의 컴퓨터언어에는 키로 값을 꺼내올 수 있는 자료구조인 hashmap을 제공합니다.

Python에는 자료구조 딕셔너리 dictionary를 지원합니다.

R에도 env라는 것이 있어서 딕셔너리 형태로 쓸 수는 있지만 R의 environment는 작업 공간으로도 사용되기 때문에 사용하기 복잡하고 부담스럽습니다. 학습장벽도 높은 편입니다.

그런데 다행히도 r2r 패키지라는 것이 있어서 이 패키지를 이용하면 R에서도 hashmap을 쓸 수 있습니다.

R-Object to R-Object Hash Maps

해시 테이블은 효율적인 코딩을 위한 가장 유용한 자료구조 중 하나입니다. 추상적으로 해시 테이블은 고유 키(문자열, 숫자 또는 더 복잡한 객체일 수 있음)로 값을 가르키게 한다고 생각 할 수 있습니다. 계산적 관점에서 볼 때 가장 두드러진 특징은 읽기/쓰기 작업(예: 특정 키 또는 키-값 쌍 저장, 검색 또는 삭제)이 테이블과 관계없이 평균 시간복잡도가 O(1)입니다.

많은 프로그래밍 언어는 해시 테이블이 각각의 방법으로 구현된 것이 제공됩니다. 예를 들면 C++의 std::unordered_map/sets와 Python의 dicts 및 set이 있습니다. 기본 R에서는 해시 테이블에 가장 가까운 객체는 environment입니다. 하지만 environment는사용자의 관점에서 처리하기 다소 번거로울 수 있으며 문자열타입의 키만 지원합니다.

r2r패키지는 기본 R 환경에서 제공하는 environment보다 R에서 쓸 수 있는 더 유연한 해시 테이블 기능을 구현해서 제공합니다.

특히 r2r 해시 테이블은 다음을 지원합니다.

  • 키와 값으로 임의의 R 객체
  • 임의의 키 비교 및 해시 함수
  • 누락된 키 예외에 대한 사용자 정의 가능한 동작(기본값 던지거나 반환)

여기에서는 r2r 해시 테이블에 대한 간단한 실습용 코드를 소개합니다.

우선 r2r 패키지를 로딩합니다.

library(r2r)

기본 사용법 Basic Manipulations

다음을 사용하여 빈 해시맵(해시 테이블)을 만듭니다.

m <- hashmap()

여러 가지 방법으로 m이라는 해시맵에 키-값 쌍을 삽입할 수 있습니다.

m[["key"]] <- "value" 
m[c(1, 2, 3)] <- c("a", "b", "c") 
# Vectorized over keys and values 
m[[c(4, 5, 6)]] <- c("d", "e", "f") 
# Not vectorized

다음 쿼리는 위의 설명에서 언급한 [[ 및 [ 연산자 간의 차이점을 설명합니다.

m[["key"]]
#> [1] "value"

m[c(1, 2, 3)]
#> [[1]]
#> [1] "a"
#> 
#> [[2]]
#> [1] "b"
#> 
#> [[3]]
#> [1] "c"
m[[c(1, 2, 3)]]
#> NULL

m[c(4, 5, 6)]
#> [[1]]
#> NULL
#> 
#> [[2]]
#> NULL
#> 
#> [[3]]
#> NULL
m[[c(4, 5, 6)]]
#> [1] "d" "e" "f"

단일 요소 삽입 및 쿼리는 제네릭 insert() 및 query()를 통해 수행할 수도 있습니다.

insert(m, "user", "vgherard") # Modifies `m` in place
query(m, "user")
#> [1] "vgherard"

세트 Sets

해시 테이블 외에도 단순히 키를 저장하는 해시 세트를 만들 수도 있습니다.

s <- hashset()
insert(s, 1)
s[[2]] <- T # equivalent to insert(s, 2)
s[c(1, 2, 3)]
#> [[1]]
#> [1] TRUE
#> 
#> [[2]]
#> [1] TRUE
#> 
#> [[3]]
#> [1] FALSE

키와 값의 타입 Key and value types

키와 값으로 사용할 수 있는 개체 유형에는 제한이 없습니다.

예를 들면 다음과 같이 모델을 키로 사용할 수도 있습니다.

m[[ lm(wt ~ mpg, mtcars) ]] <- list("This is my fit!", 840)
m[[ lm(wt ~ mpg, mtcars) ]]
#> [[1]]
#> [1] "This is my fit!"
#> 
#> [[2]]
#> [1] 840
m[[ lm(cyl ~ mpg, mtcars) ]]
#> NULL

기본값 설정하기 Setting default values

누락된 키에 대한 기본값을 설정할 수 있습니다.

다음은 그 예입니다.

m <- hashmap(default = 0)

이 기능은 카운터를 만드는 데 유용합니다.

objects <- list(1, 1, "1", FALSE, "1", 1)
for (object in objects)
    m[[object]] <- m[[object]] + 1
m[["1"]]
#> [1] 2

누락된 키를 참조하려고 할 때 exception을 throw할 수 있습니다.

m <- hashmap(on_missing_key = "throw")
tryCatch(m[["Missing key"]], error = function(cnd) "Oops!")
#> [1] "Oops!"

커스텀 키 비교와 해시 함수 Using custom key comparison and hash functions

hashmaps와 hashmaps는 기본적으로 base::identical()을 사용하여 키를 비교합니다.

m <- hashmap()
m[[1]] <- "double"
m[["1"]] <- "character"
m[[1]]
#> [1] "double"

이 동작은 키 비교 기능을 명시적으로 제공하여 변경할 수 있습니다. 올바르게 작동하게 하려면 동등한 키에 대해 동일한 해시를 생성하는 해시 함수를 명시적으로 제공해야 합니다.

이를 수행하는 간단한 방법은 다음 예제와 해시맵을 생성할 때 키에 전처리 함수를 정의할 수 있습니다.

m <- hashmap(key_preproc_fn = Arg)

키가 하나의 복소수라고 가정할 때 복소수 평면에서 방향이 같을 때 두 개의 키를 동등하게 간주합니다. 복소수 벡터의 방향은 R 함수 Arg()를 사용해서 구할 수 있기 때문에 Arg함수를 사용하는 걸이 이 경우에는 맞습니다. 생성자의 key_preproc_fn 인수를 통해 이러한 방식으로 키를 사전 처리하도록 해시맵에 지정할 수 있습니다.

의도한 대로 잘 작동하는지 확인해 봅니다.

m[list(1, 1 + 1i, 1i)] <- list("EAST", "NORTH-EAST", "NORTH")
m[[10]]
#> [1] "EAST"
m[[100i]]
#> [1] "NORTH"
m[[2 + 2i]]
#> [1] "NORTH-EAST"

참고: https://cran.r-project.org/web/packages/r2r/vignettes/r2r.html

Author: 떰학

답글 남기기