環境作為雜湊對映

注意:在隨後的段落中,術語雜湊對映雜湊表可互換使用,並且指的是相同的概念 ,即通過使用內部雜湊函式提供有效金鑰查詢的資料結構。

介紹

雖然 R 不提供本機雜湊表結構,但是通過利用從 new.env(預設情況下)返回的 environment 物件提供雜湊鍵查詢的事實,可以實現類似的功能。以下兩個語句是等效的,因為 hash 引數預設為 TRUE

H <- new.env(hash = TRUE)
H <- new.env() 

另外,可以通過 size 引數指定內部雜湊表預先分配特定大小,size 引數的預設值為 29.與所有其他 R 物件一樣,environments 管理自己的記憶體並根據需要增加容量,因此,雖然沒有必要為 size 請求非預設值,但如果物件(最終)包含大量元素,則可能會有輕微的效能優勢。值得注意的是,通過 size 分配額外空間本身並不會導致記憶體佔用更大的物件:

object.size(new.env())
# 56 bytes

object.size(new.env(size = 10e4))
# 56 bytes 

插入

可以使用為 environment 類提供的 [[<-$<- 方法來完成元素的插入,不能使用單括號賦值([<-

H <- new.env()

H[["key"]] <- rnorm(1)

key2 <- "xyz"
H[[key2]] <- data.frame(x = 1:3, y = letters[1:3])

H$another_key <- matrix(rbinom(9, 1, 0.5) > 0, nrow = 3)

H["error"] <- 42
#Error in H["error"] <- 42 : 
#  object of type 'environment' is not subsettable 

與 R 的其他方面一樣,第一種方法(object[[key]] <- value)通常優於第二種方法(object$key <- value),因為在前一種情況下,可以使用變數而不是文字值(例如上面示例中的 key2)。

與雜湊對映實現的情況一樣,environment 物件不會儲存重複鍵。嘗試為現有金鑰插入鍵值對將替換先前儲存的值:

H[["key3"]] <- "original value"

H[["key3"]] <- "new value"

H[["key3"]]
#[1] "new value"

重點查詢

同樣,可以使用 [[$ 訪問元素,但不能使用 [ 訪問:

H[["key"]]
#[1] 1.630631
 
H[[key2]]   ## assuming key2 <- "xyz"
#   x y
# 1 1 a
# 2 2 b
# 3 3 c

H$another_key
#       [,1]  [,2]  [,3]
# [1,]  TRUE  TRUE  TRUE
# [2,] FALSE FALSE FALSE
# [3,]  TRUE  TRUE  TRUE

H[1]
#Error in H[1] : object of type 'environment' is not subsettable

檢查雜湊地圖

作為一個普通的 environment,雜湊對映可以通過典型的方式進行檢查:

names(H)
#[1] "another_key" "xyz"         "key"         "key3"       

ls(H)
#[1] "another_key" "key"         "key3"        "xyz"        
 
str(H)
#<environment: 0x7828228> 
 
ls.str(H)
# another_key :  logi [1:3, 1:3] TRUE FALSE TRUE TRUE FALSE TRUE ...
# key :  num 1.63
# key3 :  chr "new value"
# xyz : 'data.frame':    3 obs. of  2 variables:
#  $ x: int  1 2 3
#  $ y: chr  "a" "b" "c"

可以使用 rm 刪除元素:

rm(list = c("key", "key3"), envir = H)

ls.str(H)
# another_key :  logi [1:3, 1:3] TRUE FALSE TRUE TRUE FALSE TRUE ...
# xyz : 'data.frame':    3 obs. of  2 variables:
#  $ x: int  1 2 3
#  $ y: chr  "a" "b" "c"

靈活性

使用 environment 物件作為雜湊表的一個主要好處是它能夠將幾乎任何型別的物件儲存為值,甚至其他 environments

H2 <- new.env()

H2[["a"]] <- LETTERS
H2[["b"]] <- as.list(x = 1:5, y = matrix(rnorm(10), 2))
H2[["c"]] <- head(mtcars, 3)
H2[["d"]] <- Sys.Date()
H2[["e"]] <- Sys.time()
H2[["f"]] <- (function() {
    H3 <- new.env()
    for (i in seq_along(names(H2))) {
        H3[[names(H2)[i]]] <- H2[[names(H2)[i]]]
    }
    H3
})()

ls.str(H2)
# a :  chr [1:26] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" ...
# b : List of 5
#  $ : int 1
#  $ : int 2
#  $ : int 3
#  $ : int 4
#  $ : int 5
# c : 'data.frame':    3 obs. of  11 variables:
#  $ mpg : num  21 21 22.8
#  $ cyl : num  6 6 4
#  $ disp: num  160 160 108
#  $ hp  : num  110 110 93
#  $ drat: num  3.9 3.9 3.85
#  $ wt  : num  2.62 2.88 2.32
#  $ qsec: num  16.5 17 18.6
#  $ vs  : num  0 0 1
#  $ am  : num  1 1 1
#  $ gear: num  4 4 4
#  $ carb: num  4 4 1
# d :  Date[1:1], format: "2016-08-03"
# e :  POSIXct[1:1], format: "2016-08-03 19:25:14"
# f : <environment: 0x91a7cb8> 

ls.str(H2$f)
# a :  chr [1:26] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" ...
# b : List of 5
#  $ : int 1
#  $ : int 2
#  $ : int 3
#  $ : int 4
#  $ : int 5
# c : 'data.frame':    3 obs. of  11 variables:
#  $ mpg : num  21 21 22.8
#  $ cyl : num  6 6 4
#  $ disp: num  160 160 108
#  $ hp  : num  110 110 93
#  $ drat: num  3.9 3.9 3.85
#  $ wt  : num  2.62 2.88 2.32
#  $ qsec: num  16.5 17 18.6
#  $ vs  : num  0 0 1
#  $ am  : num  1 1 1
#  $ gear: num  4 4 4
#  $ carb: num  4 4 1
# d :  Date[1:1], format: "2016-08-03"
# e :  POSIXct[1:1], format: "2016-08-03 19:25:14"

限制

使用 environment 物件作為雜湊對映的一個主要限制是,與 R 的許多方面不同,元素查詢/插入不支援向量化:

names(H2)
#[1] "a" "b" "c" "d" "e" "f"

H2[[c("a", "b")]]
#Error in H2[[c("a", "b")]] : 
#  wrong arguments for subsetting an environment
 
Keys <- c("a", "b")
H2[[Keys]]
#Error in H2[[Keys]] : wrong arguments for subsetting an environment

根據儲存在物件中的資料的性質,可以使用 vapplylist2env 一次分配多個元素:

E1 <- new.env()
invisible({
    vapply(letters, function(x) {
        E1[[x]] <- rnorm(1)
        logical(0)
    }, FUN.VALUE = logical(0))
})

all.equal(sort(names(E1)), letters)
#[1] TRUE

Keys <- letters
E2 <- list2env(
    setNames(
        as.list(rnorm(26)),
        nm = Keys), 
    envir = NULL,
    hash = TRUE
)

all.equal(sort(names(E2)), letters)
#[1] TRUE

上述兩者都不是特別簡潔,但是當鍵值對的數量很大時,可能優選使用 for 迴圈等。