상각 된 상수 시간 O (1)에서 R의 목록에 오브젝트를 추가 하시겠습니까?


245

R list 가 있으면 다음과 같이 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 Kim

답변:


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-Feb-01) : 이 게시물은 다섯 번째 생일에 올라옵니다. 어떤 종류의 독자들은 계속해서 그 단점을 반복하므로, 아래의 주석도 참조하십시오. list유형에 대한 한 가지 제안 :

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

일반적으로 R 유형은 모든 유형 및 용도에 대해 하나의 관용구를 갖기가 어려울 수 있습니다.


19
이것은 추가되지 않습니다 ... 연결됩니다. 호출 된 LL후에도 여전히 두 가지 요소 C(LL, c="harry")가 있습니다.
Nick

27
LL :으로 다시 지정하십시오 LL <- c(LL, c="harry").
Dirk Eddelbuettel

51
이것은 문자열에서만 작동합니다. a, b 및 c가 정수 벡터이면 동작이 완전히 다릅니다.
Alexandre Rademaker

8
@ Dirk : 당신은 parens가 나와 다르게 중첩되어 있습니다. 에 대한 호출 c()에는 두 가지 인수가 있습니다. 추가하려는 목록, 즉 list(a=3, b=c(4, 5))추가하려는 항목, 즉 c=c(6, 7). 내 접근 방식을 사용하는 경우, 당신은 것을 볼 수 2 목록 항목이 추가 (하는 67, 이름 c1c2) 대신이라는 단일 2 요소 벡터의 c명확하게 의도로!
j_random_hacker

7
결론은 mylist <- list(mylist, list(obj))? 그렇다면 대답을 수정하는 것이 좋을 것입니다.
Matthew

96

OP (2012 년 4 월 업데이트 된 질문 개정판)는 예를 들어 C ++ vector<>컨테이너를 사용하여 할 수있는 것처럼 상각 된 일정한 시간에 목록에 추가하는 방법이 있는지 알고 싶어 합니다. 지금까지 가장 좋은 답변은 고정 크기 문제가있는 다양한 솔루션의 상대 실행 시간 만 보여 주지만 다양한 솔루션의 알고리즘 효율성을 직접적으로 다루지는 않습니다 . 많은 답변 아래의 의견은 일부 솔루션의 알고리즘 효율성에 대해 설명하지만 모든 경우 (2015 년 4 월 현재) 잘못된 결론에 도달합니다.

알고리즘 효율성은 문제 크기가 커짐에 따라 시간 (실행 시간) 또는 공간 (소비되는 메모리 양)의 성장 특성을 캡처합니다 . 고정 된 크기의 문제가있는 경우 다양한 솔루션에 대한 성능 테스트를 실행해도 다양한 솔루션의 성장률이 해결되지 않습니다. OP는 "암시 된 상수 시간"으로 R 목록에 개체를 추가하는 방법이 있는지 알고 싶어합니다. 그게 무슨 뜻이야? 설명하기 위해 먼저 "일정한 시간"을 설명하겠습니다.

  • 지속적 또는 O (1) 성장 :

    주어진 작업을 수행하는 데 필요한 시간 이 문제의 크기가 두 배인 것과 동일 하게 유지되면 알고리즘이 일정한 시간 성장을 보이 거나 "Big O"표기법에 명시된 것처럼 O (1) 시간 성장을 나타냅니다. OP가 "정확한"일정 시간이라고 말하면 "장기적으로"를 의미합니다. 즉, 단일 작업을 수행 할 때 가끔 정상보다 훨씬 오래 걸리는 경우 (예 : 사전 할당 된 버퍼가 소진되고 때로는 더 큰 크기로 조정해야하는 경우) 버퍼 평균), 장기 평균 성능이 일정한 시간 인 한 여전히 O (1)이라고합니다.

    비교를 위해 "선형 시간"과 "2 차 시간"도 설명합니다.

  • 선형 또는 O (n) 성장 :

    문제의 크기가 두 배가 되므로 주어진 작업을 수행하는 데 필요한 시간이 두 배가 되면 알고리즘이 선형 시간 또는 O (n) 증가를 나타냅니다.

  • 2 차 또는 O (n 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 인 경우 방법이 가장 빠르다는 것을 암시 하지만 단일 문제 크기의 결과는 솔루션의 성장을 해결하지 못합니다. 이를 위해 문제 크기가 다른 최소 두 개의 프로파일 링 테스트를 실행해야합니다.

> 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의 문제 크기로 먼저 실행하기로 결정 했으므로 문제의 크기는 첫 번째 실행에서 두 번째 실행으로 10 배 증가했습니다.

list솔루션의 성능 : O (1) (일정 시간)

list두 프로파일 링 실행에서 가장 빠른 솔루션임을 즉시 알 수 있으므로 솔루션 의 성장을 먼저 살펴 보겠습니다 . 첫 번째 실행에서 2000 "추가"작업을 수행 하는 데 854 마이크로 초 (0.854 밀리 초)가 걸렸습니다 . 두 번째 실행에서는 20000 "추가"작업을 수행하는 데 8.746 밀리 초가 걸렸습니다. 순진한 관찰자는 "아, list문제 크기가 10 배 증가함에 따라 솔루션이 O (n) 성장을 보이는데, 이는 테스트를 실행하는 데 필요한 시간도 있었기 때문"이라고 말했다. 이 분석의 문제점은 OP가 원하는 것은 전체 문제의 성장률이 아니라 단일 객체 삽입 의 성장률이라는 것입니다. 그것을 알고, 그것은 분명하다list 솔루션 OP가 원하는 것을 정확하게 제공한다는 것입니다. 즉, O (1) 시간 내에 객체를 목록에 추가하는 방법입니다.

다른 솔루션의 성능

다른 솔루션은 솔루션의 속도에 가깝지 list않지만 어쨌든 솔루션을 조사하는 것이 유익합니다.

다른 솔루션의 대부분은 성능면에서 O (n) 인 것으로 보입니다. 예를 들어, by_index다른 SO 게시물에서 찾은 빈도에 따라 매우 인기있는 솔루션 인 솔루션은 2000 개의 객체를 추가하는 데 11.6 밀리 초가 걸리고 그 많은 객체의 10 배를 추가하는 데 953 밀리 초가 걸렸습니다. 전체 문제의 시간이 100 배 증가 했으므로 순진한 관찰자는 "아, 문제 크기가 10 배 증가함에 따라 테스트 실행에 걸리는 시간 이 늘어났기 때문에 솔루션의 O (n 2 ) 증가가 나타납니다. "100 배가됩니다."by_index이전과 마찬가지로 OP가 단일 객체 삽입의 성장에 관심이 있기 때문에이 분석에는 결함이 있습니다. 전체 시간 증가를 문제의 크기 증가로 나눈 경우, 개체 추가의 시간 증가는 문제 크기의 증가와 일치하는 100 배가 아닌 10 배만 증가하므로 문제 by_index는 O입니다. (엔). 단일 대상체를 추가하기 위해 O (n 2 ) 성장을 나타내는 용액은 열거되어 있지 않습니다 .


1
독자에게 : JanKanis의 답변을 읽으십시오. 위의 결과에 대한 실질적인 확장을 제공하고 R의 C 구현의 내부 작업을 감안할 때 다양한 솔루션의 오버 헤드로 조금 들어갑니다.
phonetagger

4
list 옵션이 필요한 것을 구현하는지 확실하지 않은 경우 :> length (c (c (c (c (list (1)), list (2)), list (3))) [1] 3> length (list (list (list) (list (1)), list (2)), list (3))) [1] 2. 중첩 된 목록처럼 보입니다.
Picarus

@Picarus-당신이 옳다고 생각합니다. 더 이상 R과 함께 일하고 있지는 않지만 JanKanis는 훨씬 유용한 O (1) 솔루션으로 답변을 게시하고 확인한 문제를 지적합니다. JanKanis가 귀하의 공감대에 감사 할 것입니다.
phonetagger

@phonetagger, 답변을 수정해야합니다. 모든 사람이 모든 답을 읽을 수있는 것은 아닙니다.
Picarus

"단일 답변이 실제 질문을 해결하지 못했습니다"-> 문제는 원래 질문이 알고리즘 복잡성에 관한 것이 아니라는 것입니다. 질문의 판을보십시오. OP는 몇 개월 후 질문을 변경 한 것보다 목록에 요소를 추가하는 방법을 먼저 요청했습니다.
Carlos Cinelli 2016 년

41

다른 답변에서는 list접근 방식 만 O (1) 추가하지만 결과는 평범한 단일 목록이 아니라 깊이 중첩 된 목록 구조가됩니다. 아래 데이터 구조를 사용했으며 O (1) (amortized) 추가를 지원하며 결과를 일반 목록으로 다시 변환 할 수 있습니다.

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_container변경했습니다 env_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

나는 둘 다 추가 linkedList하고 expandingList인라인 버전을 추가했습니다. 는 inlinedLinkedList기본적으로의 복사 list_뿐만 아니라 일반 목록으로 중첩 된 구조를 다시 변환합니다. 인라인 버전과 인라인되지 않은 버전의 차이점은 함수 호출의 오버 헤드 때문입니다.

O (1) 의 모든 변형 expandingListlinkedList성능은 벤치 마크 시간이 추가 된 항목 수에 따라 선형으로 조정되는 성능을 추가합니다. 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방법은 64 비트 컴퓨터에서 추가 된 항목 당 총 (4 * 8 + 4 + 4 + 2 * 8 =) 56 바이트에 대해 추가 당 길이 2의 목록 하나를 할당합니다 (메모리 할당 오버 헤드 제외, 아마도 64에 근접) 바이트). 이 expandingList접근법은 추가 된 항목 당 하나의 단어와 벡터 길이를 두 배로 늘릴 때 사본을 사용하므로 항목 당 총 16 바이트의 총 메모리 사용량 메모리가 모두 하나 또는 두 개의 오브젝트에 있기 때문에 오브젝트 별 오버 헤드는 중요하지 않습니다. env메모리 사용에 대해 자세히 살펴 보지 않았지만 더 가까이에 있다고 생각합니다 linkedList.


해결하려는 문제를 해결할 수없는 경우 목록 옵션을 유지하는 요점은 무엇입니까?
피카 루스

1
@Picarus 무슨 말인지 잘 모르겠습니다. 벤치 마크에 유지 한 이유는 무엇입니까? 다른 옵션과 비교하여. 이 list_옵션은 더 빠르며 일반 목록으로 변환 할 필요가없는 경우 (예 : 결과를 스택으로 사용하는 경우) 유용 할 수 있습니다.
JanKanis

@Gabor Csardi는 stackoverflow.com/a/29482211/264177에서 환경을 목록으로 다시 변환하는 더 빠른 방법을 게시했습니다. 내 시스템에서도 벤치마킹했습니다. 그것은 better_env_as_container보다 두 배 빠르지 만 여전히 linkedList 및 expandList보다 느립니다.
JanKanis

중첩 (N = 99,999) 목록은 특정 응용 프로그램에 대한 관리 및 허용 같다 : 사람의 희망을 벤치 마크에 네스터 ? (나는 아직도 environmentnestoR에 사용한 것들에 대해 약간의 멍청한 놈 입니다.) 병목 현상은 거의 항상 인간이 코딩하고 데이터를 분석하는 데 소요되는 시간이지만,이 게시물에서 찾은 벤치 마크에 감사드립니다. 메모리 오버 헤드와 관련하여 내 응용 프로그램의 노드 당 최대 kB는 신경 쓰지 않습니다. 나는 큰 배열을 붙잡고 있습니다.
Ana Nimbus

17

Lisp에서 우리는 다음과 같이했습니다.

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

'c'뿐만 아니라 'cons'였습니다. empy 목록으로 시작해야하는 경우 l <-NULL을 사용하십시오.


3
우수한! 다른 모든 솔루션은 이상한 목록을 반환합니다.
metakermit 2013

4
Lisp에서 목록 앞에 추가하는 것은 O (1) 연산이며 O (n), @flies로 실행을 추가합니다. 복귀의 필요성은 성능 향상으로 인해 중요합니다. R의 경우에는 해당되지 않습니다. 일반적으로 목록과 가장 유사한 페어리스트도 아닙니다.
Palec

@Palec "R의 경우는 아닙니다"-어떤 "이것"을 말하는지 잘 모르겠습니다. 추가가 O (1)이 아니거나 O (n)이 아니라고 말하는가?
운항

1
나는 당신이 Lisp로 코딩한다면, 당신의 접근 방식은 비효율적이라고 말하고 있습니다. 그 말은 왜 답이 그대로 쓰여 졌는지를 설명하기위한 것이었다. R에서 두 가지 접근 방식은 성능 측면에서 AFAIK입니다. 그러나 이제는 상각 된 복잡성에 대해 확신하지 못합니다. 내 이전 의견이 작성된 시점부터 R을 만지지 않았습니다.
Palec

3
R에서이 접근법은 O (n)입니다. 이 c()함수는 인수를 새로운 벡터 / 목록에 복사하여 반환합니다.
JanKanis

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 

이것은 훌륭한 정보입니다 : 결코list = list 승자가 아니라고 1 ~ 2 주문 또는 규모 까지 추측 하지 못했을 것입니다 !
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
이것은 기본적으로 원하는 동작이지만 여전히 내부적으로 append를 호출하여 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
이것은 다시 이차 시간 복잡성을 갖는 것으로 보인다. 문제는 목록 / 벡터 크기 조정이 일반적으로 대부분의 언어로 구현되는 방식으로 구현되지 않는다는 것입니다.
aold

예. 끝에 추가하는 것이 매우 느린 것 같습니다. 아마도 b / c 목록은 재귀 적이며 R은 루프 유형 연산보다는 벡터 연산에서 가장 좋습니다.
DavidM

1
system.time (for (i in c (1 : 10000) mylist [i] = i) (몇 초) 또는 한 번의 작업으로 모두 더 잘 수행하십시오 : system.time (mylist = list (1 : 100000)) (이하 초 이상)이면 수정 루프 용으로 미리 할당 된리스트는 더 빠를 것이다.
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()는 벡터와 목록에서 작동하며 실제 추가입니다 (기본적으로 연결과 동일하므로 문제가 무엇인지 알 수 없음)
hadley

8
추가 기능은 기존 객체를 변경하고 새 객체를 생성하지 않아야합니다. 실제 추가에는 O (N ^ 2) 동작이 없습니다.
Nick

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처럼 보이지 않습니다 ... 파이썬?
JD Long

1
나는 편집하고 그것을 시험해 보았다 : 그것은 엄청나게 느리다. c()또는 list-method를 사용하는 것이 좋습니다. 둘 다 더 빠릅니다.
5

의 코드를 살펴보면 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

이것은 목록에 요소를 추가하지 않습니다. 여기서 정수 벡터의 요소를 늘리고 있는데, 이는 목록의 유일한 요소입니다. 리스트에는 정수 벡터 인 하나의 요소 만 있습니다.
Sergio

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

내가 추가하고 싶은 것은 두 가지 수준의 중첩 목록을 제공한다는 것입니다. 리스트와리스트를 해제하는 방법은 나에게 분명하지 않지만, 이것은 코드를 테스트 한 결과입니다.
xappppp

-1

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

따라서 위의 코드를 사용하여 요소 / 객체를 쉽게 추가 할 수 있습니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.