Buscar..


Entornos como mapas hash

Nota: en los pasajes subsiguientes, los términos hash map y hash table se usan indistintamente y se refieren al mismo concepto , es decir, una estructura de datos que proporciona una búsqueda de claves eficiente mediante el uso de una función hash interna.

Introducción

Aunque R no proporciona una estructura de tabla hash nativa, se puede lograr una funcionalidad similar aprovechando el hecho de que el objeto de environment devuelto por new.env (de forma predeterminada) proporciona búsquedas de clave hash. Las siguientes dos declaraciones son equivalentes, ya que el parámetro hash defecto es TRUE :

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

Además, se puede especificar que la tabla hash interna se asigne previamente con un tamaño particular a través del parámetro size , que tiene un valor predeterminado de 29. Como todos los demás objetos R, los environment administran su propia memoria y aumentarán su capacidad según sea necesario Entonces, si bien no es necesario solicitar un valor de size no predeterminado, puede haber una ligera ventaja de rendimiento al hacerlo si el objeto (eventualmente) contendrá una gran cantidad de elementos. Vale la pena señalar que la asignación de espacio adicional a través del size no resulta, en sí mismo, en un objeto con una huella de memoria más grande:

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

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

Inserción

La inserción de elementos se puede hacer usando cualquiera de los métodos [[<- o $<- proporcionados para la clase de environment , pero no usando la asignación de "corchete único" ( [<- ) :

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 

Como otras facetas de R, el primer método ( object[[key]] <- value ) generalmente se prefiere al segundo ( object$key <- value ) porque en el primer caso, se puede usar una variable en lugar de un valor literal (por ejemplo, key2 en el ejemplo anterior).

Como suele ser el caso con las implementaciones de mapeo hash, el objeto de environment no almacenará claves duplicadas. El intento de insertar un par clave-valor para una clave existente reemplazará el valor previamente almacenado:

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

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

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

Búsqueda de claves

Del mismo modo, se puede acceder a los elementos con [[ o $ , pero no con [ :

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

Inspeccionando el mapa de hash

Al ser solo un environment normal, el mapa hash puede ser inspeccionado por medios típicos:

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"

Los elementos se pueden eliminar usando 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"

Flexibilidad

Uno de los principales beneficios de usar objetos de environment como tablas hash es su capacidad para almacenar virtualmente cualquier tipo de objeto como valor, incluso otros 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"

Limitaciones

Una de las principales limitaciones de usar objetos de environment como mapas hash es que, a diferencia de muchos aspectos de R, la vectorización no es compatible con la búsqueda / inserción de elementos:

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

Dependiendo de la naturaleza de los datos que se almacenan en el objeto, puede ser posible usar vapply o list2env para asignar muchos elementos a la vez:

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

Ninguno de los anteriores es particularmente conciso, pero puede ser preferible a usar un bucle for , etc. cuando el número de pares clave-valor es grande.

paquete: hash

El paquete hash ofrece una estructura de hash en R. Sin embargo, los términos de tiempo para ambas inserciones y lecturas se comparan desfavorablemente con el uso de entornos como hash. Esta documentación simplemente reconoce su existencia y proporciona un código de tiempo de muestra a continuación por las razones mencionadas anteriormente. No hay un caso identificado donde el hash sea una solución apropiada en el código R hoy.

Considerar:

# 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')
    )
})

paquete: listenv

Aunque package:listenv implementa una interfaz similar a una lista para los entornos, su rendimiento en relación con los entornos con fines similares a hash es pobre en la recuperación de hash . Sin embargo, si los índices son numéricos, puede ser bastante rápido en la recuperación. Sin embargo, tienen otras ventajas, por ejemplo, compatibilidad con el package:future . Cubrir este paquete para ese propósito va más allá del alcance del tema actual. Sin embargo, el código de tiempo proporcionado aquí se puede usar junto con el ejemplo para el paquete: hash para los tiempos de escritura.

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow