Append an object to a list in R in amortized const

2019-01-03 19:52发布

If I have some R list mylist, you can append an item obj to it like so:

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

But surely there is some more compact way. When I was new at R, I tried writing lappend() like so:

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

but of course that doesn't work due to R's call-by-name semantics (lst is effectively copied upon call, so changes to lst are not visible outside the scope of lappend(). I know you can do environment hacking in an R function to reach outside the scope of your function and mutate the calling environment, but that seems like a large hammer to write a simple append function.

Can anyone suggest a more beautiful way of doing this? Bonus points if it works for both vectors and lists.

17条回答
Melony?
2楼-- · 2019-01-03 20:15

If it's a list of string, just use the c() function :

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> 

That works on vectors too, so do I get the bonus points?

Edit (2015-Feb-01): This post is coming up on its fifth birthday. Some kind readers keep repeating any shortcomings with it, so by all means also see some of the comments below. One suggestion for list types:

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

In general, R types can make it hard to have one and just one idiom for all types and uses.

查看更多
疯言疯语
3楼-- · 2019-01-03 20:18

try this function lappend

lappend <- function (lst, ...){
  lst <- c(lst, list(...))
  return(lst)
}

and other suggestions from this page Add named vector to a list

Bye.

查看更多
混吃等死
4楼-- · 2019-01-03 20:19

There is also list.append from the rlist (link to the documentation)

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

It's very simple and efficient.

查看更多
来,给爷笑一个
5楼-- · 2019-01-03 20:20

You want something like this maybe?

> 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

It's not a very polite function (assigning to parent.frame() is kind of rude) but IIUYC it's what you're asking for.

查看更多
Evening l夕情丶
6楼-- · 2019-01-03 20:24

Not sure why you don't think your first method won't work. You have a bug in the lappend function: length(list) should be length(lst). This works fine and returns a list with the appended obj.

查看更多
7楼-- · 2019-01-03 20:24

For validation I ran the benchmark code provided by @Cron. There is one major difference (in addition to running faster on the newer i7 processor): the by_index now performs nearly as well as the 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

For reference here is the benchmark code copied verbatim from @Cron's answer (just in case he later changes the contents):

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
        }
)
查看更多
登录 后发表回答