핑거 트리 구조 부트 스트랩


16

2 ~ 3 개의 손가락 나무로 작업 한 후 나는 대부분의 작업에서 그들의 속도에 깊은 인상을 받았습니다. 그러나 내가 겪었던 한 가지 문제는 큰 손가락 나무의 초기 생성과 관련된 큰 오버 헤드입니다. 건물은 일련의 연결 작업으로 정의되므로 불필요한 손가락 트리 구조를 많이 만들게됩니다.

2-3 손가락 나무의 복잡한 특성으로 인해 부트 스트래핑을위한 직관적 인 방법이 없으며 모든 검색 결과가 비어 있습니다. 문제는 최소한의 오버 헤드로 2-3 핑거 트리를 부트 스트래핑하는 방법에 대한 것입니다.

명시 적으로 , 알려진 길이 n 의 시퀀스 S 가 주어지면 최소한의 조작 으로 S 의 핑거 트리 표현이 생성 됩니다.SnS

달성하는 순진한 방법은 cons 작업에 대한 연속적인 호출입니다 (문헌에서 ' '연산자). 그러나 이것은 [ 1 .. i ]에 대한 S의 모든 슬라이스를 나타내는 n 개의 분리 된 핑거 트리 구조를 생성 합니다.nS[1..i]


1
않는 간단한 범용 데이터 구조 : 손가락 나무를 HINZE와 패터슨로는 답변을 제공?
Dave Clarke

@Dave 나는 실제로 그들의 논문을 구현했고, 그들은 효율적인 창조를 다루지 않았다.
jbondeson

나는 많이 생각했다.
Dave Clarke

이 경우 "빌드"의 의미에 대해 좀 더 구체적으로 설명해 주시겠습니까? 이 폴더가 펴 집니까?
jbapple

@jbapple-혼란스러워서 더 명확하게 편집했습니다.
jbondeson

답변:


16

GHC의 Data.Sequencereplicate기능 에 fingertree 빌드 시간과 공간을, 그러나 이것은 GET-이동에서 손가락 트리의 오른쪽 척추에가는 요소를 알고으로 활성화되어 있습니다. 이 도서관은 원본 종이의 저자가 2-3 손가락 나무에 썼습니다.O(lgn)

반복적으로 연결하여 핑거 트리를 만들려면 등뼈의 표현을 변경하여 건축하는 동안 일시적 공간 사용을 줄일 수 있습니다. 2 ~ 3 개의 손가락 나무의 등뼈는 동기화 된 단일 연결 목록으로 영리하게 저장됩니다. 대신, 척추를 deque로 저장하면 나무를 연결할 때 공간을 절약 할 수 있습니다. 아이디어는 같은 높이의 두 나무를 연결 하면 나무의 등뼈를 재사용하여 공간을 차지한다는 것입니다 . 원래 설명 된대로 2-3 개의 손가락 나무를 연결할 때 새 나무의 내부에있는 가시를 더 이상 그대로 사용할 수 없습니다.영형(1)

Kaplan과 Tarjan의 "분류 가능한 정렬 목록의 순수한 기능 표현" 은보다 복잡한 핑거 트리 구조를 설명합니다. 이 논문 (섹션 4)은 위에서 언급 한 deque 제안과 유사한 구성에 대해서도 설명합니다. 나는 그들이 묘사 한 구조가 시간과 공간 에서 같은 높이의 두 나무를 연결할 수 있다고 믿는다 . 손가락 나무를 만들 때 공간이 충분합니까?영형(1)

주의 : "부트 스트래핑"이라는 단어의 사용은 위의 사용과 조금 다른 것을 의미합니다. 이는 동일한 구조의 더 간단한 버전을 사용하여 데이터 구조의 일부를 저장하는 것을 의미합니다.


매우 흥미로운 아이디어입니다. 이것에 대해 살펴보고 전반적인 데이터 구조와의 타협점을 확인해야합니다.
jbondeson

나는이 대답에 두 가지 아이디어가 있음을 의미했다. 입력이 배열이면 복제 아이디어가 여분의 공간에서 손가락 나무를 만들 수 있다고 생각합니다.
jbapple

네, 둘 다 봤어요 미안하지만 둘 다에 대해 언급하지 않았습니다. 복제 코드를 먼저 살펴보고 있지만 Haskell 지식을 확실히 확장하고 있습니다. 처음에 홍당무는 빠른 임의 액세스 권한이 있다면 내가 가진 대부분의 문제를 해결할 수있는 것처럼 보입니다. 임의 액세스가없는 경우 빠른 연결은 좀 더 일반적인 솔루션 일 수 있습니다.
jbondeson

10

jbapple의 훌륭한 답변에 Riffing에 관한 replicate하지만, 사용 replicateA(하는 replicate대신에, 나는 다음과 같은 내놓았다에 내장되어 있습니다) :

--Unlike fromList, one needs the length explicitly. 
myFromList :: Int -> [b] -> Seq b
myFromList l xs = flip evalState xs $ Seq.replicateA l go
    where go = do
           (y:ys) <- get
            put ys
            return y

myFromList(약간보다 효율적인 버전에서)는 이미 정의되어Data.Sequence 있으며 일종의 결과 인 핑거 트리를 구성하기 위해 내부적 으로 사용됩니다 .

일반적으로 직관 replicateA은 간단합니다. applicativeTree 함수 replicateA위에 구축되었습니다 . size의 나무 조각 을 가져 와서 이것의 사본을 포함하는 균형 잡힌 나무를 만듭니다. 의 경우 8에서 (하나 의 손가락)이 하드 코딩되어있다. 이 이상의 것은 재귀 적으로 호출됩니다. "적용 적"요소는 단순히 상기 코드의 경우와 같은 상태를 통해 스레딩 효과를 갖는 트리의 구성을 인터리빙한다는 것이다.applicativeTreemnnDeep

go복제 된 함수는 단순히 현재 상태를 가져 와서 요소를 맨 위에 표시하고 나머지를 대체하는 작업입니다. 따라서 각 호출에서 입력으로 제공된 목록을 한 단계 더 내려갑니다.

좀 더 구체적인 메모

main = print (length (show (Seq.fromList [1..10000000::Int])))

몇 가지 간단한 테스트에서 이로 인해 흥미로운 성능 트레이드 오프가 발생했습니다. 위의 주요 기능은 myFromList보다 약 1/3 낮았습니다 fromList. 반면, myFromList2MB의 상수 힙을 fromList사용 하고 표준 은 최대 926MB를 사용했습니다. 926MB는 전체 목록을 한 번에 메모리에 저장해야 할 때 발생합니다. 한편, 솔루션 myFromList은 지연 스트리밍 방식으로 구조를 소비 할 수 있습니다. 속도 문제는 myFromList(모나드 모나드의 쌍 구성 / 파괴 결과) 약 2 배의 할당을 수행해야 한다는 사실에서 비롯 됩니다.fromList. CPS 변환 상태 모나드로 이동하여 이러한 할당을 제거 할 수 있지만 게으름을 상실하면 비 스트리밍 방식으로 목록을 통과해야하기 때문에 주어진 시간에 훨씬 더 많은 메모리를 보유하게됩니다.

반면에 전체 시퀀스를 쇼로 강제하지 않고 헤드 또는 마지막 요소를 추출하여 myFromList즉시 더 큰 승리를 제시하면 헤드 요소를 추출하는 것이 거의 즉각적이며 마지막 요소를 추출하는 것은 0.8입니다. . 한편, 표준 fromList에서는 헤드 또는 마지막 요소를 추출하는 데 ~ 2.3 초가 소요됩니다.

이것은 모든 세부 사항이며 순도와 게으름의 결과입니다. 돌연변이와 무작위 접근이 가능한 상황에서는 replicate해결책이 엄청나게 더 좋다고 생각합니다 .

그러나, 다시 할 수있는 방법이 있는지 여부의 문제 제기 않는 applicativeTreemyFromList엄격하게 더 효율적입니다. 문제는 적용 작업이 트리와 다른 순서로 실행된다는 것입니다. 그러나 자연스럽게 통과하는 방법이 있지만이 작동 방법이나 해결 방법이 있는지는 완전히 알지 못했습니다.


4
(1) 흥미 롭습니다. 이 작업을 수행 하는 올바른 방법 처럼 보입니다 . 나는 이것이 fromList전체 시퀀스가 ​​강제 될 때보 다 느리다는 것을 듣고 놀랐습니다 . (2)이 답변은 cstheory.stackexchange.com에 너무 코드가 많고 언어에 따라 다를 수 있습니다. replicateA언어 독립적 인 방식으로 작동 하는 방식에 대한 설명을 추가 할 수 있다면 좋을 것 입니다.
이토 쓰요시

9

많은 수의 중간 핑거 트리 구조를 사용하는 동안 대부분의 구조를 서로 공유합니다. 결국 이상적인 경우보다 최대 두 배의 메모리를 할당하고 나머지는 첫 번째 컬렉션에서 해제됩니다. 이것의 점근선은 끝에 n 값으로 채워진 핑거 트리가 필요하기 때문에 얻을 수있는 것과 동일합니다.

당신은 사용하여 fingertree을 구축 할 수 있습니다 Data.FingerTree.replicate그들을 사용 FingerTree.fmapWithPos하여 유한 시퀀스의 역할을 배열에 값을 조회하거나 사용하여 traverseWithPos목록 또는 다른 알려진 크기의 용기의 그들을 밖으로 껍질.

O(logn)O(n)영형(로그) 가비지 수집이 정리 될 때까지 메모리를 유지하므로 최적의 ~ 1000 노드 대신 ~ 1010의 비용을 지불해야합니다.

마지막으로,에서 복제를 사용하지 않고 해당 중간 생성을 피할 수 있습니다 영형(로그)메모리를 사용하여 트리를 복제 replicateA하지만, @sclv가 언급 한 바와 같이,에 국가 고유 조작에 대한 tupling mapAccumL또는 상태와 횡단은 자신이 실제로 단지 모든 여분의 제품 세포에 대한 지불에 비례하여 유사한 오버 헤드를 소개합니다 모나드.

TL; DR이 작업을 수행해야 할 경우 다음을 사용합니다.

rep :: (Int -> a) -> Int -> Seq a 
rep f n = mapWithIndex (const . f) $ replicate n () 

고정 크기의 배열로 색인 (arr !)하기 위해 f위의 것만 제공 합니다 .

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