İtfa edilmiş sabit sürede R'deki bir listeye bir nesne eklensin mi O (1)?


245

Bazı R listem mylistvarsa, objbuna bir öğe ekleyebilirsiniz :

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

Ama elbette daha kompakt bir yol var. R'de yeniyken, şöyle yazmayı denedim lappend():

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

ama tabii ki bu R'nin çağrı-ad-semantiği nedeniyle işe yaramaz ( lstçağrı üzerine etkin bir şekilde kopyalanır, bu yüzden değişiklikler lstkapsamı dışında görünmez lappend(). işlevinizin kapsamı ve çağrı ortamını değiştirir, ancak basit bir ekleme işlevi yazmak için büyük bir çekiç gibi görünür.

Herkes bunu yapmanın daha güzel bir yolunu önerebilir mi? Hem vektörler hem de listeler için çalışıyorsa bonus puanlar.


5
R genellikle fonksiyonel dillerde bulunan değişmez veri özelliklerine sahiptir, bunu söylemekten nefret eder, ancak bence bununla uğraşmanız gerekir. Artıları ve eksileri vardır
Dan

"Call-by-name" dediğinizde, gerçekten "değer-göre-çağrı" demek istediniz, değil mi?
Ken Williams

7
Hayır, kesinlikle değere göre değil, aksi takdirde sorun olmazdı. R aslında ihtiyaca göre çağrı kullanır ( en.wikipedia.org/wiki/Evaluation_strategy#Call_by_need ).
Nick

4
İyi bir fikir, vektör / listenizi önceden ayırmaktır: N = 100 mlist = vektör ('liste', N) (1'de 1: N) {#mylist [[i]] = ...} 'Büyümekten kaçının 'R. içindeki nesneler
Fernando

Yanıtı burada buldum, stackoverflow.com/questions/17046336/… Bu kadar kolay algoritmayı uygulamak çok zor!
KH Kim

Yanıtlar:


255

Bir dize listesiyse, şu c()işlevi kullanın :

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> 

Bu vektörler üzerinde de çalışır, bu yüzden bonus puanları alabilir miyim?

Edit (2015-Feb-01): Bu yazı beşinci doğum gününde geliyor. Bazı tür okuyucular bununla ilgili eksiklikleri tekrarlamaya devam ederler, bu nedenle elbette aşağıdaki yorumlardan bazılarına bakın. listTipler için bir öneri :

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

Genel olarak, R tipleri tüm tipler ve kullanımlar için bir ve sadece bir deyime sahip olmayı zorlaştırabilir.


19
Bu eklenmez ... bitiştirir. LLarandıktan sonra hala iki elementi olurdu C(LL, c="harry").
Nick

27
Sadece LL Atanacak: LL <- c(LL, c="harry").
Dirk Eddelbuettel

51
Bu yalnızca dizelerle çalışır. A, b ve c tamsayı vektörleri ise, davranış tamamen farklıdır.
Alexandre Rademaker

8
@Dirk: Partileriniz benden farklı yuvalanmış. Çağrımın c()2 bağımsız değişkeni var: eklemeye çalıştığım liste, yani list(a=3, b=c(4, 5))eklemeye çalıştığım öğe, yani c=c(6, 7). Yaklaşımımı kullanırsanız , açıkça belirtildiği şekilde adlandırılmış tek bir 2 öğeli vektör yerine 2 liste öğesinin eklendiğini ( 6ve 7adlarla c1ve ile birlikte c2) görürsünüz c!
j_random_hacker

7
Sonuç öyle mylist <- list(mylist, list(obj))mi? Cevabınız evet ise cevabı değiştirmek güzel olurdu
Matthew

96

OP (sorunun Nisan 2012 güncellenmiş revizyonunda), örneğin bir C ++ vector<>kapsayıcısı ile yapılabileceği gibi, amortize edilmiş sabit zamanda bir listeye eklemenin bir yolu olup olmadığını bilmekle ilgilenmektedir . Şimdiye kadarki en iyi cevap (lar), yalnızca sabit boyutlu bir sorunla karşılaşılan çeşitli çözümler için göreceli yürütme sürelerini göstermektedir, ancak çeşitli çözümlerin algoritmik verimliliğini doğrudan ele almamaktadır . Yanıtların çoğunun yorumları, bazı çözümlerin algoritmik verimliliğini tartışıyor, ancak bugüne kadar her durumda (Nisan 2015 itibariyle) yanlış sonuca varıyorlar.

Algoritmik verimlilik, sorun boyutu büyüdükçe zaman (uygulama süresi) veya boşluk (tüketilen bellek miktarı) gibi büyüme özelliklerini yakalar . Sabit boyutlu bir problem göz önüne alındığında çeşitli çözümler için performans testi yapılması, çeşitli çözümlerin büyüme oranını ele almaz. OP, "itfa edilmiş sabit zamanlı" bir R listesine nesne eklemenin bir yolu olup olmadığını bilmekle ilgilenir. Bu ne anlama geliyor? Açıklamak için önce "sabit zaman" ı tanımlayayım:

  • Sabit veya O (1) büyüme:

    Belirli bir görevi gerçekleştirmek için gerekli süre ise aynı kalır sorun boyutu gibi çift , o zaman algoritma sergiler söylemek sabit zaman "Büyük O" gösterimde büyümesini veya belirtildiği sergiler O (1) zaman büyümesi. OP "amortisman" sabit zamanını söylediğinde, sadece "uzun vadede" anlamına gelir ... yani, tek bir işlemin gerçekleştirilmesi zaman zaman normalden çok daha uzun sürerse (örneğin, önceden belirlenmiş bir arabellek tükendiğinde ve bazen daha büyük arabellek boyutu), uzun vadeli ortalama performans sabit bir süre olduğu sürece, buna hala O (1) diyoruz.

    Karşılaştırma için, ben de "doğrusal zaman" ve "ikinci dereceden zaman":

  • Doğrusal veya O (n) büyüme:

    Süresi, belirli bir görevi yerine getirmek için gerekli ise katına sorun büyüklüğü olarak çiftler , o zaman algoritma sergiler ki zaman, doğrusal ya da O (n) büyümesi.

  • İkinci dereceden veya O (n 2 ) büyüme:

    Belirli bir görevi gerçekleştirmek için gereken süre sorun büyüklüğünün karesi kadar artarsa , algoritmanın ikinci derece zaman veya O (n 2 ) büyümesi sergilediğini söyleriz .

Algoritmaların başka birçok verimlilik sınıfı vardır; Daha fazla tartışma için Wikipedia makalesini erteliyorum.

R için yeniyim ve bu sayfada sunulan çeşitli çözümlerin performans analizini yapmak için tamamen oluşturulmuş bir kod bloğuna sahip olmak güzeldi ve cevabı için @CronAcronis'e teşekkür ederim. Aşağıdaki çoğaltmak (bir fonksiyona sarılmış) benim analiz için kodunu ödünç alıyorum:

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 tarafından gönderilen sonuçlar kesinlikle a <- list(a, list(i))en azından 10000 problem büyüklüğü için yöntemin en hızlı olduğunu göstermektedir , ancak tek bir problem büyüklüğü için sonuçlar çözümün büyümesine hitap etmemektedir. Bunun için, farklı sorun boyutlarına sahip en az iki profil oluşturma testi yapmamız gerekir:

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

Her şeyden önce, min / lq / mean / median / uq / max değerleri hakkında bir kelime: 5 çalışmanın her biri için tam olarak aynı görevi gerçekleştirdiğimizden, ideal bir dünyada, bunun aynı şekilde gerçekleşmesini bekleyebiliriz her koşu için zaman miktarı. Ancak ilk çalıştırma, test ettiğimiz kodun CPU önbelleğine henüz yüklenmemiş olması nedeniyle normalde daha uzun zamanlara doğru eğilimlidir. İlk çalıştırmadan sonra, zamanların oldukça tutarlı olmasını bekleriz, ancak zaman zaman kodumuz zamanlayıcı kene kesintileri veya test ettiğimiz kodla ilgili olmayan diğer donanım kesintileri nedeniyle önbellekten çıkartılabilir. Kod snippet'lerini 5 kez test ederek, kodun ilk çalıştırma sırasında önbelleğe yüklenmesine izin veriyoruz ve ardından her snippet'e dışarıdaki olaylardan müdahale etmeden tamamlanması için 4 şans veriyoruz. Bu yüzden,

İlk önce 2000 ve sonra 20000 problem büyüklüğü ile çalışmayı seçtim, bu yüzden problem boyutum ilk çalıştırmadan ikincisine 10 kat arttı.

listÇözeltinin performansı : O (1) (sabit zaman)

İlk önce listçözümün büyümesine bakalım , çünkü her iki profil oluşturma işleminde de en hızlı çözüm olduğunu söyleyebiliriz: İlk çalışmada, 2000 "ekleme" görevlerini gerçekleştirmek 854 mikro saniye (0.854 mili saniye) sürdü . İkinci çalışmada, 20000 "ekleme" görevinin gerçekleştirilmesi 8.746 milisaniye sürdü. Saf bir gözlemci, "Ah, listçözüm O (n) büyümesi gösterir, çünkü problem büyüklüğü on kat büyüdükçe, testi gerçekleştirmek için gereken süre de arttı." Bu analizdeki sorun, OP'nin istediği şey , genel sorunun büyüme oranı değil, tek bir nesne yerleştirmenin büyüme oranıdır. Bunu bilerek,list çözümü OP'nin tam olarak ne istediğini sağlar: O (1) zamanında bir listeye nesne ekleme yöntemi.

Diğer çözümlerin performansı

Diğer çözümlerin hiçbiri çözümün hızına yaklaşmıyor list, ancak yine de bunları incelemek bilgilendirici:

Diğer çözümlerin çoğu performansta O (n) gibi görünmektedir. Örneğin, by_indexdiğer SO yayınlarında bulduğum frekansa dayanan çok popüler bir çözüm olan çözüm, 2000 nesneyi eklemek için 11.6 milisaniye ve bu nesnelerin on katını eklemek için 953 milisaniye sürdü. Genel sorunun süresi 100 kat büyüdü, bu yüzden saf bir gözlemci "Ah, by_indexçözüm O (n 2 ) büyümesi sergiler , çünkü problem büyüklüğü on kat büyüdükçe, testi uygulamak için gereken süre büyüdü 100 faktörü ile. "Daha önce olduğu gibi, bu analiz kusurludur, çünkü OP tek bir nesne eklemenin büyümesiyle ilgilenir. Toplam zaman büyümesini problemin boyut büyümesine böldüğümüzde, eklenen nesnelerin zaman büyümesinin, problem boyutunun büyümesiyle eşleşen 100 faktörü değil, sadece 10 kat arttığını görüyoruz, böylece by_indexçözüm O (n). O (n sergileyen listelenen bir çözüm vardır 2 tek bir nesne ekleme için) büyüme.


1
Okuyucuya: Lütfen yukarıdaki bulgularıma çok pratik bir eklenti sağlayan ve R'nin C uygulamasının dahili çalışmaları göz önüne alındığında çeşitli çözümlerin
geneline

4
Liste seçeneğinin gerekeni uyguladığından emin değilim:> uzunluk (c (c (c (liste (1)), liste (2)), liste (3))) [1] 3> uzunluk (liste (liste (liste) (list (1)), list (2)), list (3))) [1] 2. İç içe listelere daha çok benziyor.
Picarus

@Picarus - Bence haklısın. Artık R ile çalışmıyorum, ama neyse ki JanKanis çok daha kullanışlı bir O (1) çözümü ile bir cevap gönderdi ve belirlediğiniz sorunu not ediyor. Eminim JanKanis oyunuzu takdir edecektir.
phonetagger

@phonetagger, cevabınızı düzenlemelisiniz. Herkes tüm cevapları okumaz.
Picarus

"tek bir cevap asıl soruya cevap vermedi" -> Sorun, asıl sorunun algoritma karmaşıklığı ile ilgili olmamasıdır, sorunun basımlarına bir göz atın. OP önce bir listeye bir elemanın nasıl ekleneceğini sordu, birkaç ay sonra soruyu değiştirdi.
Carlos Cinelli

41

Diğer cevaplarda, sadece listyaklaşım O (1) ile sonuçlanır, ancak tek bir liste değil, derin iç içe bir liste yapısı ile sonuçlanır. Aşağıdaki veri yapılarını kullandım, O (1) (itfa edilmiş) eklerini destekliyorlar ve sonucun düz bir listeye dönüştürülmesine izin veriyorlar.

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
}

ve

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
}

Bunları aşağıdaki gibi kullanın:

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

[[2]]
[1] "world"

[[3]]
[1] 101

Bu çözümler, listeyle ilgili tüm işlemleri kendi başlarına destekleyen tam nesnelere genişletilebilir, ancak bu okuyucu için bir alıştırma olarak kalacaktır.

Adlandırılmış bir liste için başka bir varyant:

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
}

Deneyler

@ Phonetagger kodunu kullanarak performans karşılaştırması (@Cron Arconis 'kodunu temel alır). Ayrıca a ekledim better_env_as_containerve env_as_container_biraz değiştirdim . Orijinal env_as_container_kırıldı ve aslında tüm sayıları saklamaz.

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
    }

sonuç:

> 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

Eklediğim linkedListve expandingListhem bir inlined versiyonu. inlinedLinkedListTemelde bir kopyasıdır list_, ama aynı zamanda düz bir liste halinde içiçe yapı geri dönüştürür. Bunun ötesinde, satır içi ve satır içi olmayan sürümler arasındaki fark, işlev çağrılarının ek yükünden kaynaklanmaktadır.

O (1) ' in tüm varyantları expandingListve linkedListşovu performansa eklenir, kıyaslama süresi eklenen öğe sayısıyla doğrusal olarak ölçeklenir. linkedListdaha yavaştır expandingListve fonksiyon çağrısı yükü de görülebilir. Yani gerçekten elde edebileceğiniz tüm hıza ihtiyacınız varsa (ve R koduna bağlı kalmak istiyorsanız), inline sürümünü kullanın expandingList.

Ayrıca R'nin C uygulamasına da bir göz attım ve her iki yaklaşım da bellek bitene kadar herhangi bir boyut için O (1) eklenmelidir.

Ben de değiştirdim env_as_container_, orijinal sürümü önceden eklenen öğenin üzerine yazarak, "i" dizini altındaki her öğeyi saklar. better_env_as_containerEklediğim çok benzer env_as_container_fakat olmadan deparseşeyler. Her ikisi de O (1) performansı sergiler, ancak bağlantılı / genişleyen listelerden biraz daha büyük bir ek yüke sahiptirler.

Bellek yükü

CR uygulamasında, tahsis edilen nesne başına 4 kelime ve 2 int ek yükü vardır. linkedList(4 x 8 + 4 + 4 + 2 * 8 =) üst bellek ayırma hariç 64 bit bilgisayarlarda (ilgili ekteki madde başına 56 bayt, yani muhtemelen daha yakın 64 kadar bir toplam ekleme yapılması başına uzunluğu iki yaklaşım ayırır bir listesi, bayt). expandingListYaklaşım ürün başına 16 bayta kadar toplam bellek kullanımı bu nedenle, tek bir ekteki öğe başına sözcük artı vektör uzunluğu iki katına kopyasını kullanır. Bellek bir veya iki nesnede olduğu için, nesne başına yükü önemsizdir. envHafıza kullanımına derinlemesine bakmadım , ama daha yakın olacağını düşünüyorum linkedList.


Çözmeye çalıştığımız sorunu çözmezse liste seçeneğini tutmanın anlamı nedir?
Picarus

1
@Picarus Ne demek istediğinden emin değilim. Bunu neden kıyaslamada tuttum? Diğer seçeneklerle karşılaştırıldığında. Bu list_seçenek daha hızlıdır ve normal bir listeye dönüştürmeniz gerekmiyorsa, örneğin sonucu bir yığın olarak kullanırsanız yararlı olabilir.
JanKanis

@Gabor Csardi, stackoverflow.com/a/29482211/264177 adresindeki ortamları farklı bir sorudaki listelere geri dönüştürmenin daha hızlı bir yolunu yayınladı. Bunu sistemimde de karşılaştırdım. Better_env_as_container'ın iki katı daha hızlıdır, ancak hala LinkedList ve expandingList'ten daha yavaştır.
Ocak

Derin iç içe geçmiş (n = 99999) listeler belirli uygulamalar için yönetilebilir ve tolere edilebilir görünüyor: Herkes nestoR'u karşılaştırmak ister mi? ( environmentNestoR için kullandığım şeylerde hala bir noob'um.) Darboğazım neredeyse her zaman insan kodlaması ve veri analizi yapmak için harcanan zaman, ama bu yazıda bulduğum kriterleri takdir ediyorum. Bellek yüküne gelince, uygulamalarım için düğüm başına yaklaşık bir kB'ye kadar aldırmazdım. Ben büyük diziler, vb tutun
Ana Nimbus

17

Lisp'de bunu şu şekilde yaptık:

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

sadece 'c' değil, 'eksiler' olmasına rağmen Empy listesiyle başlamanız gerekiyorsa l <- NULL tuşunu kullanın.


3
Mükemmel! Diğer tüm çözümler bazı garip listeler getirir.
metakermit

4
Lisp'de listeye eklenmek O (1) işlemidir, ekleme ise O (n), @ fly uçlarında çalışır. Tersine çevirme ihtiyacı, performans artışı ile daha ağır basmaktadır. Bu, R'de durum böyle değildir. Genellikle Liste listelerine en çok benzeyen çift listede bile değildir.
Palec

@Palec "Bu R'de durum böyle değil" - Hangi "bu" dan bahsettiğinizden emin değilim. Eklemenin O (1) olmadığını veya O (n) olmadığını mı söylüyorsunuz?
uçar

1
Diyorum ki Lisp'de kod yazsaydın, yaklaşımın verimsiz olurdu, @ sinekler. Bu açıklama, cevabın neden olduğu gibi yazıldığını açıklamak içindir. R'de iki yaklaşım performans açısından AFAIK üzerindedir. Ama şimdi amortisman karmaşıklığından emin değilim. Önceki yorumum yazıldığı zamandan beri R'ye dokunmadım.
Palec

3
R'de bu yaklaşım O (n) olacaktır. c()Fonksiyon yeni bir vektör / listeye kopyalayan argümanları ve iadeler söyledi.
JanKanis

6

Belki böyle bir şey ister misin?

> 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

Çok kibar bir işlev değil (atamak parent.frame()biraz kaba) ama IIUYC istediğin şey bu.


6

Burada bahsedilen yöntemlerin küçük bir karşılaştırmasını yaptım.

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

Sonuçlar:

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 

Bu harika bir bilgi: aslalist = list sadece kazanan değil, aynı zamanda 1 ila 2 sipariş veya büyüklükte olduğunu tahmin ederdim !
javadba

5

List değişkenini tırnak içine alınmış bir dize olarak iletirseniz, değişkene aşağıdaki fonksiyondan ulaşabilirsiniz:

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

yani:

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

[[2]]
[1] 2

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

[[2]]
[1] 2

[[3]]
[1] 3

> 

veya ekstra kredi için:

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

1
Bu temelde istediğim davranıştır, ancak yine de dahili olarak append çağırır ve O (n ^ 2) performansı ile sonuçlanır.
Nick

4

İlk yönteminizin neden işe yaramadığını düşündüğünüzden emin değilim. Lappend işlevinde bir hata var: uzunluk (liste) uzunluk (lst) olmalıdır. Bu iyi çalışır ve ekteki obj ile bir liste döndürür.


3
Kesinlikle haklısın. Kodda bir hata vardı ve ben düzelttim. Sağladığımı test lappend()ettim ve hepsi O (n ^ 2) davranışı sergileyen c () ve append () gibi performans sergiliyor.
Nick


2

Yapmak istediğiniz şey aslında referansa (işaretçi) işleve geçmek olduğunu - liste eklendi ile yeni bir ortam (işlevlere başvuru ile geçirilir) oluşturmak olduğunu düşünüyorum:

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
}

Artık yalnızca mevcut listeyi değiştiriyorsunuz (yeni bir liste oluşturmuyor)


1
Bu yine ikinci dereceden zaman karmaşıklığına sahip gibi görünüyor. Sorun, liste / vektör yeniden boyutlandırmasının çoğu dilde genellikle uygulandığı şekilde uygulanmadığıdır.
eold

Evet - sondaki ekleme çok yavaş gibi görünüyor - muhtemelen b / c listeleri özyinelemelidir ve R, döngü tipi işlemleri yerine vektör işlemlerinde en iyisidir. Yapması çok daha iyi:
DavidM

1
system.time (i (c (1: 10000) mylist [i] = i) (birkaç saniye) veya daha iyisi için hepsini tek bir işlemde yapın: system.time (mylist = liste (1: 100000)) (bir saniyeden az), sonra for döngüsü ile önceden tahsis edilen listeyi değiştirmek de daha hızlı olacaktır.
DavidM

2

Bu, bir R Listesine öğe eklemenin basit bir yoludur:

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

Veya programlı olarak:

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

Bu gerçekten eklenmiyor. 100 nesnem varsa ve bunları programlı olarak bir listeye eklemek istersem ne olur? R'nin bir append()işlevi vardır, ancak gerçekten bir birleştirilmiş işlevdir ve yalnızca vektörler üzerinde çalışır.
Nick

append()vektörler ve listeler üzerinde çalışır ve gerçek bir append (temelde bitiştirmekle aynıdır, bu yüzden probleminizin ne olduğunu görmüyorum)
hadley 13:10

8
Bir ekleme işlevi yeni bir nesne oluşturmamalı, var olan bir nesneyi değiştirmelidir. Gerçek bir ekte O (N ^ 2) davranışı olmaz.
Nick

2

aslında bu c()işlevin alt sınırı var . Yaparsan:

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

beklendiği gibi elde edersiniz:

[[1]]
[1]

[[2]]
[1] "foo"

ancak ile bir matris eklerseniz x <- c(x, matrix(5,2,2), listenizde 4 değer daha bulunur 5! Yapsan iyi olur:

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

Diğer herhangi bir nesne için çalışır ve beklediğiniz gibi elde edersiniz:

[[1]]
[1]

[[2]]
[1] "foo"

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

Son olarak, fonksiyonunuz şöyle olur:

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

ve her tür nesne için çalışır. Daha akıllı olabilir ve şunları yapabilirsiniz:

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

1

Orada da list.appendgelen rlist( belgelerine bağlantı )

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

Çok basit ve verimli.


1
Bana R gibi görünmüyor ... Python?
JD Long

1
Bir düzenleme yaptım ve denedim: Yavaş yavaş. c()Veya listyöntemini daha iyi kullanın . Her ikisi de çok daha hızlı.
5

Bir kod bakarak rlist::list.append(), aslında etrafında bir sarıcı base::c().
nbenn

1

Doğrulama için @Cron tarafından sağlanan karşılaştırma kodunu çalıştırdım. Büyük bir fark var (daha yeni i7 işlemcide daha hızlı çalışmaya ek olarak): by_indexşimdi neredeyse aynı performansı sergiliyor 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

Referans olarak, @ Cron'un cevabından kelimesi kelimesine kopyalanan karşılaştırma kodu (daha sonra içeriği değiştirmesi durumunda):

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
Ben bu OP aradığını ekleme tür olduğunu sanmıyorum.
joran

Bu, listeye öğe eklemez. Burada, listenin tek elemanı olan tamsayı vektörünün elemanlarını artırıyorsunuz. Listede yalnızca bir öğe, bir tamsayı vektörü var.
Sergio

0

Bu çok ilginç bir soru ve umarım aşağıdaki düşüncem buna bir çözüm yolu getirebilir. Bu yöntem, dizinleme olmadan düz bir liste verir, ancak iç içe geçme yapılarından kaçınmak için liste ve liste içerir. Hızdan emin değilim, çünkü nasıl kıyaslayacağımı bilmiyorum.

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

Eklemek istediğim, iki seviyeli iç içe bir liste vermesidir, ancak bu kadar. Liste ve liste dışı çalışmanın benim için çok net olmadığı, ancak bu kodu test ederek sonuç
xappppp

-1

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

Böylece, yukarıdaki kodu kullanarak öğeyi / nesneyi kolayca ekleyebiliriz

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.