Tambahkan objek ke daftar dalam R dalam waktu konstan diamortisasi, O (1)?


245

Jika saya memiliki beberapa daftar R mylist, Anda dapat menambahkan item objke dalamnya seperti:

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

Tapi pasti ada beberapa cara yang lebih kompak. Ketika saya masih baru di R, saya mencoba menulis lappend()seperti:

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

tetapi tentu saja itu tidak berhasil karena semantik panggilan-dengan-nama R ( lstsecara efektif disalin atas panggilan, sehingga perubahan lsttidak terlihat di luar lingkup lappend(). Saya tahu Anda dapat melakukan peretasan lingkungan dalam fungsi R untuk menjangkau di luar ruang lingkup fungsi Anda dan mutasi lingkungan panggilan, tetapi sepertinya palu besar untuk menulis fungsi penambahan sederhana.

Adakah yang bisa menyarankan cara yang lebih indah untuk melakukan ini? Poin bonus jika berfungsi untuk vektor dan daftar.


5
R memiliki karakteristik data yang tidak dapat diubah yang sering ditemukan dalam bahasa fungsional, benci mengatakan ini, tapi saya pikir Anda hanya harus menghadapinya. Ini memiliki pro dan kontra
Dan

Ketika Anda mengatakan "panggilan-dengan-nama", Anda benar-benar bermaksud "panggilan berdasarkan nilai", bukan?
Ken Williams

7
Tidak, itu pasti bukan panggilan-oleh-nilai, kalau tidak ini tidak akan menjadi masalah. R sebenarnya menggunakan call-by-need ( en.wikipedia.org/wiki/Evaluation_strategy#Call_by_need ).
Nick

4
Ide yang bagus adalah dengan pra-alokasikan vektor / daftar Anda: N = 100 mylist = vektor ('daftar', N) untuk (i dalam 1: N) {#mylist [[i]] = ...} Hindari 'tumbuh 'objek dalam R.
Fernando

Saya tidak sengaja menemukan jawabannya di sini, stackoverflow.com/questions/17046336/... Sangat sulit untuk mengimplementasikan algoritma yang begitu mudah!
KH Kim

Jawaban:


255

Jika daftar string, gunakan saja c()fungsinya:

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> 

Itu bekerja pada vektor juga, jadi apakah saya mendapatkan poin bonus?

Sunting (2015-Feb-01): Posting ini akan muncul pada ulang tahun kelima. Beberapa pembaca yang baik terus mengulangi kekurangannya, jadi tentu saja lihat juga komentar di bawah ini. Satu saran untuk listjenis:

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

Secara umum, tipe R membuatnya sulit untuk memiliki satu dan hanya satu idiom untuk semua jenis dan kegunaan.


19
Ini tidak menambahkan ... itu menyatukan. LLpasti masih memiliki dua elemen setelah C(LL, c="harry")dipanggil.
Nick

27
Hanya menetapkan kembali ke LL: LL <- c(LL, c="harry").
Dirk Eddelbuettel

51
Ini hanya berfungsi dengan string. Jika a, b dan c adalah vektor integer, tingkah lakunya benar-benar berbeda.
Alexandre Rademaker

8
@ Malas: Anda memiliki parens bersarang berbeda dari saya. Panggilan saya untuk c()memiliki 2 argumen: daftar yang saya coba tambahkan, yaitu list(a=3, b=c(4, 5)), dan item yang saya coba tambahkan, yaitu c=c(6, 7). Jika Anda menggunakan pendekatan saya, Anda akan melihat bahwa 2 item daftar ditambahkan ( 6dan 7, dengan nama c1dan c2) alih-alih vektor 2-elemen tunggal bernama csebagaimana dimaksudkan dengan jelas!
j_random_hacker

7
Jadi kesimpulannya mylist <- list(mylist, list(obj))? Jika ya akan menyenangkan untuk memodifikasi jawabannya
Matius

96

OP (dalam revisi pertanyaan yang diperbarui pada bulan April 2012) tertarik untuk mengetahui apakah ada cara untuk menambah daftar dalam waktu konstan yang diamortisasi, seperti yang dapat dilakukan, misalnya, dengan wadah C ++ vector<>. Jawaban terbaik (s?) Di sini sejauh ini hanya menunjukkan waktu eksekusi relatif untuk berbagai solusi mengingat masalah ukuran tetap, tetapi tidak membahas salah satu efisiensi algoritmik berbagai solusi secara langsung. Komentar di bawah ini banyak dari jawaban yang membahas efisiensi algoritmik dari beberapa solusi, tetapi dalam setiap kasus hingga saat ini (per April 2015) mereka sampai pada kesimpulan yang salah.

Efisiensi algoritmik menangkap karakteristik pertumbuhan, baik dalam waktu (waktu eksekusi) atau ruang (jumlah memori yang dikonsumsi) ketika ukuran masalah bertambah . Menjalankan tes kinerja untuk berbagai solusi mengingat masalah ukuran tetap tidak membahas tingkat pertumbuhan berbagai solusi. OP tertarik untuk mengetahui apakah ada cara untuk menambahkan objek ke daftar R dalam "waktu konstan diamortisasi". Apa artinya? Untuk menjelaskan, pertama izinkan saya menggambarkan "waktu konstan":

  • Pertumbuhan konstan atau O (1) :

    Jika waktu yang diperlukan untuk melakukan tugas yang diberikan tetap sama dengan ukuran masalah yang berlipat ganda , maka kita katakan algoritma menunjukkan pertumbuhan waktu yang konstan , atau dinyatakan dalam notasi "O Besar", menunjukkan O (1) pertumbuhan waktu. Ketika OP mengatakan "diamortisasi" waktu konstan, ia hanya berarti "dalam jangka panjang" ... yaitu, jika melakukan satu operasi kadang-kadang membutuhkan waktu lebih lama dari biasanya (misalnya jika buffer yang dialokasikan sebelumnya habis dan kadang-kadang membutuhkan pengubahan ukuran ke yang lebih besar). ukuran buffer), selama kinerja rata-rata jangka panjang adalah waktu yang konstan, kita masih akan menyebutnya O (1).

    Sebagai perbandingan, saya juga akan menjelaskan "waktu linear" dan "waktu kuadratik":

  • Pertumbuhan linier atau O (n) :

    Jika waktu yang dibutuhkan untuk melakukan tugas yang diberikan berlipat ganda ketika ukuran masalahnya berlipat ganda , maka kita katakan algoritma menunjukkan waktu linier , atau pertumbuhan O (n) .

  • Pertumbuhan kuadratik atau O (n 2 ) :

    Jika waktu yang dibutuhkan untuk melakukan tugas yang diberikan meningkat dengan kuadrat dari ukuran masalah , mereka kita katakan algoritma menunjukkan waktu kuadratik , atau O (n 2 ) pertumbuhan.

Ada banyak kelas efisiensi algoritma lainnya; Saya tunduk pada artikel Wikipedia untuk diskusi lebih lanjut.

Saya berterima kasih kepada @CronAcronis atas jawabannya, karena saya baru di R dan senang memiliki blok kode yang dibuat untuk melakukan analisis kinerja dari berbagai solusi yang disajikan pada halaman ini. Saya meminjam kode untuk analisis saya, yang saya duplikat (dibungkus dengan fungsi) di bawah:

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

Hasil yang diposting oleh @CronAcronis tampaknya menyarankan bahwa a <- list(a, list(i))metode ini tercepat, setidaknya untuk ukuran masalah 10.000, tetapi hasil untuk ukuran masalah tunggal tidak mengatasi pertumbuhan solusi. Untuk itu, kita perlu menjalankan minimal dua tes profil, dengan ukuran masalah yang berbeda:

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

Pertama-tama, sebuah kata tentang nilai min / lq / mean / median / uq / max: Karena kita melakukan tugas yang sama persis untuk masing-masing 5 run, di dunia yang ideal, kita dapat berharap bahwa itu akan mengambil persis sama persis jumlah waktu untuk setiap lari. Tetapi proses pertama biasanya bias terhadap waktu yang lebih lama karena fakta bahwa kode yang kami uji belum dimuat ke dalam cache CPU. Setelah menjalankan pertama kali, kami berharap waktu akan cukup konsisten, tetapi kadang-kadang kode kami dapat diusir dari cache karena gangguan centang timer atau gangguan perangkat keras lain yang tidak terkait dengan kode yang kami uji. Dengan menguji cuplikan kode 5 kali, kami mengizinkan kode untuk dimuat ke dalam cache selama proses pertama dan kemudian memberikan masing-masing 4 cuplikan peluang untuk berjalan hingga selesai tanpa gangguan dari peristiwa luar. Untuk alasan ini,

Perhatikan bahwa saya memilih untuk pertama kali menjalankan dengan ukuran masalah 2000 dan kemudian 20000, jadi ukuran masalah saya meningkat dengan faktor 10 dari menjalankan pertama ke yang kedua.

Performa listsolusi: O (1) (waktu konstan)

Pertama-tama mari kita lihat pertumbuhan listsolusi, karena kita dapat segera mengetahui bahwa ini adalah solusi tercepat di kedua proses pembuatan profil: Pada proses pertama, dibutuhkan 854 detik mikro (0,854 mili detik) untuk melakukan 2.000 tugas "menambahkan" tugas. Pada putaran kedua, dibutuhkan 8,746 milidetik untuk melakukan 20.000 tugas "tambahkan". Seorang pengamat yang naif akan berkata, "Ah, listsolusinya menunjukkan pertumbuhan O (n), karena ketika ukuran masalah bertambah sepuluh kali lipat, begitu pula waktu yang diperlukan untuk melaksanakan tes." Masalah dengan analisis itu adalah bahwa apa yang diinginkan OP adalah tingkat pertumbuhan dari penyisipan objek tunggal , bukan tingkat pertumbuhan dari keseluruhan masalah. Mengetahui itu, maka jelas bahwa itulist solusi memberikan persis apa yang diinginkan OP: metode menambahkan objek ke daftar dalam waktu O (1).

Kinerja solusi lain

Tidak ada solusi lain yang mendekati kecepatan dari listsolusi tersebut, tetapi bagaimanapun, informatif untuk memeriksanya:

Sebagian besar solusi lain tampaknya O (n) dalam kinerja. Misalnya, by_indexsolusinya, solusi yang sangat populer berdasarkan frekuensi yang saya temukan di pos SO lainnya, membutuhkan 11,6 milidetik untuk menambahkan 2000 objek, dan 953 milidetik untuk menambahkan sepuluh kali lebih banyak objek. Waktu masalah secara keseluruhan tumbuh dengan faktor 100, sehingga pengamat yang naif mungkin mengatakan "Ah, by_indexsolusinya menunjukkan pertumbuhan O (n 2 ), karena ketika ukuran masalah tumbuh dengan faktor sepuluh, waktu yang dibutuhkan untuk melaksanakan tes tumbuh dengan faktor 100. "Seperti sebelumnya, analisis ini cacat, karena OP tertarik pada pertumbuhan penyisipan objek tunggal. Jika kita membagi pertumbuhan waktu keseluruhan dengan pertumbuhan ukuran masalah, kita menemukan bahwa pertumbuhan waktu objek penambahan meningkat dengan faktor hanya 10, bukan faktor 100, yang cocok dengan pertumbuhan ukuran masalah, jadi by_indexsolusinya adalah O (n). Tidak ada solusi yang menunjukkan pertumbuhan O (n 2 ) untuk menambahkan objek tunggal.


1
Kepada pembaca: Silakan baca jawaban JanKanis, yang memberikan perluasan yang sangat praktis untuk temuan saya di atas, dan menyelam sedikit ke dalam overhead dari berbagai solusi mengingat cara kerja internal implementasi C dari R.
phonetagger

4
Tidak yakin opsi daftar mengimplementasikan apa yang diperlukan:> panjang (c (c (c (daftar (1)), daftar (2)), daftar (3))) [1] 3> panjang (daftar (daftar (daftar (daftar) (daftar (1)), daftar (2)), daftar (3))) [1] 2. Lebih mirip daftar bersarang.
Picarus

@Picarus - Saya pikir Anda benar. Saya tidak bekerja dengan R lagi, tapi untungnya JanKanis memposting jawaban dengan solusi O (1) yang jauh lebih berguna dan mencatat masalah yang Anda identifikasi. Saya yakin JanKanis akan menghargai upvote Anda.
phonetagger

@ phonetagger, Anda harus mengedit jawaban Anda. Tidak semua orang akan membaca semua jawaban.
Picarus

"tidak ada satu jawaban yang menjawab pertanyaan aktual" -> Masalahnya adalah bahwa pertanyaan awal bukan tentang kompleksitas algoritme, lihat edisi pertanyaan. OP pertama-tama bertanya bagaimana menambahkan elemen dalam daftar, kemudian beberapa bulan kemudian, ia mengubah pertanyaan.
Carlos Cinelli

41

Dalam jawaban lain, hanya listpendekatan yang menghasilkan O (1) menambahkan, tetapi menghasilkan struktur daftar yang sangat bersarang, dan bukan daftar tunggal. Saya telah menggunakan struktur data di bawah ini, mereka mendukung O (1) (diamortisasi) ditambahkan, dan memungkinkan hasilnya dikonversi kembali ke daftar biasa.

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
}

dan

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
}

Gunakan mereka sebagai berikut:

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

[[2]]
[1] "world"

[[3]]
[1] 101

Solusi ini dapat diperluas menjadi objek penuh yang mendukung operasi yang terkait dengan daftar sendiri, tetapi itu akan tetap sebagai latihan bagi pembaca.

Varian lain untuk daftar bernama:

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
}

Tolak ukur

Perbandingan kinerja menggunakan kode @ phonetagger (yang didasarkan pada kode @Cron Arconis). Saya juga menambahkan better_env_as_containerdan mengubah env_as_container_sedikit. Dokumen asli env_as_container_rusak dan tidak benar-benar menyimpan semua angka.

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
    }

hasil:

> 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

Saya telah menambahkan linkedListdan expandingListdan versi keduanya. Ini inlinedLinkedListpada dasarnya adalah salinan list_, tetapi juga mengubah struktur bersarang kembali menjadi daftar polos. Di luar itu perbedaan antara versi inline dan non-inline adalah karena overhead panggilan fungsi.

Semua varian expandingListdan linkedListmenunjukkan kinerja penambahan O (1), dengan penskalaan waktu penskalaan linear dengan jumlah item yang ditambahkan. linkedListlebih lambat daripada expandingList, dan overhead panggilan fungsi juga terlihat. Jadi, jika Anda benar-benar membutuhkan semua kecepatan yang bisa Anda dapatkan (dan ingin tetap menggunakan kode R), gunakan versi inline dari expandingList.

Saya juga sudah melihat implementasi C dari R, dan kedua pendekatan harus O (1) tambahkan untuk ukuran apa pun sampai Anda kehabisan memori.

Saya juga telah mengubah env_as_container_, versi asli akan menyimpan setiap item di bawah indeks "i", menimpa item yang ditambahkan sebelumnya. The better_env_as_containerSaya telah menambahkan sangat mirip dengan env_as_container_tapi tanpa deparsebarang-barang. Keduanya menunjukkan kinerja O (1), tetapi mereka memiliki overhead yang sedikit lebih besar dari daftar yang ditautkan / diperluas.

Memori di atas kepala

Dalam implementasi CR ada overhead 4 kata dan 2 int per objek yang dialokasikan. The linkedListpendekatan mengalokasikan satu daftar panjang dua per append, untuk total (4 * 8 + 4 + 4 + 2 * 8 =) 56 bytes per item ditambahkan pada 64-bit komputer (termasuk alokasi memori di atas kepala, jadi mungkin lebih dekat ke 64 byte). The expandingListPendekatan menggunakan satu kata per item ditambahkan, ditambah salinan ketika dua kali lipat panjang vektor, sehingga penggunaan memori total hingga 16 byte per item. Karena memori semua dalam satu atau dua objek, overhead per objek tidak signifikan. Saya belum melihat ke dalam envpenggunaan memori, tapi saya pikir akan lebih dekat linkedList.


apa gunanya menyimpan opsi daftar jika tidak memecahkan masalah yang kita coba pecahkan?
Picarus

1
@Picarus Saya tidak yakin apa yang Anda maksud. Kenapa saya menyimpannya di benchmark? Sebagai pembanding dengan opsi lain. The list_pilihan adalah lebih cepat dan dapat berguna jika Anda tidak perlu mengkonversi ke daftar normal, yaitu jika Anda menggunakan hasilnya sebagai stack.
JanKanis

@Gabor Csardi memposting cara yang lebih cepat untuk mengubah lingkungan kembali ke daftar dalam pertanyaan berbeda di stackoverflow.com/a/29482211/264177. Saya membandingkan itu juga di sistem saya. Ini sekitar dua kali lebih cepat better_env_as_container tapi masih lebih lambat dari linkedList dan ExpandList.
JanKanis

Daftar yang sangat bersarang (n = 99999) tampaknya dapat dikelola dan dapat ditoleransi untuk aplikasi tertentu: Adakah yang ingin membandingkan nestoR ? (Saya masih sedikit noob pada environmenthal - hal yang saya gunakan untuk nestoR.) Kemacetan saya hampir selalu menghabiskan waktu manusia coding dan melakukan analisis data, tapi saya menghargai tolok ukur yang saya temukan pada posting ini. Adapun overhead memori, saya tidak keberatan hingga sekitar satu kB per node untuk aplikasi saya. Saya berpegang pada array besar, dll.
Ana Nimbus

17

Di Lisp kami melakukannya dengan cara ini:

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

meskipun itu 'kontra', bukan hanya 'c'. Jika Anda perlu memulai dengan daftar kosong, gunakan l <- NULL.


3
Luar biasa! Semua solusi lain mengembalikan beberapa daftar aneh.
metakermit

4
Di Lisp, menambahkan kata ke daftar adalah operasi O (1), sementara menambahkan berjalan di O (n), @ terbang. Kebutuhan akan pembalikan lebih besar dari perolehan kinerja. Ini tidak terjadi di R. Bahkan dalam pasangan, yang umumnya paling mirip Daftar daftar.
Palec

@Palec "Ini bukan kasus di R" - Saya tidak yakin yang "ini" yang Anda maksud. Apakah Anda mengatakan bahwa menambahkan bukan O (1), atau bukan O (n)?
terbang

1
Saya mengatakan bahwa jika Anda mengkode dalam Lisp, pendekatan Anda akan tidak efisien, @ terbang. Pernyataan itu dimaksudkan untuk menjelaskan mengapa jawabannya ditulis seperti apa adanya. Dalam R, kedua pendekatan tersebut setara dengan kinerja, AFAIK. Tapi sekarang saya tidak yakin tentang kompleksitas yang diamortisasi. Belum pernah menyentuh R sejak komentar saya sebelumnya ditulis.
Palec

3
Dalam R, pendekatan ini adalah O (n). The c()salinan fungsi argumen menjadi vektor / daftar baru dan kembali itu.
JanKanis

6

Anda menginginkan sesuatu seperti ini, mungkin?

> 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

Ini bukan fungsi yang sangat sopan (menugaskan parent.frame()agak kasar) tetapi IIUYC adalah apa yang Anda minta.


6

Saya telah membuat perbandingan kecil metode yang disebutkan di sini.

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

Hasil:

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 

Ini adalah info hebat: tidak akan pernah menduga bahwa list = listbukan hanya pemenang - tetapi dengan 1 hingga 2 pesanan atau besarnya!
javadba

5

Jika Anda meneruskan variabel daftar sebagai string yang dikutip, Anda dapat mencapainya dari dalam fungsi seperti:

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

begitu:

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

[[2]]
[1] 2

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

[[2]]
[1] 2

[[3]]
[1] 3

> 

atau untuk kredit tambahan:

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

1
Ini pada dasarnya adalah perilaku yang saya inginkan, namun masih memanggil append internal, menghasilkan kinerja O (n ^ 2).
Nick

4

Tidak yakin mengapa Anda tidak berpikir metode pertama Anda tidak akan berhasil. Anda memiliki bug dalam fungsi lappend: length (daftar) harus length (lst). Ini berfungsi dengan baik dan mengembalikan daftar dengan obj yang ditambahkan.


3
Anda memang benar. Ada bug dalam kode dan saya sudah memperbaikinya. Saya telah menguji lappend()yang saya berikan dan tampaknya berkinerja sebaik c () dan append (), yang semuanya menunjukkan perilaku O (n ^ 2).
Nick


2

Saya pikir apa yang ingin Anda lakukan sebenarnya adalah lewat referensi (pointer) ke function-- buat lingkungan baru (yang diteruskan oleh referensi ke fungsi) dengan daftar ditambahkan ke dalamnya:

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
}

Sekarang Anda hanya mengubah daftar yang ada (tidak membuat yang baru)


1
Ini tampaknya memiliki kompleksitas waktu kuadratik lagi. Masalahnya jelas bahwa pengubahan ukuran daftar / vektor tidak diterapkan dengan cara yang biasanya diterapkan di sebagian besar bahasa.
dicetak

Ya - sepertinya menambahkan pada akhir sangat lambat - mungkin daftar b / c bersifat rekursif, dan R lebih baik pada operasi vektor daripada operasi tipe loop. Jauh lebih baik untuk dilakukan:
DavidM

1
system.time (untuk (i in c (1: 10000) mylist [i] = i) (beberapa detik), atau lebih baik lagi melakukan semuanya dalam satu operasi: system.time (mylist = daftar (1: 100000)) (kurang dari satu detik), kemudian memodifikasi daftar yang dialokasikan sebelumnya dengan for loop juga akan lebih cepat
DavidM

2

Ini adalah cara mudah untuk menambahkan item ke Daftar 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"]

Atau secara terprogram:

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

Ini tidak benar-benar menambahkan. Bagaimana jika saya memiliki 100 objek dan saya ingin menambahkannya ke daftar secara terprogram? R memiliki append()fungsi, tetapi ini benar-benar fungsi gabungan dan hanya bekerja pada vektor.
Nick

append()bekerja pada vektor dan daftar, dan itu adalah append yang benar (yang pada dasarnya sama dengan concatenate, jadi saya tidak melihat apa masalah Anda)
hadley

8
Fungsi append harus memutasi objek yang ada, bukan membuat yang baru. A true append tidak akan memiliki perilaku O (N ^ 2).
Nick

2

sebenarnya ada subtelty dengan c()fungsi tersebut. Jika kamu melakukan:

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

Anda akan mendapatkan seperti yang diharapkan:

[[1]]
[1]

[[2]]
[1] "foo"

tetapi jika Anda menambahkan matriks dengan x <- c(x, matrix(5,2,2), daftar Anda akan memiliki 4 elemen nilai lainnya 5! Anda sebaiknya melakukan:

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

Ini berfungsi untuk objek lain dan Anda akan mendapatkan seperti yang diharapkan:

[[1]]
[1]

[[2]]
[1] "foo"

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

Akhirnya, fungsi Anda menjadi:

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

dan itu bekerja untuk semua jenis objek. Anda bisa lebih pintar dan melakukan:

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

1

Ada juga list.appenddari rlist( tautan ke dokumentasi )

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

Ini sangat sederhana dan efisien.


1
tidak terlihat seperti R bagiku ... Python?
JD Long

1
Saya mengedit dan mencobanya: Sangat lambat. Lebih baik gunakan c()atau list-metode. Keduanya jauh lebih cepat.
5

Mencari kode untuk rlist::list.append(), itu pada dasarnya pembungkus base::c().
nbenn

1

Untuk validasi saya menjalankan kode benchmark yang disediakan oleh @Cron. Ada satu perbedaan utama (selain berjalan lebih cepat pada prosesor i7 yang lebih baru): by_indexsekarang berkinerja hampir sama dengan 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

Untuk referensi di sini adalah kode benchmark disalin secara verbatim dari jawaban @ Cron (kalau-kalau nanti dia mengubah konten):

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
Saya tidak berpikir ini adalah jenis menambahkan OP cari.
joran

Ini bukan elemen tambahan dalam daftar. Di sini Anda meningkatkan elemen vektor integer, yang merupakan satu-satunya elemen daftar. Daftar ini hanya memiliki satu elemen, vektor integer.
Sergio

0

Ini adalah pertanyaan yang sangat menarik dan saya harap pemikiran saya di bawah ini dapat memberikan kontribusi solusi untuk itu. Metode ini memang memberikan daftar datar tanpa pengindeksan, tetapi memang memiliki daftar dan tidak terdaftar untuk menghindari struktur bersarang. Saya tidak yakin tentang kecepatan karena saya tidak tahu bagaimana melakukan benchmark.

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

Yang ingin saya tambahkan adalah memberikan daftar bersarang dua tingkat, tapi hanya itu. Cara bagaimana daftar dan berhenti bekerja tidak terlalu jelas bagi saya, tetapi ini adalah hasil dengan menguji kode
xappppp

-1

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

Jadi kita dapat dengan mudah menambahkan elemen / objek menggunakan kode di atas

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.