tomprimozic / vector

A persistent list experiment

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Licence: public domain

This is an implementation of a persistent array data structure which can be used as a general-purpose random-access array in functional programming languages. It is modeled after Clojure's Vector, but supports amortized O(1) modification on both ends, and can therefore be used both as a stack and as a queue.

The basic idea is simple - we take 2 Clojure's Vectors and join them together; since Clojure's Vector supports efficient modification on one end (the tail), we can use 2 of them (joined together at the head) to support efficient modification on both sides.

  [tail  ...   tree] [tree  ...   tail]
    ↑     <- rotceV   Vector ->    ↑
    ┃                              ┃
    ┗━ efficiently `push` / `pop` ━┛

However, the devil is in the details. First of all, in order to enable structural sharing, we the direction of data on the Left and Right halves must be the same (so that we can move a subtree from one side to the other without copying), so we need to implement slightly different code for the Left and Right sub-vectors.

Additionally, we maintain the invariant that the depth of the tree on the Left and Right sides differ for at most 1 - therefore, we have to make sure to rebalance both halves as necessary. This is fairly straightforward when adding elements (push) but can get quite complicated when removing elements (pop):

  [tail  ...   tree]    [tree  ...   tail]
              /   \       |  \
             N     N      N   N
           / |   / |     / \
          N  N  N  N    N   N
                       /|   |\
                      N N   N N

If we pop_left on the tree above, we'll have to reduce the depth of the Left side (otherwise the root of the tree would have a single child). Then, the depth of the Left side will be 1, while the depth of the Right side will be 3, breaking the invariant. To maintain the invariant, we'll have to "steal" some data from the Right side. But if we take the whole left subtree of the Right tree (depth=3), we will break the invariant in the other direction (Left.depth=3 but Right.depth=1). Therefore, we have to split the left subtree of the Right tree.

Quick benchmark (miliseconds - less is better):

                    mean        std        min         max
Clojure
      append:     480.00     270.96        269        1057
         get:    2478.83     228.00       2113        2812
       stack:    2726.67     337.09       2230        3294
      update:    8958.67    1228.49       7749       10796
Scala2
      append:     719.67     195.52        506         991
         get:    1939.33     564.50       1275        2796
       queue:    3754.67     478.91       3189        4575
       stack:    5615.33    1227.48       4267        7940
      update:    7484.83     895.97       6926        9466
Vector
      append:     401.17      41.57        361         489
         get:    1815.17     309.99       1498        2413
       queue:    1637.17     410.76       1208        2317
       stack:    2924.83     422.32       2620        3857
      update:    8403.33    1265.10       6931        9791

About

A persistent list experiment


Languages

Language:Java 77.5%Language:Scala 22.5%