Suche…


Umgebungen als Hash-Maps

Hinweis: In den folgenden Passagen werden die Begriffe Hash-Map und Hash-Tabelle austauschbar verwendet und beziehen sich auf dasselbe Konzept , nämlich auf eine Datenstruktur, die eine effiziente Schlüsselsuche durch Verwendung einer internen Hash-Funktion ermöglicht.

Einführung

Obwohl R keine native Hashtabellenstruktur bereitstellt, kann eine ähnliche Funktionalität erreicht werden, indem die Tatsache genutzt wird, dass das environment , das von new.env (standardmäßig), Hash-Schlüsselsuchen bietet. Die folgenden beiden Anweisungen sind gleichwertig, da der hash Parameter standardmäßig TRUE :

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

Darüber hinaus kann angegeben werden, dass die interne Hashtabelle über den Parameter size mit einer bestimmten Größe vorab zugewiesen wird. Der Parameter hat den Standardwert 29. Wie alle anderen R-Objekte verwaltet auch die environment ihren eigenen Speicher und nimmt bei Bedarf mit der Kapazität zu Obwohl es nicht erforderlich ist, einen nicht voreingestellten Wert für size anzufordern, kann dies einen geringfügigen Leistungsvorteil zur Folge haben, wenn das Objekt (möglicherweise) eine sehr große Anzahl von Elementen enthält. Es ist erwähnenswert, dass das Zuweisen von zusätzlichem Speicherplatz über die size sich nicht zu einem Objekt mit einem größeren Speicherbedarf führt:

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

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

Einfügung

Das Einfügen von Elementen kann entweder mit der für die environment bereitgestellten [[<- oder $<- Methode durchgeführt werden, jedoch nicht mit der Zuweisung einer einzelnen Klammer ( [<- ) :

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 

Wie bei anderen Facetten von R wird die erste Methode ( object[[key]] <- value ) im Allgemeinen der zweiten Methode ( object[[key]] <- value object$key <- value ) vorgezogen, da im ersten Fall eine Variable anstelle eines Literalwerts verwendet werden kann (zB key2 im obigen Beispiel).

Wie bei Hash-Map-Implementierungen im Allgemeinen wird das environment keine doppelten Schlüssel speichern. Der Versuch, ein Schlüsselwertpaar für einen vorhandenen Schlüssel einzufügen, ersetzt den zuvor gespeicherten Wert:

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

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

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

Schlüsselsuche

Ebenso kann auf Elemente mit [[ oder $ ] zugegriffen werden, jedoch nicht mit [ :

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

Überprüfen der Hashkarte

Da es sich um eine gewöhnliche environment , kann die Hash-Karte mit typischen Mitteln überprüft werden:

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"

Elemente können mit rm entfernt werden:

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"

Flexibilität

Ein Hauptvorteil der Verwendung von environment als Hashtabellen besteht darin, dass praktisch alle Arten von Objekten als Wert gespeichert werden können, selbst in anderen environment :

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"

Einschränkungen

Eine der größten Einschränkungen bei der Verwendung von environment als Hash-Maps besteht darin, dass im Gegensatz zu vielen Aspekten von R die Vektorisierung für das Suchen / Einfügen von Elementen nicht unterstützt wird:

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

Abhängig von der Art der im Objekt gespeicherten Daten kann es möglich sein, vapply oder list2env für die gleichzeitige Zuweisung vieler Elemente zu verwenden:

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

Keines der obigen Punkte ist besonders knapp, kann jedoch der Verwendung einer for Schleife usw. vorzuziehen sein, wenn die Anzahl der Schlüsselwertpaare groß ist.

Paket: Hash

Das Hash-Paket bietet eine Hash-Struktur in R. Allerdings ist das Timing für beide Einfügungen und Lesevorgänge ungünstig mit der Verwendung von Umgebungen als Hash. Diese Dokumentation bestätigt lediglich das Vorhandensein und enthält aus den oben genannten Gründen einen Beispiel-Timing-Code. Es gibt keinen identifizierten Fall, in dem Hash heute eine geeignete Lösung im R-Code ist.

Erwägen:

# Generic unique string generator
unique_strings <- function(n){
    string_i <- 1
    string_len <- 1
    ans <- character(n)
    chars <- c(letters,LETTERS)
    new_strings <- function(len,pfx){
    for(i in 1:length(chars)){
        if (len == 1){
        ans[string_i] <<- paste(pfx,chars[i],sep='')
        string_i <<- string_i + 1
        } else {
        new_strings(len-1,pfx=paste(pfx,chars[i],sep=''))
        }
        if (string_i > n) return ()
    }
    }
    while(string_i <= n){
    new_strings(string_len,'')
    string_len <- string_len + 1
    }
    sample(ans)
}

# Generate timings using an enviornment
timingsEnv <- plyr::adply(2^(10:15),.mar=1,.fun=function(i){
    strings <- unique_strings(i)
    ht1 <- new.env(hash=TRUE)
    lapply(strings, function(s){ ht1[[s]] <<- 0L})
    data.frame(
    size=c(i,i),
    seconds=c(
        system.time(for (j in 1:i) ht1[[strings[j]]]==0L)[3]),
    type = c('1_hashedEnv')
    )
})

timingsHash <- plyr::adply(2^(10:15),.mar=1,.fun=function(i){
    strings <- unique_strings(i)
    ht <- hash::hash()
    lapply(strings, function(s) ht[[s]] <<- 0L)
    data.frame(
    size=c(i,i),
    seconds=c(
        system.time(for (j in 1:i) ht[[strings[j]]]==0L)[3]),
    type = c('3_stringHash')
    )
})

Paket: listenv

Obwohl package:listenv eine package:listenv Schnittstelle für Umgebungen implementiert, ist seine Leistung im Vergleich zu Umgebungen für Hash-ähnliche Zwecke beim Abrufen von Hashes schlecht . Wenn die Indizes jedoch numerisch sind, kann es beim Abrufen recht schnell gehen. Sie haben jedoch andere Vorteile, z. B. Kompatibilität mit package:future . Das Abdecken dieses Pakets zu diesem Zweck geht über den Rahmen des aktuellen Themas hinaus. Der hier angegebene Timing-Code kann jedoch in Verbindung mit dem Beispiel für package: hash für Schreib-Timings verwendet werden.

timingsListEnv <- plyr::adply(2^(10:15),.mar=1,.fun=function(i){
    strings <- unique_strings(i)
    le <- listenv::listenv()
    lapply(strings, function(s) le[[s]] <<- 0L)
    data.frame(
    size=c(i,i),
    seconds=c(
        system.time(for (k in 1:i) le[[k]]==0L)[3]),
    type = c('2_numericListEnv')
    )
})


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow