대부분의 컴퓨터언어에는 키로 값을 꺼내올 수 있는 자료구조인 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