대부분의 컴퓨터언어에는 키로 값을 꺼내올 수 있는 자료구조인 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 패키지를 로딩합니다.
1 2 3 |
<a href="https://cran.r-project.org/web/packages/r2r/vignettes/r2r.html#cb1-1"></a>[crayon-674c9e7a727f5132446887 inline="1" lang="r" decode="true" ]library(r2r) |
기본 사용법 Basic Manipulations
다음을 사용하여 빈 해시맵(해시 테이블)을 만듭니다.
1 2 3 |
[crayon-674c9e7a727fb902843125 inline="1" lang="r" decode="true" ]m <- hashmap() |
여러 가지 방법으로 m이라는 해시맵에 키-값 쌍을 삽입할 수 있습니다.
1 2 3 |
[crayon-674c9e7a72800572949691 inline="1" lang="r" decode="true" ]m[["key"]] <- "value" |
다음 쿼리는 위의 설명에서 언급한 [[ 및 [ 연산자 간의 차이점을 설명합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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()를 통해 수행할 수도 있습니다.
1 2 3 4 5 |
insert(m, "user", "vgherard") # Modifies `m` in place query(m, "user") #> [1] "vgherard" |
세트 Sets
해시 테이블 외에도 단순히 키를 저장하는 해시 세트를 만들 수도 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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
키와 값으로 사용할 수 있는 개체 유형에는 제한이 없습니다.
예를 들면 다음과 같이 모델을 키로 사용할 수도 있습니다.
1 2 3 4 5 6 7 8 9 10 11 |
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
누락된 키에 대한 기본값을 설정할 수 있습니다.
다음은 그 예입니다.
1 2 3 |
m <- hashmap(default = 0) |
이 기능은 카운터를 만드는 데 유용합니다.
1 2 3 4 5 6 7 |
objects <- list(1, 1, "1", FALSE, "1", 1) for (object in objects) m[[object]] <- m[[object]] + 1 m[["1"]] #> [1] 2 |
누락된 키를 참조하려고 할 때 exception을 throw할 수 있습니다.
1 2 3 4 5 |
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()을 사용하여 키를 비교합니다.
1 2 3 4 5 6 7 |
m <- hashmap() m[[1]] <- "double" m[["1"]] <- "character" m[[1]] #> [1] "double" |
이 동작은 키 비교 기능을 명시적으로 제공하여 변경할 수 있습니다. 올바르게 작동하게 하려면 동등한 키에 대해 동일한 해시를 생성하는 해시 함수를 명시적으로 제공해야 합니다.
이를 수행하는 간단한 방법은 다음 예제와 해시맵을 생성할 때 키에 전처리 함수를 정의할 수 있습니다.
1 2 3 |
m <- hashmap(key_preproc_fn = Arg) |
키가 하나의 복소수라고 가정할 때 복소수 평면에서 방향이 같을 때 두 개의 키를 동등하게 간주합니다. 복소수 벡터의 방향은 R 함수 Arg()를 사용해서 구할 수 있기 때문에 Arg함수를 사용하는 걸이 이 경우에는 맞습니다. 생성자의 key_preproc_fn 인수를 통해 이러한 방식으로 키를 사전 처리하도록 해시맵에 지정할 수 있습니다.
의도한 대로 잘 작동하는지 확인해 봅니다.
1 2 3 4 5 6 7 8 9 |
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