tags:

views:

1441

answers:

5

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.

A: 

This is a straightforward way to add items to an R List:

# 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"]

Or programmatically:

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
doug
This isn't really appending. What if I have 100 objects and I want to append them to a list programmatically? R has an `append()` function, but it's really a concatenate function and it only works on vectors.
Nick
`append()` works on vectors and lists, and it is a true append (which is basically the same as concatenate, so I don't see what your problem is)
hadley
An append function should mutate an existing object, not create a new one. A true append would not have O(N^2) behavior.
Nick
+6  A: 

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?

Dirk Eddelbuettel
Here's a star for your work! ★
Bob Albright
This doesn't append... it concatenates. `LL` would still have two elements after `C(LL, c="harry")` is called.
Nick
Just reassign to LL: `LL <- c(LL, c="harry")`.
Dirk Eddelbuettel
I guess this is the best you can do in R. `c()` and `append()` (and the `lappend()` I've provided) all exhibit O(n^2) behavior. I was hoping for O(n lg n), which is what you would get with a dynamically growing sequence data structure in most languages, but it doesn't seem like R has that, at least built in.
Nick
+1  A: 

If you pass in the list variable as a quoted string, you can reach it from within the function like:

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

so:

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

[[2]]
[1] 2

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

[[2]]
[1] 2

[[3]]
[1] 3

> 

or for extra credit:

> v <- vector()
> push("v", 1)
> v
[1] 1
> push("v", 2)
> v
[1] 1 2
> 
ayman
This is basically the behavior that I want, however it still calls append internally, resulting in O(n^2) performance.
Nick
+1  A: 

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.

Ken Williams
+1  A: 

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.

Paul
You are absolutely right. There was a bug in the code and I've fixed it. I've tested the `lappend()` that I've provided and it seems to perform about as well as c() and append(), all of which exhibit O(n^2) behavior.
Nick