オブジェクトをRのリストに一定の償却時間O(1)で追加しますか?


245

Rリストがある場合はmylist、次のobjようにアイテムを追加できます。

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

しかし、確かにもっとコンパクトな方法があります。私がRの初心者だったとき、次のlappend()ように書いてみました。

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

しかしもちろん、Rの名前による呼び出しのセマンティクスのために機能しません(lst呼び出し時に効果的にコピーされるため、への変更lstはのスコープ外には表示されませんlappend()。R関数で環境ハッキングを実行して、関数のスコープと呼び出し環境を変更しますが、単純な追加関数を書くのは大きなハンマーのようです。

誰かがこれを行うより美しい方法を提案できますか?ベクトルとリストの両方で機能する場合、ボーナスポイント。


5
Rには、関数型言語でよく見られる不変のデータ特性がありますが、これは言いたくないのですが、対処する必要があると思います。それには長所と短所があります
Dan

「名前による呼び出し」とは、実際には「値による呼び出し」を意味します。
ケンウィリアムズ

7
いいえ、それは間違いなく値渡しではありません。そうでなければ、これは問題にはなりません。Rは実際には必要な呼び出しを使用します(en.wikipedia.org/wiki/Evaluation_strategy#Call_by_need)。
Nick

4
ベクトル/リストを事前に割り当てることをお勧めします:N = 100 mylist = vector( 'list'、N)for(i in 1:N){#mylist [[i]] = ...}「成長しない」 'Rのオブジェクト
フェルナンド

私は誤ってここで答えを見つけました、stackoverflow.com / questions / 17046336 / …とても簡単なアルゴリズムを実装するのはとても難しいです!
KHキム

回答:


255

文字列のリストの場合は、次のc()関数を使用します。

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> 

ベクトルでも機能するので、ボーナスポイントをもらえますか?

編集(2015年2月1日):この投稿は5歳の誕生日に近づいています。一部の親切な読者は、それを使用して欠点を繰り返し続けているため、必ず以下のコメントのいくつかも参照してください。listタイプに関する1つの提案:

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

一般に、R型では、すべての型と使用法で1つだけのイディオムを持つことは困難です。


19
これは追加されません...連結します。 が呼び出されたLL後も、2つの要素C(LL, c="harry")があります。
Nick

27
LLに再割り当てするだけです LL <- c(LL, c="harry")
Dirk Eddelbuettel、

51
これは文字列でのみ機能します。a、b、cが整数ベクトルの場合、動作は完全に異なります。
Alexandre Rademaker、2010

8
@Dirk:括弧の入れ子が私とは異なります。への私の呼び出しにc()は2つの引数があります。追加しようとしているリスト、つまりlist(a=3, b=c(4, 5))と、追加しようとしているアイテム、つまりc=c(6, 7)です。私のアプローチを使用すると、明確に意図された名前の単一の2要素ベクトルの代わりに、2つのリストアイテムが追加されます(6and 7、名前c1c2c
j_random_hacker

7
それで結論はmylist <- list(mylist, list(obj))?はいの場合、答えを変更するとよいでしょう
マシュー

96

OP(2012年4月に更新された質問の改訂版)は、たとえばC ++ vector<>コンテナーで実行できるような、一定の償却期間でリストに追加する方法があるかどうかを知りたいと考えています。ここでの最良の答えは、固定サイズの問題が与えられた場合のさまざまなソリューションの相対的な実行時間のみを示していますが、さまざまなソリューションのアルゴリズム効率に直接対処していません。多くの回答の下のコメントでは、一部のソリューションのアルゴリズムの効率について説明していますが、これまでのすべての場合(2015年4月現在)では、誤った結論に達しています。

アルゴリズムの効率は、問題のサイズが大きくなるにつれて、時間(実行時間)または領域(消費されるメモリの量)のいずれかで、成長特性をキャプチャします。固定サイズの問題を前提として、さまざまなソリューションのパフォーマンステストを実行しても、さまざまなソリューションの成長率には対応できません。OPは、「一定の償却時間」でオブジェクトをRリストに追加する方法があるかどうかを知りたいと考えています。どういう意味ですか?説明するには、まず「一定の時間」について説明します。

  • 一定またはO(1)の成長:

    特定のタスクを実行するために必要な時間がdoublesの問題のサイズと同じである場合、アルゴリズムは一定の時間の増加を示すか、「Big O」表記でO(1)時間の増加を示します。OPが「償却済み」の一定の時間を表す場合、彼は単に「長期的に」を意味します...つまり、単一の操作の実行に通常よりもはるかに長い時間がかかる場合(たとえば、事前に割り当てられたバッファーが使い果たされ、大きなサイズに変更する必要がある場合)バッファーサイズ)、長期平均パフォーマンスが一定時間である限り、それをO(1)と呼びます。

    比較のために、「線形時間」と「二次時間」についても説明します。

  • 線形またはO(n)成長:

    時間が与えられたタスクを実行するために必要な場合はダブルス問題の大きさとダブルス、そして我々は、アルゴリズムの展示を言う時、線形、またはO(n)の成長。

  • 二次またはO(n 2成長:

    特定のタスクを実行するために必要な時間が問題サイズの2乗で増加する場合、それらはアルゴリズムが2次時間またはO(n 2)の成長を示すと言います。

アルゴリズムには他にも多くの効率クラスがあります。詳細については、Wikipediaの記事参照してください

私はRに不慣れであり、このページに提示されているさまざまなソリューションのパフォーマンス分析を行うための完全に構築されたコードブロックがあったので、@ CronAcronisに答えてくれました。私は分析のために彼のコードを借りていますが、これを以下のように(関数にラップして)複製しています:

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によって投稿された結果a <- list(a, list(i))は、少なくとも問題のサイズが10000の場合、この方法が最も速いことを明らかに示唆しているように見えますが、単一の問題のサイズの結果はソリューションの成長に対応していません。そのためには、問題のサイズが異なる、少なくとも2つのプロファイリングテストを実行する必要があります。

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

まず、min / lq / mean / median / uq / max値についての言葉:5回の実行のそれぞれに対してまったく同じタスクを実行しているので、理想的な世界では、まったく同じになることが期待できます各実行の時間。ただし、最初の実行は、テストしているコードがまだCPUのキャッシュにロードされていないため、通常はより長い時間に偏っています。最初の実行に続いて、時間はかなり一貫していると予想しますが、タイマーティック割り込みまたはテストしているコードに関係のない他のハードウェア割り込みにより、コードがキャッシュから追い出されることがあります。コードスニペットを5回テストすることで、最初の実行中にコードをキャッシュにロードし、各スニペットに外部イベントからの干渉なしに実行が完了するまで4回のチャンスを与えることができます。このために、

最初は2000の問題サイズで実行し、次に20000で実行することを選択したため、問題のサイズは最初の実行から2回目に10倍に増加しました。

listソリューションのパフォーマンス:O(1)(一定時間)

最初にlistソリューションの成長を見てみましょう。どちらのプロファイリング実行でも最速のソリューションであることがすぐにわかります。最初の実行では、2000の「追加」タスクを実行するのに854 マイクロ秒(0.854 ミリ秒)かかりました。2回目の実行では、20000の「追加」タスクを実行するのに8.746ミリ秒かかりました。ナイーブなオブザーバーは、「ああ、list問題のサイズが10倍になったので、テストの実行に必要な時間も増えたので、解はO(n)の成長を示します」と言うでしょうその分析の問題は、OPが必要としているのは、全体の問題の成長率ではなく、単一オブジェクト挿入の成長率であるということです。それを知っていれば、それは明らかですlist ソリューションは、OPが望むものを正確に提供します。オブジェクトをO(1)時間でリストに追加する方法です。

他のソリューションのパフォーマンス

他のソリューションはどれもソリューションの速度に近いものはありませんが、listとにかくそれらを調べることは有益です:

他のソリューションのほとんどは、パフォーマンスがO(n)のようです。たとえば、by_index私が他のSO投稿で見つけた頻度に基づく非常に人気のあるソリューションは、2000個のオブジェクトを追加するのに11.6ミリ秒かかり、10倍のオブジェクトを追加するのに953ミリ秒かかりました。全体的な問題の時間は100倍に増加したため、ナイーブな観察者は「ああ、by_index解はO(n 2)の増加を示します。問題のサイズが10倍に増加すると、テストの実行に必要な時間が増加したため100倍です。」以前と同様に、OPは単一オブジェクトの挿入の増加に関心があるため、この分析には欠陥があります。全体の時間の増加を問題のサイズの増加で除算すると、追加オブジェクトの時間の増加は100の係数ではなく10の係数で増加することがわかります。これは問題のサイズの増加と一致するため、by_index解決策はOです。 (n)。単一のオブジェクトを追加するためにO(n 2)の増加を示すソリューションはリストされていません。


1
読者に:上記の私の調査結果に非常に実用的な拡張を提供JanKanisの答えを、読んでください、とR.のC実装の内部動作与えられた様々なソリューションのオーバーヘッドに少しダイブ
phonetagger

4
リストオプションが必要なものを実装していることを確認していない:> length(c(c(c(list(1))、list(2))、list(3)))[1] 3> length(list(list(list(list (list(1))、list(2))、list(3)))[1] 2.ネストされたリストのように見えます。
ピカルス2016年

@Picarus-私はあなたが正しいと思います。私はもうRで作業していませんが、ありがたいことにJanKanisはより便利なO(1)ソリューションを使用して回答を投稿し、特定した問題を指摘しています。JanKanisはあなたの投票に感謝するでしょう。
Phonetagger、2016年

@phonetagger、回答を編集する必要があります。誰もがすべての答えを読むわけではありません。
ピカルス2016年

「単一の回答では実際の質問に対応していません」->問題は、元の質問がアルゴリズムの複雑さに関するものではなかったことです。質問の版を見てください。OPは最初にリストに要素を追加する方法を尋ね、数か月後に質問を変更しました。
Carlos Cinelli、2016年

41

他の回答では、listアプローチのみがO(1)の追加をもたらしますが、結果は深くネストされたリスト構造になり、プレーンな単一のリストにはなりません。私は以下のデータ構造を使用しましたが、それらはO(1)(償却済み)の追加をサポートしており、結果をプレーンリストに変換して戻すことができます。

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
}

次のように使用します。

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

[[2]]
[1] "world"

[[3]]
[1] 101

これらのソリューションは、それ自体でリスト関連の操作をサポートする完全なオブジェクトに拡張できますが、それは読者の課題として残ります。

名前付きリストの別のバリアント:

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
}

ベンチマーク

@phonetaggerのコード(@Cron Arconisのコードに基づく)を使用したパフォーマンスの比較。aも追加better_env_as_containerenv_as_container_、少し変更しました。オリジナルenv_as_container_は壊れていて、実際にはすべての数値を格納していません。

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
    }

結果:

> 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

私が追加したlinkedListexpandingListし、両方のインライン化バージョン。これinlinedLinkedListは基本的にのコピーですlist_が、ネストされた構造をプレーンリストに変換します。それ以上に、インラインバージョンと非インラインバージョンの違いは、関数呼び出しのオーバーヘッドによるものです。

のすべてのバリアントexpandingListlinkedListO(1)追加パフォーマンスを示し、追加されたアイテムの数に比例してベンチマーク時間スケーリングを行います。linkedListはより遅くexpandingList、関数呼び出しのオーバーヘッドも表示されます。したがって、得ることができるすべての速度が本当に必要な場合(およびRコードにこだわりたい場合)は、インラインバージョンのを使用してくださいexpandingList

私はまた、RのC実装を確認しました。どちらのアプローチも、メモリが不足するまで、任意のサイズでO(1)を追加する必要があります。

私も変更env_as_container_しました。元のバージョンはすべてのアイテムをインデックス「i」の下に保存し、以前に追加されたアイテムを上書きします。better_env_as_container私が追加したが非常によく似ているenv_as_container_が、なしのdeparseもの。どちらもO(1)パフォーマンスを示しますが、リンク/拡張リストよりもかなり大きなオーバーヘッドがあります。

メモリのオーバーヘッド

CRの実装では、割り当てられたオブジェクトごとに4ワードと2整数のオーバーヘッドがあります。このlinkedListアプローチは、追加ごとに長さ2のリストを1つ割り当て、64ビットコンピューターで追加アイテムごとに合計(4 * 8 + 4 + 4 + 2 * 8 =)56バイト(メモリ割り当てオーバーヘッドを除くため、おそらく64に近い)バイト)。このexpandingListアプローチでは、追加されたアイテムごとに1ワードと、ベクターの長さを2倍にしたときにコピーが使用されるため、合計メモリ使用量はアイテムごとに最大16バイトになります。メモリはすべて1つまたは2つのオブジェクト内にあるため、オブジェクトごとのオーバーヘッドはわずかです。envメモリ使用量については詳しく調べていませんが、に近いと思いますlinkedList


解決しようとしている問題が解決しない場合に、リストオプションを維持する意味は何ですか?
ピカルス2016年

1
@Picarusどういう意味かわかりません。なぜそれをベンチマークにしたのですか?他のオプションとの比較として。このlist_オプションはより高速で、通常のリストに変換する必要がない場合、つまり結果をスタックとして使用する場合に役立ちます。
JanKanis 2016年

@Gabor Csardiは、環境をリストに戻すより速い方法を別の質問でstackoverflow.com/a/29482211/264177に投稿しました。私のシステムでも同様にそれをベンチマークしました。これは、better_env_as_containerの約2倍の速度ですが、linkedListおよびexpandedListよりも低速です。
JanKanis 2016年

深くネストされた(n = 99999)リストは、特定のアプリケーションでは扱いやすく、許容できるようです:誰でもnestoRのベンチマークを行いたいですか?(私はまだenvironmentnestoRに使用したものに少し戸惑っています。)私のボトルネックは、ほとんどの場合、コーディングとデータ分析に費やした人間の時間ですが、この投稿で見つけたベンチマークに感謝します。メモリのオーバーヘッドについては、アプリケーションではノードあたり約kBまで気にしません。私は大きな配列などにしがみつく
アナニンバス

17

Lispでは、次のようにしました。

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

「c」だけでなく「cons」でした。空のリストから始める必要がある場合は、l <-NULLを使用します。


3
優れた!他のすべてのソリューションは、奇妙なリストのリストを返します。
metakermit 2013年

4
Lispでは、リストの前に追加することはO(1)操作であり、追加はO(n)、@ fliesで実行されます。復帰の必要性は、パフォーマンスの向上よりも重要です。これはRには当てはまりません。ペアリストでは、一般的にリストリストに最も似ています。
Palec、2015年

@Palec「これはRには当てはまりません」-どの「これ」を参照しているのかわかりません。追加がO(1)ではない、またはO(n)ではないということですか?
2015年

1
Lispでコーディングしている場合、@ fliesのアプローチは効率が悪いと言っています。その発言は、答えがそのまま書かれている理由を説明するためのものでした。Rでは、2つのアプローチはパフォーマンス面で同等です(AFAIK)。しかし、今は償却後の複雑さについてはわかりません。以前のコメントが書かれた頃からRに触れていません。
Palec、2015年

3
Rでは、このアプローチはO(n)になります。c()機能の新しいベクトル/リストにコピーその引数をして戻っています。
JanKanis 2016年

6

あなたは多分このようなものが欲しいですか?

> 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

これは非常に丁寧な機能ではありません(割り当てparent.frame()は一種の失礼です)が、IIUYCはあなたが求めているものです。


6

ここで述べた方法を少し比較しました。

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

結果:

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 

これはすばらしい情報です。勝者だけでなく、1から2桁または大きさであるとは決して想像もしてlist = listいませんでした。
javadba

5

リスト変数を引用符付きの文字列として渡すと、次のように関数内からアクセスできます。

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

そう:

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

[[2]]
[1] 2

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

[[2]]
[1] 2

[[3]]
[1] 3

> 

または追加のクレジット:

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

1
これは基本的に私が望む動作ですが、それでも内部的に追加を呼び出し、結果としてO(n ^ 2)のパフォーマンスになります。
Nick

4

最初の方法が機能しないと思われる理由がわかりません。lappend関数にバグがあります:length(list)はlength(lst)でなければなりません。これは正常に動作し、objが追加されたリストを返します。


3
あなたは、絶対に正しい。コードにバグがあり、修正しました。私はlappend()提供したものをテストしましたが、c()やappend()とほぼ同じように動作するようです。これらはすべてO(n ^ 2)の動作を示します。
Nick


2

あなたがしたいことは、実際には参照(ポインタ)によって関数渡されることだと思います- リストに追加された新しい環境(関数への参照によって渡される)を作成します。

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
}

これで、既存のリストを変更するだけです(新しいリストは作成しません)。


1
これは再び二次時間の複雑さを持っているように見えます。問題は、リスト/ベクトルのサイズ変更が、ほとんどの言語で通常実装されている方法では実装されていないことです。
2011

はい-最後に追加するのは非常に遅いようです-おそらくb / cリストは再帰的であり、Rはループタイプの演算ではなくベクトル演算に最適です。それを行う方がはるかに良い:
DavidM

1
system.time(for(i in c(1:10000)mylist [i] = i)(数秒)、またはそれをすべて1回の操作で実行する:system.time(mylist = list(1:100000)) (1秒未満)、forループで事前に割り当てられたリストを変更することも高速になります
DavidM

2

これは、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"]

またはプログラムで:

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

これは実際には追加されていません。100個のオブジェクトがあり、それらをプログラムでリストに追加したい場合はどうなりますか?Rにはappend()関数がありますが、これは実際には連結関数であり、ベクトルに対してのみ機能します。
Nick

append()ベクトルとリストで動作し、それは本当の追加です(これは基本的に連結と同じなので、問題が何であるかはわかりません)
ハドリー

8
追加関数は、新しいオブジェクトを作成するのではなく、既存のオブジェクトを変更する必要があります。真の追加では、O(N ^ 2)の動作はありません。
ニック

2

実際には、c()機能を持つサブテルティがあります。もし、するなら:

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

期待どおりに取得します。

[[1]]
[1]

[[2]]
[1] "foo"

しかし、で行列を追加するとx <- c(x, matrix(5,2,2)、リストにはさらに4つの要素の値が含まれます5。あなたはもっと良いでしょう:

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

それは他のオブジェクトでも機能し、期待どおりに取得します:

[[1]]
[1]

[[2]]
[1] "foo"

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

最後に、関数は次のようになります。

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

そして、それはあらゆるタイプのオブジェクトに対して機能します。あなたはより賢くなり、次のことができます:

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

1

ありますlist.appendからrlistドキュメントへのリンク

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

とてもシンプルで効率的です。


1
私にはRのように見えません... Python?
JD Long

1
私は編集してそれを試してみました。c()or listメソッドを使用することをお勧めします。どちらもはるかに高速です。
第五

のコードを見ると、rlist::list.append()本質的にはのラッパーbase::c()です。
nbenn

1

検証のために、@ Cronが提供するベンチマークコードを実行しました。(新しいi7プロセッサー上でより速く実行することに加えて)一つの大きな違いがあります:by_index今、行ってほぼ同様に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

参照用に、@ Cronの回答からベンチマークコードをそのままコピーしました(後で内容を変更した場合に備えて):

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
これは、OPが探していた種類の追加ではないと思います。
joran

これはリストの要素を追加するものではありません。ここでは、リストの唯一の要素である整数ベクトルの要素を増やしています。リストには1つの要素、整数ベクトルしかありません。
セルジオ

0

これは非常に興味深い質問であり、以下の私の考えがそれを解決する方法に貢献できることを願っています。このメソッドは、インデックス付けなしでフラットリストを提供しますが、ネスト構造を回避するためにリストとリスト解除があります。ベンチマークの方法がわからないので、速度はわかりません。

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

追加したいのは、2レベルのネストされたリストを提供するということですが、それだけです。リストとリスト解除の動作方法は私にはあまり明確ではありませんが、これはコードをテストした結果です
xappppp

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.