R中的嵌套散列 [英] Nested hashes in R

查看:7
本文介绍了R中的嵌套散列的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何有效地将深度散列与R中的变量嵌套?例如:

hash <- new.env(hash = TRUE, parent = emptyenv(), size = 100L)
foo <- 'food'
bar <- 'fruits'
var <- 'apple'
count <- 1

# this will work... but it's only one level in
hash[[foo]] <- count

# A deeper nest is needed, but does not work:
hash[[foo]][[bar]][[var]] <- count

# this gets closer, but foo and bar need to be evaluated as their variables
hash$foo$bar$var <- count

# examine the keys
ls(hash)
我从post here中看到了答案,这在R中可能不可用。这是真的吗?它与$赋值一起出现,我们可以更深入一些,但这里的问题需要变量。

我看到using the environment's hash capabilities are faster than using some packages,学习最快的方法可能是最好的方法,但如果需要一个包来完成这项工作,那么我想必须使用它。

推荐答案

这里有一个列表列表,其中包含您想要的属性,可以添加任意级别,而无需‘预声明’。

l = list()
l[[foo]][[bar]][[var]] = 2

列表到哈希哈希

之后,您可以将其从列表列表转换为嵌套环境(这将允许将结构传递给函数,并更新叶节点,例如,不必返回结构),如下所示

as_environment = function(x) {
    if (is.list(x)) {
        x <- lapply(x, as_environment)
        x <- as.environment(x)
    }
    x
}

e = as_environment(l)

这表明可以在R中嵌套哈希

以下是一些数据--包含50、100或1,000个可能值的嵌套级别,以及总共10000个数据点

m = c(50, 100, 1000)
n = 1000 * 10
d = list(
    a = sample(as.character(seq_len(m[[1]])), n, TRUE),
    b = sample(as.character(seq_len(m[[2]])), n, TRUE),
    c = sample(as.character(seq_len(m[[3]])), n, TRUE)
)

以下是一些用于衡量性能的函数

f0 = function(a, b, c, n) {
    ## data access fixed cost
    for (i in seq_len(n))
        c(a[[i]], b[[i]], c[[i]])
}

f1 = function(x, a, b, c, n) {
    ## creation / assignment
    for (i in seq_len(n))
        x[[ a[[i]] ]][[ b[[i]] ]][[ c[[i]] ]] <- 1
    x
}

f2 = function(x, a, b, c, n) {
    ## update
    for (i in seq_len(n))
        x[[ a[[i]] ]][[ b[[i]] ]][[ c[[i]] ]] <-
            x[[ a[[i]] ]][[ b[[i]] ]][[ c[[i]] ]] + 1
    x
}

以下是一些基准数据

library(microbenchmark)

l <- with(d, f1(list(), a, b, c, n))
e <- as_environment(l)
microbenchmark(
    with(d, f0(a, b, c, n)),
    with(d, f1(list(), a, b, c, n)),
    with(d, f2(l, a, b, c, n)),
    with(d, f2(e, a, b, c, n)),
    times = 10
)

带输出...

Unit: milliseconds
                            expr      min       lq      mean    median        uq       max neval
         with(d, f0(a, b, c, n)) 16.59220 17.37859  19.36920  18.02578  20.46342  25.21631    10
 with(d, f1(list(), a, b, c, n)) 72.54094 74.24085  83.54071  81.90286  90.75257  98.03838    10
      with(d, f2(l, a, b, c, n)) 86.65550 96.49548 104.69007 101.74540 116.04673 135.76844    10
      with(d, f2(e, a, b, c, n)) 48.53202 52.89202  57.76179  55.37080  64.14356  69.74413    10
首先,时间单位是毫秒。其次,更新散列的时间比更新列表快不到50%。如果我将n增加10倍,我会看到这些时间大约增加了10倍--列表列表和散列的散列都在近似线性地扩展。

这些时间安排强烈表明,就性能而言,至少对于这种规模的数据,我们不妨使用直接的列表方法。

尽管如此...

支持嵌套构造的哈希类?

这里有一个"Hash"类,它是一个环境

Hash <- function()
    structure(new.env(parent = emptyenv()), class = "Hash")
h = Hash()

如果我们尝试

h[[foo]][[bar]][[var]] <- 1

h是哈希,但它包含作为列表列表的单个键

> h
<environment: 0x7fddbd00c490>
attr(,"class")
[1] "Hash"
> h[[foo]]
$fruits
$fruits$apple
[1] 1

这是因为R执行赋值的方式--基本上是从右到左,因此在将其分配给我们的Hash/环境之前,创建一个list(apple = 1),然后创建list(fruits = list(apple = 1))。我真的看不出如何使用现有语法来强制创建具有最右侧赋值的环境,但我们可以编写一个UPDATE方法,在第一次赋值时将列表列表强制为Hash-of-Hash

## like as_environment, above...
as_Hash = function(x) {
    if (is.list(x)) {
        x <- lapply(x, as_Hash)
        x <- structure(as.environment(x), class = "Hash")
    }
    x
}

## re-define assignment of an element to a hash -- if it's a list-of-lists, 
## then coerce to a Hash-of-Hashes
`[[<-.Hash` <- function(x, i, value) {
    if (is.list(value))
        value <- as_Hash(value)
    assign(i, value, x)
    x
}

分配完成后,结果始终为Hash-of-Hash。

> h = Hash()
> h[[foo]][[bar]][[var]] <- 1
> h[[foo]][["vegetable"]][["tomato"]] <- 2
> h
<environment: 0x7fddbb7102c8>
attr(,"class")
[1] "Hash"
> h[[foo]]
<environment: 0x7fddbb719ca8>
attr(,"class")
[1] "Hash"
> ls(h[[foo]])
[1] "fruits"    "vegetable"
> h[[foo]][[bar]]
<environment: 0x7fddbb718d80>
attr(,"class")
[1] "Hash"
> h[[foo]][[bar]][[var]]
[1] 1

但有必要吗?

返回原始示例

l = list()
l[[foo]][[bar]][[var]] = 2

您可以使用.Internal(inspect(l))了解R是如何组织事物的

> .Internal(inspect(l))
@7f886228d6d0 19 VECSXP g0c1 [REF(1),ATT] (len=1, tl=0)
  @7f886228d698 19 VECSXP g0c1 [REF(1),ATT] (len=1, tl=0)
    @7f886228d660 19 VECSXP g0c1 [REF(1),ATT] (len=1, tl=0)
      @7f886228d740 14 REALSXP g0c1 [REF(3)] (len=1, tl=0) 2
## ... additional output, dealing with the names (ATTRIB) at each level
这意味着l由位于特定地址@7f886228d6d0的存储器表示,该地址表示R的内部列表表示(aVECSXP)。VECSXP长度为1,指向@7f886228d698处的另一个列表/VECSXP。这指向另一个列表/VECSXPat@7f886228d660,其中包含您分配的值--aREALSXPat@7f886228d740

如果更新元素,会发生什么情况?

> l[[foo]][[bar]][[var]] <- 3
> .Internal(inspect(l))
@7f886228d6d0 19 VECSXP g0c1 [REF(1),ATT] (len=1, tl=0)
  @7f886228d698 19 VECSXP g0c1 [REF(1),ATT] (len=1, tl=0)
    @7f886228d660 19 VECSXP g0c1 [REF(1),ATT] (len=1, tl=0)
      @7f886228d5f0 14 REALSXP g0c1 [REF(3)] (len=1, tl=0) 3
...
请注意,只有REALSXP的内存位置发生了更改,因此您并没有复制整个结构,而只是复制了实际发生更改的部分。很好。

再加一个水果怎么样?

> l[[foo]][[bar]][["pear"]] <- 4
> .Internal(inspect(l))
@7f886228d6d0 19 VECSXP g0c1 [REF(1),ATT] (len=1, tl=0)
  @7f886228d698 19 VECSXP g0c1 [REF(1),ATT] (len=1, tl=0)
    @7f885dc9ff48 19 VECSXP g0c2 [REF(1),ATT] (len=2, tl=0)
      @7f886228d5f0 14 REALSXP g0c1 [REF(3)] (len=1, tl=0) 3
      @7f88622a4200 14 REALSXP g0c1 [REF(3)] (len=1, tl=0) 4
...

我们为梨添加了REALSXP,但也更改了VECSXP水果。我们没有为苹果更改REALSXP,也没有更改其他VECSXP--我们再次只更改(或几乎只更改)需要更改的内存。

和改变食物链中较高的元素;)?

> l[[foo]][["vegetables"]][["tomato"]] <- 4
> .Internal(inspect(l))
@7f886228d6d0 19 VECSXP g0c1 [REF(1),ATT] (len=1, tl=0)
  @7f885dca0148 19 VECSXP g0c2 [REF(1),ATT] (len=2, tl=0)
    @7f885dc9ff48 19 VECSXP g0c2 [REF(1),ATT] (len=2, tl=0)
      @7f886228d5f0 14 REALSXP g0c1 [REF(3)] (len=1, tl=0) 3
      @7f88622a4200 14 REALSXP g0c1 [REF(3)] (len=1, tl=0) 4
...
    @7f88622a3f98 19 VECSXP g0c1 [REF(1),ATT] (len=1, tl=0)
      @7f88622a4008 14 REALSXP g0c1 [REF(3)] (len=1, tl=0) 4

我们更改了水果和蔬菜级别对应的VECSXP,当然也添加了我们的番茄,但数据结构的其他组件保持不变。

这表明R对数据进行了最小限度的复制,因此我们可以预期该数据结构对于大小合理的嵌套列表是相对有效的。在投资更多(或这么多)之前,应该先弄清楚情况是否如此。努力!

这篇关于R中的嵌套散列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆