将对象以摊销的固定时间O(1)附加到R中的列表吗?


245

如果我有一些R list mylist,则可以obj像这样添加一个项目:

mylist[[length(mylist)+1]] <- obj

但是肯定有一些更紧凑的方法。当我刚加入R时,我尝试这样写lappend()

lappend <- function(lst, obj) {
    lst[[length(lst)+1]] <- obj
    return(lst)
}

但是由于R的按名称调用语义(当然lst会在调用时有效复制,因此当然不起作用,因此对的更改lst在.scope范围之外是不可见的lappend()。我知道您可以在R函数中进行环境黑客攻击,以到达函数范围并更改调用环境,但这似乎是编写简单附加函数的重击。

谁能建议一种更漂亮的方式呢?奖励积分(如果它适用于矢量和列表)。


5
R具有函数语言中常有的不变数据特征,讨厌这样说,但是我认为您只需要处理它。它有它的优点和缺点它

当您说“按名称致电”时,您的意思是“按值致电”,对吗?
肯·威廉姆斯

7
不,绝对不是按值调用,否则这不会成为问题。R实际上使用按需调用(en.wikipedia.org/wiki/Evaluation_strategy#Call_by_need)。
尼克

4
一个好主意是预先分配向量/列表:N = 100 mylist = vector('list',N)for(i in 1:N){#mylist [[i]] = ...}避免'增长'对象在R。–
费尔南多

我不小心在这里找到了答案,stackoverflow.com / questions / 17046336 /…很难实现如此简单的算法!
KH Kim

Answers:


255

如果是字符串列表,则只需使用c()函数:

R> LL <- list(a="tom", b="dick")
R> c(LL, c="harry")
$a
[1] "tom"

$b
[1] "dick"

$c
[1] "harry"

R> class(LL)
[1] "list"
R> 

这也适用于矢量,所以我可以获得奖励积分吗?

编辑(2015-Feb-01):这篇帖子即将发表五周年。某些读者不断重复使用它的任何缺点,因此也一定会看到下面的一些评论。一种建议的list类型:

newlist <- list(oldlist, list(someobj))

通常,R类型很难使所有类型和用途只有一个成语。


19
这不附加...它串联在一起。 调用LL后仍将有两个元素C(LL, c="harry")
尼克

27
只需重新分配给LL:即可 LL <- c(LL, c="harry")
Dirk Eddelbuettel

51
这仅适用于字符串。如果a,b和c是整数向量,则行为是完全不同的。
Alexandre Rademaker 2010年

8
@Dirk:您的parens嵌套方式与我不同。我的调用c()有2个参数:我要附加的列表,即list(a=3, b=c(4, 5)),和我要附加的项目,即c=c(6, 7)。如果使用我的方法,您会看到追加了两个列表项(67,名称分别为c1c2),而不是c显然要使用的单个2元素向量命名为!
j_random_hacker 2011年

7
结论是mylist <- list(mylist, list(obj))吗?如果是的话,修改答案会很好
Matthew

96

OP(在问题的2012年4月更新版中)有兴趣了解是否有办法以固定的固定时间添加到列表中,例如可以使用C ++ vector<>容器来完成。到目前为止,此处的最佳答案仅显示在给定大小问题的情况下各种解决方案的相对执行时间,而没有直接解决各种解决方案的算法效率问题。在许多答案下面的评论讨论了某些解决方案的算法效率,但是在每种情况下(截至2015年4月),它们得出的结论都是错误的。

随着问题规模的增长,算法效率捕捉时间(执行时间)或空间(消耗的内存量)的增长特征。针对给定大小问题的各种解决方案运行性能测试不能解决各种解决方案的增长率。OP有兴趣了解是否有办法在“摊销的固定时间”内将对象附加到R列表。那是什么意思?为了解释,首先让我描述“恒定时间”:

  • 恒定O(1)增长:

    如果执行给定任务所需的时间与问题的大小加倍保持相同,则可以说该算法显示出恒定的时间增长,或者用“ Big O”表示法表示出O(1)时间增长。当OP说“摊销”恒定时间时,他只是表示“从长远来看”……即,如果执行单个操作偶尔会比正常情况花费更长的时间(例如,如果预分配的缓冲区已用完,并且偶尔需要调整为更大的大小)缓冲区大小),只要长期平均性能是恒定时间,我们仍将其称为O(1)。

    为了进行比较,我还将描述“线性时间”和“二次时间”:

  • 线性O(n)增长:

    如果时间需要执行给定任务双打作为问题的大小加倍,那么我们说的算法表现出的线性时间,或者为O(n)的增长。

  • 二次O(n 2增长:

    如果执行给定任务所需的时间增加问题大小的平方,我们可以说算法显示二次时间,即O(n 2增长。

还有许多其他效率级别的算法。我顺应Wikipedia的文章进行进一步讨论。

我感谢@CronAcronis的回答,因为我是R的新手,很高兴有一个完整的代码块可以对本页上显示的各种解决方案进行性能分析。我借用他的代码进行分析,并在下面复制(包装在函数中):

library(microbenchmark)
### Using environment as a container
lPtrAppend <- function(lstptr, lab, obj) {lstptr[[deparse(substitute(lab))]] <- obj}
### Store list inside new environment
envAppendList <- function(lstptr, obj) {lstptr$list[[length(lstptr$list)+1]] <- obj} 
runBenchmark <- function(n) {
    microbenchmark(times = 5,  
        env_with_list_ = {
            listptr <- new.env(parent=globalenv())
            listptr$list <- NULL
            for(i in 1:n) {envAppendList(listptr, i)}
            listptr$list
        },
        c_ = {
            a <- list(0)
            for(i in 1:n) {a = c(a, list(i))}
        },
        list_ = {
            a <- list(0)
            for(i in 1:n) {a <- list(a, list(i))}
        },
        by_index = {
            a <- list(0)
            for(i in 1:n) {a[length(a) + 1] <- i}
            a
        },
        append_ = { 
            a <- list(0)    
            for(i in 1:n) {a <- append(a, i)} 
            a
        },
        env_as_container_ = {
            listptr <- new.env(parent=globalenv())
            for(i in 1:n) {lPtrAppend(listptr, i, i)} 
            listptr
        }   
    )
}

@CronAcronis发布的结果似乎肯定表明该a <- list(a, list(i))方法最快,至少对于问题大小为10000而言,但是对于单个问题大小的结果并不能解决解决方案的增长。为此,我们至少需要运行两个性能不同的分析测试:

> runBenchmark(2e+3)
Unit: microseconds
              expr       min        lq      mean    median       uq       max neval
    env_with_list_  8712.146  9138.250 10185.533 10257.678 10761.33 12058.264     5
                c_ 13407.657 13413.739 13620.976 13605.696 13790.05 13887.738     5
             list_   854.110   913.407  1064.463   914.167  1301.50  1339.132     5
          by_index 11656.866 11705.140 12182.104 11997.446 12741.70 12809.363     5
           append_ 15986.712 16817.635 17409.391 17458.502 17480.55 19303.560     5
 env_as_container_ 19777.559 20401.702 20589.856 20606.961 20939.56 21223.502     5
> runBenchmark(2e+4)
Unit: milliseconds
              expr         min         lq        mean    median          uq         max neval
    env_with_list_  534.955014  550.57150  550.329366  553.5288  553.955246  558.636313     5
                c_ 1448.014870 1536.78905 1527.104276 1545.6449 1546.462877 1558.609706     5
             list_    8.746356    8.79615    9.162577    8.8315    9.601226    9.837655     5
          by_index  953.989076 1038.47864 1037.859367 1064.3942 1065.291678 1067.143200     5
           append_ 1634.151839 1682.94746 1681.948374 1689.7598 1696.198890 1706.683874     5
 env_as_container_  204.134468  205.35348  208.011525  206.4490  208.279580  215.841129     5
> 

首先,要提一下关于min / lq / mean / median / uq / max值的信息:由于我们在5个运行中都执行完全相同的任务,因此在理想的世界中,我们可以预期它将花费完全相同的时间每次运行的时间量。但是由于我们正在测试的代码尚未加载到CPU的缓存中,因此通常将首次运行的时间偏向更长的时间。第一次运行后,我们希望时间保持一致,但是有时由于定时器滴答中断或与我们正在测试的代码无关的其他硬件中断,我们的代码可能会从缓存中逐出。通过对代码段进行5次测试,我们允许在第一次运行时将代码加载到缓存中,然后为每个代码段提供4次运行完成的机会,而不会受到外部事件的干扰。为此原因,

请注意,我选择首先运行问题大小为2000的问题,然后选择20000,因此我的问题大小从第一次运行到第二次增加了10倍。

list解决方案的性能:O(1)(恒定时间)

首先,让我们看一下该list解决方案的增长,因为我们可以立即说出这是两次分析运行中最快的解决方案:在第一次运行中,执行2000个“追加”任务花费了854 微秒(0.854 毫秒)。在第二次运行中,花费了8.746毫秒来执行20000个“追加”任务。幼稚的观察者会说:“啊,list解决方案呈现出O(n)增长,因为问题的大小增加了十倍,所以执行测试所需的时间也增加了。” 该分析的问题在于,OP希望的是单个对象插入的增长率,而不是整个问题的增长率。知道这一点,那么很明显list 解决方案完全提供了OP想要的:一种在O(1)时间内将对象追加到列表的方法。

其他解决方案的性能

其他解决方案都无法与list解决方案的速度相提并论,但是无论如何检查它们都是有益的:

其他大多数解决方案的性能似乎为O(n)。例如,该by_index解决方案是一种非常流行的解决方案,它基于我在其他SO帖子中发现它的频率,它花费11.6毫秒来添加2000个对象,而花费953毫秒来添加十倍于许多对象。整个问题的时间增加了100倍,因此,一个幼稚的观察者可能会说:“啊,by_index解决方案的O(n 2)增长,因为问题的大小增加了10倍,执行测试所需的时间也增加了乘以100。”和以前一样,这种分析是有缺陷的,因为OP对单个对象插入的增长很感兴趣。如果用总时间增长除以问题大小的增长,我们发现附加对象的时间增长仅增长了10倍,而不是100倍,这与问题大小的增长相符,因此by_index解为O (n)。没有列出解决方案显示O(n 2)增长用于附加单个对象。


1
给读者:请阅读JanKanis的答案,它提供了一个非常实用的扩展,我发现上面,以及潜水位到给定的C实现R的内部工作的各种解决方案的开销
phonetagger

4
不确定list选项是否满足要求:> length(c(c(c(list(1)),list(2)),list(3)))[1] 3>长度(list(list(list (list(1)),list(2)),list(3)))[1] 2.看起来更像是嵌套列表。
Picarus '16

@Picarus-我认为你是对的。我不再使用R,但值得庆幸的是,JanKanis发布了一个包含更有用的O(1)解决方案的答案,并指出了您发现的问题。我确定JanKanis会感谢您的支持。
phonetagger

@phonetagger,您应该编辑答案。并非所有人都会阅读所有答案。
Picarus

“没有一个答案解决了实际问题”->问题是原来的问题与算法复杂性无关,请看该问题的版本。OP首先询问如何在列表中添加元素,几个月后,他改变了这个问题。
卡洛斯·辛纳利

41

在其他答案中,仅该list方法导致O(1)追加,但它导致深度嵌套的列表结构,而不是简单的单个列表。我使用了以下数据结构,它们支持O(1)(摊销)附加,并允许将结果转换回纯列表。

expandingList <- function(capacity = 10) {
    buffer <- vector('list', capacity)
    length <- 0

    methods <- list()

    methods$double.size <- function() {
        buffer <<- c(buffer, vector('list', capacity))
        capacity <<- capacity * 2
    }

    methods$add <- function(val) {
        if(length == capacity) {
            methods$double.size()
        }

        length <<- length + 1
        buffer[[length]] <<- val
    }

    methods$as.list <- function() {
        b <- buffer[0:length]
        return(b)
    }

    methods
}

linkedList <- function() {
    head <- list(0)
    length <- 0

    methods <- list()

    methods$add <- function(val) {
        length <<- length + 1
        head <<- list(head, val)
    }

    methods$as.list <- function() {
        b <- vector('list', length)
        h <- head
        for(i in length:1) {
            b[[i]] <- head[[2]]
            head <- head[[1]]
        }
        return(b)
    }
    methods
}

如下使用它们:

> l <- expandingList()
> l$add("hello")
> l$add("world")
> l$add(101)
> l$as.list()
[[1]]
[1] "hello"

[[2]]
[1] "world"

[[3]]
[1] 101

这些解决方案可以扩展为完整的对象,这些对象本身可以支持与列表相关的所有操作,但对于读者来说仍然是一种练习。

命名列表的另一个变体:

namedExpandingList <- function(capacity = 10) {
    buffer <- vector('list', capacity)
    names <- character(capacity)
    length <- 0

    methods <- list()

    methods$double.size <- function() {
        buffer <<- c(buffer, vector('list', capacity))
        names <<- c(names, character(capacity))
        capacity <<- capacity * 2
    }

    methods$add <- function(name, val) {
        if(length == capacity) {
            methods$double.size()
        }

        length <<- length + 1
        buffer[[length]] <<- val
        names[length] <<- name
    }

    methods$as.list <- function() {
        b <- buffer[0:length]
        names(b) <- names[0:length]
        return(b)
    }

    methods
}

基准测试

使用@phonetagger的代码(基于@Cron Arconis的代码)进行性能比较。我还添加了一个,better_env_as_container并更改env_as_container_了一点。原始文件env_as_container_已损坏,实际上并未存储所有数字。

library(microbenchmark)
lPtrAppend <- function(lstptr, lab, obj) {lstptr[[deparse(lab)]] <- obj}
### Store list inside new environment
envAppendList <- function(lstptr, obj) {lstptr$list[[length(lstptr$list)+1]] <- obj} 
env2list <- function(env, len) {
    l <- vector('list', len)
    for (i in 1:len) {
        l[[i]] <- env[[as.character(i)]]
    }
    l
}
envl2list <- function(env, len) {
    l <- vector('list', len)
    for (i in 1:len) {
        l[[i]] <- env[[paste(as.character(i), 'L', sep='')]]
    }
    l
}
runBenchmark <- function(n) {
    microbenchmark(times = 5,  
        env_with_list_ = {
            listptr <- new.env(parent=globalenv())
            listptr$list <- NULL
            for(i in 1:n) {envAppendList(listptr, i)}
            listptr$list
        },
        c_ = {
            a <- list(0)
            for(i in 1:n) {a = c(a, list(i))}
        },
        list_ = {
            a <- list(0)
            for(i in 1:n) {a <- list(a, list(i))}
        },
        by_index = {
            a <- list(0)
            for(i in 1:n) {a[length(a) + 1] <- i}
            a
        },
        append_ = { 
            a <- list(0)    
            for(i in 1:n) {a <- append(a, i)} 
            a
        },
        env_as_container_ = {
            listptr <- new.env(hash=TRUE, parent=globalenv())
            for(i in 1:n) {lPtrAppend(listptr, i, i)} 
            envl2list(listptr, n)
        },
        better_env_as_container = {
            env <- new.env(hash=TRUE, parent=globalenv())
            for(i in 1:n) env[[as.character(i)]] <- i
            env2list(env, n)
        },
        linkedList = {
            a <- linkedList()
            for(i in 1:n) { a$add(i) }
            a$as.list()
        },
        inlineLinkedList = {
            a <- list()
            for(i in 1:n) { a <- list(a, i) }
            b <- vector('list', n)
            head <- a
            for(i in n:1) {
                b[[i]] <- head[[2]]
                head <- head[[1]]
            }                
        },
        expandingList = {
            a <- expandingList()
            for(i in 1:n) { a$add(i) }
            a$as.list()
        },
        inlineExpandingList = {
            l <- vector('list', 10)
            cap <- 10
            len <- 0
            for(i in 1:n) {
                if(len == cap) {
                    l <- c(l, vector('list', cap))
                    cap <- cap*2
                }
                len <- len + 1
                l[[len]] <- i
            }
            l[1:len]
        }
    )
}

# We need to repeatedly add an element to a list. With normal list concatenation
# or element setting this would lead to a large number of memory copies and a
# quadratic runtime. To prevent that, this function implements a bare bones
# expanding array, in which list appends are (amortized) constant time.
    expandingList <- function(capacity = 10) {
        buffer <- vector('list', capacity)
        length <- 0

        methods <- list()

        methods$double.size <- function() {
            buffer <<- c(buffer, vector('list', capacity))
            capacity <<- capacity * 2
        }

        methods$add <- function(val) {
            if(length == capacity) {
                methods$double.size()
            }

            length <<- length + 1
            buffer[[length]] <<- val
        }

        methods$as.list <- function() {
            b <- buffer[0:length]
            return(b)
        }

        methods
    }

    linkedList <- function() {
        head <- list(0)
        length <- 0

        methods <- list()

        methods$add <- function(val) {
            length <<- length + 1
            head <<- list(head, val)
        }

        methods$as.list <- function() {
            b <- vector('list', length)
            h <- head
            for(i in length:1) {
                b[[i]] <- head[[2]]
                head <- head[[1]]
            }
            return(b)
        }

        methods
    }

# We need to repeatedly add an element to a list. With normal list concatenation
# or element setting this would lead to a large number of memory copies and a
# quadratic runtime. To prevent that, this function implements a bare bones
# expanding array, in which list appends are (amortized) constant time.
    namedExpandingList <- function(capacity = 10) {
        buffer <- vector('list', capacity)
        names <- character(capacity)
        length <- 0

        methods <- list()

        methods$double.size <- function() {
            buffer <<- c(buffer, vector('list', capacity))
            names <<- c(names, character(capacity))
            capacity <<- capacity * 2
        }

        methods$add <- function(name, val) {
            if(length == capacity) {
                methods$double.size()
            }

            length <<- length + 1
            buffer[[length]] <<- val
            names[length] <<- name
        }

        methods$as.list <- function() {
            b <- buffer[0:length]
            names(b) <- names[0:length]
            return(b)
        }

        methods
    }

结果:

> runBenchmark(1000)
Unit: microseconds
                    expr       min        lq      mean    median        uq       max neval
          env_with_list_  3128.291  3161.675  4466.726  3361.837  3362.885  9318.943     5
                      c_  3308.130  3465.830  6687.985  8578.913  8627.802  9459.252     5
                   list_   329.508   343.615   389.724   370.504   449.494   455.499     5
                by_index  3076.679  3256.588  5480.571  3395.919  8209.738  9463.931     5
                 append_  4292.321  4562.184  7911.882 10156.957 10202.773 10345.177     5
       env_as_container_ 24471.511 24795.849 25541.103 25486.362 26440.591 26511.200     5
 better_env_as_container  7671.338  7986.597  8118.163  8153.726  8335.659  8443.493     5
              linkedList  1700.754  1755.439  1829.442  1804.746  1898.752  1987.518     5
        inlineLinkedList  1109.764  1115.352  1163.751  1115.631  1206.843  1271.166     5
           expandingList  1422.440  1439.970  1486.288  1519.728  1524.268  1525.036     5
     inlineExpandingList   942.916   973.366  1002.461  1012.197  1017.784  1066.044     5
> runBenchmark(10000)
Unit: milliseconds
                    expr        min         lq       mean     median         uq        max neval
          env_with_list_ 357.760419 360.277117 433.810432 411.144799 479.090688 560.779139     5
                      c_ 685.477809 734.055635 761.689936 745.957553 778.330873 864.627811     5
                   list_   3.257356   3.454166   3.505653   3.524216   3.551454   3.741071     5
                by_index 445.977967 454.321797 515.453906 483.313516 560.374763 633.281485     5
                 append_ 610.777866 629.547539 681.145751 640.936898 760.570326 763.896124     5
       env_as_container_ 281.025606 290.028380 303.885130 308.594676 314.972570 324.804419     5
 better_env_as_container  83.944855  86.927458  90.098644  91.335853  92.459026  95.826030     5
              linkedList  19.612576  24.032285  24.229808  25.461429  25.819151  26.223597     5
        inlineLinkedList  11.126970  11.768524  12.216284  12.063529  12.392199  13.730200     5
           expandingList  14.735483  15.854536  15.764204  16.073485  16.075789  16.081726     5
     inlineExpandingList  10.618393  11.179351  13.275107  12.391780  14.747914  17.438096     5
> runBenchmark(20000)
Unit: milliseconds
                    expr         min          lq       mean      median          uq         max neval
          env_with_list_ 1723.899913 1915.003237 1921.23955 1938.734718 1951.649113 2076.910767     5
                      c_ 2759.769353 2768.992334 2810.40023 2820.129738 2832.350269 2870.759474     5
                   list_    6.112919    6.399964    6.63974    6.453252    6.910916    7.321647     5
                by_index 2163.585192 2194.892470 2292.61011 2209.889015 2436.620081 2458.063801     5
                 append_ 2832.504964 2872.559609 2983.17666 2992.634568 3004.625953 3213.558197     5
       env_as_container_  573.386166  588.448990  602.48829  597.645221  610.048314  642.912752     5
 better_env_as_container  154.180531  175.254307  180.26689  177.027204  188.642219  206.230191     5
              linkedList   38.401105   47.514506   46.61419   47.525192   48.677209   50.952958     5
        inlineLinkedList   25.172429   26.326681   32.33312   34.403442   34.469930   41.293126     5
           expandingList   30.776072   30.970438   34.45491   31.752790   38.062728   40.712542     5
     inlineExpandingList   21.309278   22.709159   24.64656   24.290694   25.764816   29.158849     5

我添加了linkedListexpandingList两者的内联版本。的inlinedLinkedList基本上是一个副本list_,但它也转换嵌套结构回一个普通的列表。除此之外,内联和非内联版本之间的差异还归因于函数调用的开销。

的所有变体expandingListlinkedList表演O(1)追加的性能,与基准时间与附加的项目数线性扩展。linkedList比慢expandingList,并且函数调用开销也可见。因此,如果您确实需要所有可能的速度(并希望坚持使用R代码),请使用的内联版本expandingList

我还研究了R的C实现,并且两种方法都应在任何大小的情况下都附加O(1),直到用完内存。

我还进行了更改env_as_container_,原始版本将在索引“ i”下存储每个项目,覆盖先前附加的项目。在better_env_as_container我加入非常相似,env_as_container_但没有deparse东西。两者都具有O(1)性能,但是它们的开销比链接/扩展列表大得多。

内存开销

在CR实施中,每个分配的对象的开销为4个字和2个整数。该linkedList方法为每个附加项分配一个长度为2的列表,在64位计算机上,每个附加项总共(4 * 8 + 4 + 4 + 2 * 8 =)56个字节(不包括内存分配开销,因此可能接近64个字节)。该expandingList方法每个附加项使用一个字,在向量长度加倍时加上一个副本,因此每个项的总内存使用量最多为16个字节。由于内存全部位于一个或两个对象中,因此每个对象的开销微不足道。我尚未深入研究env内存使用情况,但我认为它会更接近linkedList


如果列表选项不能解决我们要解决的问题,那么保留列表选项有什么意义呢?
Picarus

1
@Picarus我不确定您的意思。为什么我将其保留在基准中?作为与其他选项的比较。该list_选项速度更快,并且在不需要转换为普通列表时(即,将结果用作堆栈时)很有用。
JanKanis

@Gabor Csardi在stackoverflow.com/a/29482211/264177上发布了一个更快的方法,可以将环境转换回列表中的另一个问题。我在系统上也进行了基准测试。它的速度约为best_env_as_container的两倍,但仍然比linkedList和expandingList慢。
JanKanis

对于某些应用程序,深层嵌套(n = 99999)列表似乎是可管理和可容忍的:任何人都想对nestoR进行基准测试?(我environment对nestoR所用的内容仍然有些不了解。)我的瓶颈几乎总是花在编写代码和进行数据分析上的时间,但是我欣赏在本文中找到的基准。至于内存开销,我不介意我的应用程序每个节点大约kB。我坚持到大阵列等
安娜雨云

17

在Lisp中,我们是这样进行的:

> l <- c(1)
> l <- c(2, l)
> l <- c(3, l)
> l <- rev(l)
> l
[1] 1 2 3

尽管它是“缺点”,而不仅仅是“ c”。如果您需要以空列表开头,请使用l <-NULL。


3
优秀的!所有其他解决方案都返回一些奇怪的列表。
metakermit

4
在Lisp中,在列表前面是O(1)操作,而在O(n)@flies中附加运行。性能提升远远超过了对还原的需求。在R中不是这种情况。甚至在成对列表中也没有,后者通常最类似于列表列表。
Palec'3

@Palec“在R中不是这种情况”-我不确定您指的是哪个“ this”。您是说附加不是O(1)还是不是O(n)?
飞行

1
我的意思是,如果您使用Lisp进行编码,则@flies会导致效率低下。那句话是为了解释为什么答案是这样写的。在R中,这两种方法在性能方面都是AFAIK。但是现在我不确定摊销的复杂性。自从撰写我之前的评论以来,还没有碰过R。
Palec 2015年

3
在R中,此方法将为O(n)。该c()函数将其参数复制到新的向量/列表中并返回。
JanKanis

6

您想要这样的东西吗?

> push <- function(l, x) {
   lst <- get(l, parent.frame())
   lst[length(lst)+1] <- x
   assign(l, lst, envir=parent.frame())
 }
> a <- list(1,2)
> push('a', 6)
> a
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 6

这不是一个很有礼貌的功能(分配给parent.frame()您很粗鲁),但是IIUYC正是您要的功能。


6

我对这里提到的方法做了一个小的比较。

n = 1e+4
library(microbenchmark)
### Using environment as a container
lPtrAppend <- function(lstptr, lab, obj) {lstptr[[deparse(substitute(lab))]] <- obj}
### Store list inside new environment
envAppendList <- function(lstptr, obj) {lstptr$list[[length(lstptr$list)+1]] <- obj} 

microbenchmark(times = 5,  
        env_with_list_ = {
            listptr <- new.env(parent=globalenv())
            listptr$list <- NULL
            for(i in 1:n) {envAppendList(listptr, i)}
            listptr$list
        },
        c_ = {
            a <- list(0)
            for(i in 1:n) {a = c(a, list(i))}
        },
        list_ = {
            a <- list(0)
            for(i in 1:n) {a <- list(a, list(i))}
        },
        by_index = {
            a <- list(0)
            for(i in 1:n) {a[length(a) + 1] <- i}
            a
        },
        append_ = { 
            a <- list(0)    
            for(i in 1:n) {a <- append(a, i)} 
            a
        },
        env_as_container_ = {
            listptr <- new.env(parent=globalenv())
            for(i in 1:n) {lPtrAppend(listptr, i, i)} 
            listptr
        }   
)

结果:

Unit: milliseconds
              expr       min        lq       mean    median        uq       max neval cld
    env_with_list_  188.9023  198.7560  224.57632  223.2520  229.3854  282.5859     5  a 
                c_ 1275.3424 1869.1064 2022.20984 2191.7745 2283.1199 2491.7060     5   b
             list_   17.4916   18.1142   22.56752   19.8546   20.8191   36.5581     5  a 
          by_index  445.2970  479.9670  540.20398  576.9037  591.2366  607.6156     5  a 
           append_ 1140.8975 1316.3031 1794.10472 1620.1212 1855.3602 3037.8416     5   b
 env_as_container_  355.9655  360.1738  399.69186  376.8588  391.7945  513.6667     5  a 

这是非常重要的信息:永远不会猜到list = list不仅是获胜者-而且是1到2个订单或数量级!
javadba

5

如果将list变量作为带引号的字符串传递,则可以从以下函数中访问它:

push <- function(l, x) {
  assign(l, append(eval(as.name(l)), x), envir=parent.frame())
}

所以:

> a <- list(1,2)
> a
[[1]]
[1] 1

[[2]]
[1] 2

> push("a", 3)
> a
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

> 

或额外的信用:

> v <- vector()
> push("v", 1)
> v
[1] 1
> push("v", 2)
> v
[1] 1 2
> 

1
这基本上是我想要的行为,但是它仍然在内部调用append,导致O(n ^ 2)性能。
尼克

4

不知道为什么您不认为第一种方法不起作用。您在lappend函数中有一个错误:length(list)应该是length(lst)。这可以正常工作并返回带有附加obj的列表。


3
你是绝对正确的。代码中有一个错误,我已修复。我已经测试了lappend()我提供的代码,它似乎和c()和append()一样好,它们都表现出O(n ^ 2)行为。
尼克,2010年


2

我认为您实际上想要通过引用(指针)传递给函数-创建一个新环境(通过引用传递给函数)并添加列表:

listptr=new.env(parent=globalenv())
listptr$list=mylist

#Then the function is modified as:
lPtrAppend <- function(lstptr, obj) {
    lstptr$list[[length(lstptr$list)+1]] <- obj
}

现在,您仅修改现有列表(而不创建新列表)


1
这似乎又具有二次时间复杂度。问题显然是列表/向量调整大小没有以大多数语言通常实现的方式实现。
2011年

是的-看起来最后的追加非常慢-可能b / c列表是递归的,并且R最适合矢量运算而不是循环类型运算。它最好做的是:
DavidM 2011年

1
system.time(for(i in c(1:10000)mylist [i] = i)(几秒钟),或者更好的做法是一次完成所有操作:system.time(mylist = list(1:100000)) (小于一秒),然后修改与for循环预分配的列表也将更快。
DavidM

2

这是将项目添加到R列表的直接方法:

# create an empty list:
small_list = list()

# now put some objects in it:
small_list$k1 = "v1"
small_list$k2 = "v2"
small_list$k3 = 1:10

# retrieve them the same way:
small_list$k1
# returns "v1"

# "index" notation works as well:
small_list["k2"]

或以编程方式:

kx = paste(LETTERS[1:5], 1:5, sep="")
vx = runif(5)
lx = list()
cn = 1

for (itm in kx) { lx[itm] = vx[cn]; cn = cn + 1 }

print(length(lx))
# returns 5

这不是真正的附加。如果我有100个对象,并且想以编程方式将它们添加到列表中怎么办?R有一个append()函数,但实际上是一个连接函数,仅适用于向量。
尼克

append()适用于向量和列表,它是一个真正的追加(与串联基本相同,所以我不知道您的问题是什么)
hadley 2010年

8
追加函数应更改现有对象,而不创建新对象。真正的追加将不具有O(N ^ 2)行为。
尼克

2

实际上,有一个具有c()功能的子对象。如果您这样做:

x <- list()
x <- c(x,2)
x = c(x,"foo")

您将获得预期的结果:

[[1]]
[1]

[[2]]
[1] "foo"

但是,如果您使用添加矩阵x <- c(x, matrix(5,2,2),您的列表将具有另外4个价值要素5!您最好这样做:

x <- c(x, list(matrix(5,2,2))

它适用于任何其他对象,您将获得预期的效果:

[[1]]
[1]

[[2]]
[1] "foo"

[[3]]
     [,1] [,2]
[1,]    5    5
[2,]    5    5

最后,您的函数变为:

push <- function(l, ...) c(l, list(...))

它适用于任何类型的对象。您可以变得更聪明,并执行以下操作:

push_back <- function(l, ...) c(l, list(...))
push_front <- function(l, ...) c(list(...), l)

1

也有list.appendrlist链接文档

require(rlist)
LL <- list(a="Tom", b="Dick")
list.append(LL,d="Pam",f=c("Joe","Ann"))

这是非常简单和高效的。


1
在我看来不像R ... Python吗?
JD

1
我进行了修改并进行了尝试:速度太慢了。更好地利用c()list-方法。两者都更快。
第5次

寻找代码rlist::list.append(),它实际上是一个包装器base::c()
nbenn

1

为了进行验证,我运行了@Cron提供的基准代码。有一个主要区别(除了在较新的i7处理器上运行更快以外):by_index现在的表现几乎与list_

Unit: milliseconds
              expr        min         lq       mean     median         uq
    env_with_list_ 167.882406 175.969269 185.966143 181.817187 185.933887
                c_ 485.524870 501.049836 516.781689 518.637468 537.355953
             list_   6.155772   6.258487   6.544207   6.269045   6.290925
          by_index   9.290577   9.630283   9.881103   9.672359  10.219533
           append_ 505.046634 543.319857 542.112303 551.001787 553.030110
 env_as_container_ 153.297375 154.880337 156.198009 156.068736 156.800135

作为参考,这里是从@Cron的答案中逐字复制的基准代码(以防万一他后来更改了内容):

n = 1e+4
library(microbenchmark)
### Using environment as a container
lPtrAppend <- function(lstptr, lab, obj) {lstptr[[deparse(substitute(lab))]] <- obj}
### Store list inside new environment
envAppendList <- function(lstptr, obj) {lstptr$list[[length(lstptr$list)+1]] <- obj}

microbenchmark(times = 5,
        env_with_list_ = {
            listptr <- new.env(parent=globalenv())
            listptr$list <- NULL
            for(i in 1:n) {envAppendList(listptr, i)}
            listptr$list
        },
        c_ = {
            a <- list(0)
            for(i in 1:n) {a = c(a, list(i))}
        },
        list_ = {
            a <- list(0)
            for(i in 1:n) {a <- list(a, list(i))}
        },
        by_index = {
            a <- list(0)
            for(i in 1:n) {a[length(a) + 1] <- i}
            a
        },
        append_ = {
            a <- list(0)
            for(i in 1:n) {a <- append(a, i)}
            a
        },
        env_as_container_ = {
            listptr <- new.env(parent=globalenv())
            for(i in 1:n) {lPtrAppend(listptr, i, i)}
            listptr
        }
)

0
> LL<-list(1:4)

> LL

[[1]]
[1] 1 2 3 4

> LL<-list(c(unlist(LL),5:9))

> LL

[[1]]
 [1] 1 2 3 4 5 6 7 8 9

2
我认为这不是追加OP所需的那种。
joran 2011年

这不是在列表中追加元素。在这里,您要增加整数向量的元素,这是列表中的唯一元素。该列表只有一个元素,一个整数向量。
塞尔吉奥(Sergio)

0

这是一个非常有趣的问题,我希望下面的想法可以为解决该问题提供一种方法。此方法的确提供了没有索引的平面列表,但是它确实具有list和unlist以避免嵌套结构。我不确定速度,因为我不知道如何进行基准测试。

a_list<-list()
for(i in 1:3){
  a_list<-list(unlist(list(unlist(a_list,recursive = FALSE),list(rnorm(2))),recursive = FALSE))
}
a_list

[[1]]
[[1]][[1]]
[1] -0.8098202  1.1035517

[[1]][[2]]
[1] 0.6804520 0.4664394

[[1]][[3]]
[1] 0.15592354 0.07424637

我要添加的是它确实提供了两级嵌套列表,仅此而已。列表和取消列表工作的方式对我来说还不是很清楚,但这是通过测试代码获得的结果
xappppp

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.