Nối một đối tượng vào danh sách trong R trong thời gian không đổi được khấu hao, O (1)?


245

Nếu tôi có một số danh sách R mylist, bạn có thể nối thêm một mục objvào đó như sau:

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

Nhưng chắc chắn có một số cách nhỏ gọn hơn. Khi tôi mới ở R, tôi đã thử viết lappend()như vậy:

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

nhưng tất nhiên điều đó không hoạt động do ngữ nghĩa của tên gọi R ( lstđược sao chép một cách hiệu quả khi gọi, vì vậy những thay đổi lstkhông thể nhìn thấy ngoài phạm vi lappend(). Tôi biết bạn có thể thực hiện hack môi trường trong hàm R để tiếp cận bên ngoài phạm vi chức năng của bạn và làm thay đổi môi trường gọi, nhưng dường như đó là một cái búa lớn để viết một chức năng chắp thêm đơn giản.

Bất cứ ai có thể đề nghị một cách đẹp hơn để làm điều này? Điểm thưởng nếu nó hoạt động cho cả vectơ và danh sách.


5
R có các đặc điểm dữ liệu bất biến thường được tìm thấy trong các ngôn ngữ chức năng, không thích nói điều này, nhưng tôi nghĩ bạn chỉ cần phải đối phó với nó. Nó có ưu và nhược điểm của nó
Dan

Khi bạn nói "gọi theo tên", bạn thực sự có nghĩa là "gọi theo giá trị", phải không?
Ken Williams

7
Không, đó chắc chắn không phải là cuộc gọi theo giá trị, nếu không điều này sẽ không thành vấn đề. R thực sự sử dụng lệnh gọi theo nhu cầu ( en.wikipedia.org/wiki/Evalval_strargety#Call_by_need ).
Nick

4
Một ý tưởng hay là phân bổ trước vectơ / danh sách của bạn: N = 100 mylist = vector ('list', N) cho (i in 1: N) {#mylist [[i]] = ...} Tránh 'phát triển 'các đối tượng trong R.
Fernando

Tôi vô tình tìm thấy câu trả lời ở đây, stackoverflow.com/questions/17046336/ Kiếm Thật khó để thực hiện thuật toán dễ dàng như vậy!
KH Kim

Câu trả lời:


255

Nếu đó là danh sách chuỗi, chỉ cần sử dụng c()hàm:

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> 

Điều đó cũng hoạt động trên các vectơ, vậy tôi có nhận được điểm thưởng không?

Chỉnh sửa (2015-Feb-01): Bài đăng này sắp ra mắt vào sinh nhật thứ năm của nó. Một số độc giả tiếp tục lặp lại bất kỳ thiếu sót nào với nó, vì vậy bằng mọi cách cũng thấy một số ý kiến ​​dưới đây. Một gợi ý cho listcác loại:

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

Nói chung, các loại R có thể làm cho khó có một và chỉ một thành ngữ cho tất cả các loại và sử dụng.


19
Điều này không nối thêm ... nó nối. LLvẫn sẽ có hai yếu tố sau khi C(LL, c="harry")được gọi.
Nick

27
Chỉ cần gán lại cho LL : LL <- c(LL, c="harry").
Dirk Eddelbuettel

51
Điều này chỉ hoạt động với chuỗi. Nếu a, b và c là các vectơ số nguyên thì hành vi hoàn toàn khác nhau.
Alexandre Rademaker

8
@Dirk: Bạn có các parens lồng nhau khác với tôi. Cuộc gọi của tôi c()có 2 đối số: danh sách tôi đang cố gắng thêm vào, cụ thể là list(a=3, b=c(4, 5)), và mục tôi đang cố gắng thêm vào, cụ thể là c=c(6, 7). Nếu bạn sử dụng phương pháp của tôi, bạn sẽ thấy rằng 2 mục danh sách được nối thêm ( 67, với tên c1c2) thay vì một vectơ 2 phần tử có tên cnhư dự định rõ ràng!
j_random_hacker

7
Vậy là kết luận mylist <- list(mylist, list(obj))? Nếu có, thật tốt khi sửa đổi câu trả lời
Matthew

96

OP (trong bản sửa đổi cập nhật của câu hỏi được cập nhật vào tháng 4 năm 2012) quan tâm đến việc liệu có cách nào để thêm vào danh sách trong thời gian không đổi được khấu hao hay không, chẳng hạn như có thể được thực hiện với bộ vector<>chứa C ++ . Câu trả lời tốt nhất ở đây cho đến nay chỉ hiển thị thời gian thực hiện tương đối cho các giải pháp khác nhau được đưa ra một vấn đề có kích thước cố định, nhưng không giải quyết trực tiếp bất kỳ hiệu quả thuật toán nào của các giải pháp khác nhau . Bình luận bên dưới nhiều câu trả lời thảo luận về hiệu quả thuật toán của một số giải pháp, nhưng trong mọi trường hợp cho đến nay (kể từ tháng 4 năm 2015), họ đi đến kết luận sai.

Hiệu quả thuật toán nắm bắt các đặc điểm tăng trưởng, theo thời gian (thời gian thực hiện) hoặc không gian (lượng bộ nhớ tiêu thụ) khi kích thước vấn đề tăng lên . Chạy thử nghiệm hiệu năng cho các giải pháp khác nhau được đưa ra một vấn đề kích thước cố định không giải quyết được tốc độ tăng trưởng của các giải pháp khác nhau. OP quan tâm đến việc biết liệu có cách nào để nối các đối tượng vào danh sách R trong "thời gian không đổi được khấu hao" hay không. Điều đó nghĩa là gì? Để giải thích, trước tiên hãy để tôi mô tả "thời gian không đổi":

  • Liên tục hoặc O (1) tăng trưởng:

    Nếu thời gian cần thiết để thực hiện một nhiệm vụ nhất định vẫn bằng với kích thước của vấn đề tăng gấp đôi , thì chúng tôi nói thuật toán thể hiện sự tăng trưởng thời gian không đổi , hoặc được nêu trong ký hiệu "Big O", thể hiện sự tăng trưởng thời gian O (1). Khi OP nói thời gian không đổi "khấu hao", anh ta chỉ có nghĩa là "về lâu dài" ... tức là, nếu thực hiện một thao tác đôi khi mất nhiều thời gian hơn bình thường (ví dụ: nếu bộ đệm preallocated bị cạn kiệt và đôi khi yêu cầu thay đổi kích thước thành lớn hơn kích thước bộ đệm), miễn là hiệu suất trung bình dài hạn là thời gian không đổi, chúng ta vẫn sẽ gọi nó là O (1).

    Để so sánh, tôi cũng sẽ mô tả "thời gian tuyến tính" và "thời gian bậc hai":

  • Tăng trưởng tuyến tính hoặc O (n) :

    Nếu thời gian cần thiết để thực hiện một nhiệm vụ nhất định tăng gấp đôi khi kích thước của vấn đề tăng gấp đôi , thì chúng tôi nói thuật toán thể hiện thời gian tuyến tính hoặc tăng trưởng O (n) .

  • Tăng trưởng bậc hai hoặc O (n 2 ) :

    Nếu thời gian cần thiết để thực hiện một tác vụ nhất định tăng theo bình phương của kích thước bài toán , thì chúng tôi nói rằng thuật toán thể hiện thời gian bậc hai hoặc tăng trưởng O (n 2 ) .

Có nhiều lớp hiệu quả khác của thuật toán; Tôi trì hoãn bài viết Wikipedia để thảo luận thêm.

Tôi cảm ơn @CronAcronis vì câu trả lời của anh ấy, vì tôi mới biết về R và thật tuyệt khi có một khối mã được xây dựng đầy đủ để thực hiện phân tích hiệu suất của các giải pháp khác nhau được trình bày trên trang này. Tôi đang mượn mã của anh ấy để phân tích, cái mà tôi nhân đôi (được bọc trong một hàm) bên dưới:

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

Các kết quả được đăng bởi @CronAcronis dường như cho thấy a <- list(a, list(i))phương pháp này là nhanh nhất, ít nhất là đối với kích thước vấn đề 10000, nhưng kết quả cho một kích thước vấn đề duy nhất không giải quyết được sự tăng trưởng của giải pháp. Để làm được điều đó, chúng ta cần chạy tối thiểu hai bài kiểm tra định hình, với các kích cỡ bài toán khác nhau:

> 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
> 

Trước hết, một từ về các giá trị min / lq / mean / median / uq / max: Vì chúng tôi đang thực hiện cùng một nhiệm vụ chính xác cho mỗi 5 lần chạy, trong một thế giới lý tưởng, chúng tôi có thể hy vọng rằng nó sẽ diễn ra giống hệt nhau lượng thời gian cho mỗi lần chạy. Nhưng lần chạy đầu tiên thường thiên về thời gian dài hơn do thực tế là mã chúng tôi đang kiểm tra chưa được tải vào bộ đệm của CPU. Sau lần chạy đầu tiên, chúng tôi hy vọng thời gian sẽ khá nhất quán, nhưng đôi khi mã của chúng tôi có thể bị xóa khỏi bộ đệm do các ngắt đánh dấu thời gian hoặc các ngắt phần cứng khác không liên quan đến mã chúng tôi đang kiểm tra. Bằng cách kiểm tra đoạn mã 5 lần, chúng tôi sẽ cho phép mã được tải vào bộ đệm trong lần chạy đầu tiên và sau đó cho mỗi đoạn mã 4 cơ hội để chạy đến hoàn thành mà không bị can thiệp từ các sự kiện bên ngoài. Vì lý do này,

Lưu ý rằng tôi đã chọn chạy lần đầu tiên với kích thước sự cố là 2000 và sau đó là 20000, vì vậy kích thước sự cố của tôi tăng thêm 10 lần từ lần chạy đầu tiên đến lần thứ hai.

Hiệu suất của listgiải pháp: O (1) (thời gian không đổi)

Trước tiên chúng ta hãy xem sự phát triển của listgiải pháp, vì chúng ta có thể biết ngay rằng đó là giải pháp nhanh nhất trong cả hai lần chạy hồ sơ: Trong lần chạy đầu tiên, phải mất 854 micro giây (0,854 mili giây) để thực hiện 2000 nhiệm vụ "nối thêm". Trong lần chạy thứ hai, phải mất 8,746 mili giây để thực hiện 20000 tác vụ "nối thêm". Một nhà quan sát ngây thơ sẽ nói, "Ah, listgiải pháp cho thấy sự tăng trưởng O (n), vì kích thước vấn đề tăng lên gấp 10 lần, do đó, thời gian cần thiết để thực hiện thử nghiệm." Vấn đề với phân tích đó là những gì OP muốn là tốc độ tăng trưởng của một đối tượng chèn , chứ không phải tốc độ tăng trưởng của vấn đề tổng thể. Biết rằng, rõ ràng làlist giải pháp cung cấp chính xác những gì OP muốn: một phương thức nối các đối tượng vào danh sách trong thời gian O (1).

Hiệu suất của các giải pháp khác

Không có giải pháp nào khác đến gần với tốc độ của listgiải pháp, nhưng dù sao cũng nên kiểm tra chúng:

Hầu hết các giải pháp khác dường như là O (n) trong hiệu suất. Ví dụ: by_indexgiải pháp, một giải pháp rất phổ biến dựa trên tần suất tôi tìm thấy nó trong các bài viết SO khác, mất 11,6 mili giây để nối thêm 2000 đối tượng và 953 mili giây để nối thêm mười lần nhiều đối tượng. Thời gian của vấn đề tổng thể tăng lên gấp 100 lần, vì vậy một người quan sát ngây thơ có thể nói "Ah, by_indexgiải pháp thể hiện sự tăng trưởng O (n 2 ), vì khi quy mô vấn đề tăng lên gấp 10 lần, thời gian cần thiết để thực hiện thử nghiệm tăng lên theo hệ số 100. "Như trước đây, phân tích này là thiếu sót, vì OP quan tâm đến sự tăng trưởng của một đối tượng chèn. Nếu chúng ta chia tăng trưởng thời gian chung cho tăng trưởng kích thước của vấn đề, chúng ta thấy rằng sự tăng trưởng thời gian của các đối tượng nối thêm tăng theo hệ số chỉ 10, không phải là 100, phù hợp với sự tăng trưởng của quy mô vấn đề, vì vậy by_indexgiải pháp là O (n). Không có giải pháp nào được liệt kê trong đó thể hiện sự tăng trưởng O (n 2 ) để nối thêm một đối tượng.


1
Đối với độc giả: Xin vui lòng đọc câu trả lời của JanKanis, cung cấp một phần mở rộng rất thực tế cho những phát hiện của tôi ở trên, và tìm hiểu một chút về các giải pháp khác nhau được đưa ra trong hoạt động nội bộ của việc triển khai C của R.
phonetagger

4
Không chắc chắn tùy chọn danh sách thực hiện những gì được yêu cầu:> length (c (c (c (list (1)), list (2)), list (3))) [1] 3> length (list (list (list) (danh sách (1)), danh sách (2)), danh sách (3))) [1] 2. Trông giống như danh sách lồng nhau.
Picarus

@Picarus - Tôi nghĩ bạn đúng. Tôi không còn làm việc với R nữa, nhưng rất may JanKanis đã đăng câu trả lời với giải pháp O (1) hữu ích hơn nhiều và ghi chú vấn đề bạn đã xác định. Tôi chắc chắn JanKanis sẽ đánh giá cao upvote của bạn.
âm vị

@phonetagger, bạn nên chỉnh sửa câu trả lời của mình. Không phải ai cũng sẽ đọc tất cả các câu trả lời.
Picarus

"Không một câu trả lời nào đã giải quyết được câu hỏi thực tế" -> Vấn đề là câu hỏi ban đầu không phải là về độ phức tạp của thuật toán, hãy xem các phiên bản của câu hỏi. OP đã hỏi trước tiên làm thế nào để nối một phần tử trong danh sách, hơn, vài tháng sau, anh ấy đã thay đổi câu hỏi.
Carlos Cinelli 7/03/2016

41

Trong các câu trả lời khác, chỉ có listcách tiếp cận dẫn đến kết quả là O (1), nhưng nó dẫn đến một cấu trúc danh sách được lồng sâu và không phải là một danh sách đơn giản. Tôi đã sử dụng các cơ sở dữ liệu dưới đây, chúng hỗ trợ các phần bổ sung O (1) (khấu hao) và cho phép kết quả được chuyển đổi trở lại danh sách đơn giản.

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
}

Sử dụng chúng như sau:

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

[[2]]
[1] "world"

[[3]]
[1] 101

Các giải pháp này có thể được mở rộng thành các đối tượng đầy đủ hỗ trợ các hoạt động liên quan đến danh sách al, nhưng đó sẽ vẫn là một bài tập cho người đọc.

Một biến thể khác cho một danh sách được đặt tên:

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
}

Điểm chuẩn

So sánh hiệu suất bằng mã của @ phonetagger (dựa trên mã của @Cron Arconis). Tôi cũng đã thêm một better_env_as_containervà thay đổi env_as_container_một chút. Bản gốc env_as_container_đã bị hỏng và không thực sự lưu trữ tất cả các số.

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
    }

kết quả:

> 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

Tôi đã thêm linkedListexpandingListmột phiên bản nội tuyến của cả hai. Các inlinedLinkedListcơ bản là một bản sao của list_, nhưng nó cũng chuyển đổi các cấu trúc lồng nhau lại thành một danh sách đơn giản. Ngoài ra, sự khác biệt giữa các phiên bản nội tuyến và không nội tuyến là do chi phí hoạt động của các lệnh gọi.

Tất cả các biến thể của expandingListlinkedListhiển thị hiệu suất nối thêm O (1), với thời gian chuẩn được chia tỷ lệ tuyến tính với số lượng vật phẩm được nối thêm. linkedListchậm hơn expandingListvà chức năng gọi qua chức năng cũng hiển thị. Vì vậy, nếu bạn thực sự cần tất cả tốc độ bạn có thể nhận được (và muốn bám vào mã R), hãy sử dụng phiên bản nội tuyến expandingList.

Tôi cũng đã xem xét việc triển khai C của R và cả hai cách tiếp cận nên nối thêm O (1) cho bất kỳ kích thước nào cho đến khi bạn hết bộ nhớ.

Tôi cũng đã thay đổi env_as_container_, phiên bản gốc sẽ lưu trữ mọi mục theo chỉ mục "i", ghi đè lên mục được thêm vào trước đó. Các better_env_as_containertôi đã thêm rất giống với env_as_container_nhưng không có deparsethứ. Cả hai đều thể hiện hiệu suất O (1), nhưng chúng có tổng phí lớn hơn một chút so với danh sách được liên kết / mở rộng.

Bộ nhớ trên cao

Trong triển khai CR có tổng cộng 4 từ và 2 int cho mỗi đối tượng được phân bổ. Cách linkedListtiếp cận phân bổ một danh sách độ dài hai cho mỗi lần nối, với tổng số (4 * 8 + 4 + 4 + 2 * 8 =) 56 byte cho mỗi mục được nối trên máy tính 64 bit (không bao gồm chi phí cấp phát bộ nhớ, nên có thể gần hơn với 64 byte). Cách expandingListtiếp cận sử dụng một từ cho mỗi mục được nối thêm, cộng với một bản sao khi nhân đôi độ dài vectơ, do đó tổng mức sử dụng bộ nhớ lên tới 16 byte cho mỗi mục. Vì bộ nhớ là tất cả trong một hoặc hai đối tượng, nên chi phí cho mỗi đối tượng là không đáng kể. Tôi đã không nhìn sâu vào việc envsử dụng bộ nhớ, nhưng tôi nghĩ nó sẽ gần hơn linkedList.


điểm giữ tùy chọn danh sách là gì nếu nó không giải quyết được vấn đề chúng ta đang cố gắng giải quyết?
Picarus

1
@Picarus Tôi không chắc ý của bạn là gì. Tại sao tôi giữ nó trong điểm chuẩn? Khi so sánh với các tùy chọn khác. Các list_tùy chọn là nhanh hơn và có thể hữu ích nếu bạn không cần phải chuyển đổi vào một danh sách bình thường, tức là nếu bạn sử dụng kết quả như một chồng.
JanKanis

@Gabor Csardi đã đăng một cách nhanh hơn để chuyển đổi môi trường trở lại danh sách trong một câu hỏi khác tại stackoverflow.com/a/29482211/264177. Tôi đã điểm chuẩn đó cũng như trên hệ thống của tôi. Nó nhanh hơn gấp đôi so với better_env_as_container nhưng vẫn chậm hơn so với linkList và extendList.
JanKanis

Danh sách lồng nhau sâu (n = 99999) dường như có thể quản lý và chấp nhận được đối với các ứng dụng nhất định: Bất kỳ ai cũng muốn điểm chuẩn nestoR ? (Tôi vẫn còn một chút gì đó về những environmentthứ tôi đã sử dụng cho nestoR.) Nút cổ chai của tôi hầu như luôn luôn dành thời gian của con người dành cho việc mã hóa và phân tích dữ liệu, nhưng tôi đánh giá cao các điểm chuẩn tôi đã tìm thấy trên bài đăng này. Đối với chi phí bộ nhớ, tôi sẽ không bận tâm đến khoảng kB trên mỗi nút cho các ứng dụng của mình. Tôi giữ các mảng lớn, v.v.
Ana Nimbus

17

Trong Lisp, chúng tôi đã làm theo cách này:

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

mặc dù đó là 'khuyết điểm', không chỉ là 'c'. Nếu bạn cần bắt đầu với một danh sách empy, hãy sử dụng l <- NULL.


3
Thông minh! Tất cả các giải pháp khác trả về một số danh sách kỳ lạ của danh sách.
metakermit

4
Trong Lisp, việc thêm vào danh sách là thao tác O (1), trong khi nối thêm chạy trong O (n), @fly. Nhu cầu đảo ngược lớn hơn hiệu suất đạt được. Đây không phải là trường hợp trong R. Ngay cả trong danh sách cặp, thường giống với Danh sách danh sách nhất.
Palec

@Palec "Đây không phải là trường hợp trong R" - Tôi không chắc chắn "cái này" mà bạn đang đề cập đến. Bạn đang nói rằng việc nối thêm không phải là O (1) hay không phải là O (n)?
bay

1
Tôi đang nói rằng nếu bạn đang viết mã ở Lisp, cách tiếp cận của bạn sẽ không hiệu quả, @fly. Nhận xét đó có nghĩa là để giải thích tại sao câu trả lời được viết như vậy. Trong R, hai cách tiếp cận là ngang tầm hiệu suất, AFAIK. Nhưng bây giờ tôi không chắc về sự phức tạp được khấu hao. Không chạm vào R kể từ khoảng thời gian bình luận trước đây của tôi được viết.
Palec

3
Trong R, cách tiếp cận này sẽ là O (n). Các c()chức năng bản đối số của nó vào một vector / danh sách mới và lợi nhuận đó.
JanKanis

6

Bạn muốn một cái gì đó như thế này có thể?

> 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

Đó không phải là một chức năng rất lịch sự (gán cho parent.frame()là loại thô lỗ) nhưng IIUYC đó là những gì bạn đang yêu cầu.


6

Tôi đã làm một so sánh nhỏ về các phương pháp được đề cập ở đây.

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

Các kết quả:

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 

Đây là thông tin tuyệt vời: sẽ không bao giờ đoán được rằng list = listkhông chỉ người chiến thắng - mà còn từ 1 đến 2 đơn hàng hoặc cường độ!
javadba

5

Nếu bạn chuyển vào biến danh sách dưới dạng một chuỗi được trích dẫn, bạn có thể tiếp cận nó từ bên trong hàm như:

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

vì thế:

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

[[2]]
[1] 2

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

[[2]]
[1] 2

[[3]]
[1] 3

> 

hoặc cho tín dụng thêm:

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

1
Về cơ bản, đây là hành vi mà tôi muốn, tuy nhiên nó vẫn gọi thêm vào bên trong, dẫn đến hiệu suất O (n ^ 2).
Nick

4

Không chắc chắn tại sao bạn không nghĩ rằng phương pháp đầu tiên của bạn sẽ không hiệu quả. Bạn có một lỗi trong hàm lappend: length (list) nên là length (lst). Điều này hoạt động tốt và trả về một danh sách với obj được nối thêm.


3
Bạn hoàn toàn đúng. Có một lỗi trong mã và tôi đã sửa nó. Tôi đã thử nghiệm những lappend()gì tôi đã cung cấp và dường như nó thực hiện cũng như c () và append (), tất cả đều thể hiện hành vi O (n ^ 2).
Nick


2

Tôi nghĩ rằng những gì bạn muốn làm thực sự là truyền tham chiếu (con trỏ) cho hàm-- tạo một môi trường mới (được truyền bởi tham chiếu đến các hàm) với danh sách được thêm vào nó:

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
}

Bây giờ bạn chỉ sửa đổi danh sách hiện có (không tạo danh sách mới)


1
Điều này dường như có độ phức tạp bậc hai một lần nữa. Vấn đề rõ ràng là thay đổi kích thước danh sách / vector không được thực hiện theo cách nó thường được thực hiện trong hầu hết các ngôn ngữ.
eold

Có-- có vẻ như việc nối thêm vào cuối rất chậm-- có thể danh sách b / c được đệ quy và R là tốt nhất trong các hoạt động vectơ thay vì hoạt động kiểu vòng lặp. Tốt hơn nhiều để làm:
DavidM

1
system.time (for (i in c (1: 10000) mylist [i] = i) (vài giây) hoặc tốt hơn là thực hiện tất cả trong một thao tác: system.time (mylist = list (1: 100000)) (chưa đến một giây), sau đó sửa đổi danh sách được sắp xếp trước đó bằng vòng lặp for cũng sẽ nhanh hơn.
DavidM

2

Đây là một cách đơn giản để thêm các mục vào Danh sách 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"]

Hoặc lập trình:

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

Đây không phải là thực sự bổ sung. Điều gì xảy ra nếu tôi có 100 đối tượng và tôi muốn thêm chúng vào danh sách theo chương trình? R có một append()hàm, nhưng nó thực sự là hàm concatenate và nó chỉ hoạt động trên các vectơ.
Nick

append()hoạt động trên các vectơ và danh sách, và nó là một phần bổ sung thực sự (về cơ bản giống như nối, vì vậy tôi không thấy vấn đề của bạn là gì)
hadley

8
Một chức năng chắp thêm sẽ làm thay đổi một đối tượng hiện có, không tạo ra một đối tượng mới. Một phụ lục thực sự sẽ không có hành vi O (N ^ 2).
Nick

2

trong thực tế có một subtelty với c()chức năng. Nếu bạn làm:

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

bạn sẽ đạt được như mong đợi:

[[1]]
[1]

[[2]]
[1] "foo"

nhưng nếu bạn thêm một ma trận x <- c(x, matrix(5,2,2), danh sách của bạn sẽ có thêm 4 yếu tố giá trị 5! Bạn nên làm:

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

Nó hoạt động cho bất kỳ đối tượng nào khác và bạn sẽ đạt được như mong đợi:

[[1]]
[1]

[[2]]
[1] "foo"

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

Cuối cùng, chức năng của bạn trở thành:

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

và nó hoạt động cho bất kỳ loại đối tượng. Bạn có thể thông minh hơn và làm:

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

1

Ngoài ra còn có list.appendtừ rlist( liên kết đến các tài liệu hướng dẫn )

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

Nó rất đơn giản và hiệu quả.


1
không giống R với tôi ... Python?
JD Long

1
Tôi đã thực hiện một chỉnh sửa và thử nó: Nó chậm chạp. Sử dụng tốt hơn c()hoặc list-method. Cả hai đều nhanh hơn.
5

Tìm kiếm một mã cho rlist::list.append(), về cơ bản nó là một trình bao bọc xung quanh base::c().
nbenn

1

Để xác thực, tôi đã chạy mã điểm chuẩn được cung cấp bởi @Cron. Có một sự khác biệt lớn (ngoài việc chạy nhanh hơn trên bộ xử lý i7 mới hơn): by_indexhiện tại hoạt động gần như cũng như 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

Để tham khảo ở đây là mã điểm chuẩn được sao chép nguyên văn từ câu trả lời của @ Cron (chỉ trong trường hợp sau đó anh ta thay đổi nội dung):

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
Tôi không nghĩ rằng đây là loại nối thêm OP đang tìm kiếm.
joran

Đây không phải là phần tử bổ sung trong một danh sách. Ở đây bạn đang tăng các phần tử của vectơ số nguyên, là phần tử duy nhất của danh sách. Danh sách chỉ có một phần tử, một vectơ số nguyên.
Sergio

0

Đây là một câu hỏi rất thú vị và tôi hy vọng suy nghĩ của tôi dưới đây có thể đóng góp một cách giải quyết cho nó. Phương pháp này đưa ra một danh sách phẳng mà không lập chỉ mục, nhưng nó có danh sách và không liệt kê để tránh các cấu trúc lồng nhau. Tôi không chắc về tốc độ vì tôi không biết cách đo điểm chuẩn.

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

Những gì tôi muốn thêm là nó đưa ra một danh sách lồng nhau hai cấp độ, nhưng đó là nó. Cách làm việc của danh sách và hủy danh sách không rõ ràng đối với tôi, nhưng đây là kết quả bằng cách kiểm tra mã
xappppp

-1

mylist<-list(1,2,3) mylist<-c(mylist,list(5))

Vì vậy, chúng ta có thể dễ dàng nối thêm phần tử / đối tượng bằng cách sử dụng đoạn mã trên

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.