├── .Makefile.swp ├── .gitignore ├── Makefile ├── src ├── FingerTree.cs └── Spine.cs └── test └── SpineTest.cs /.Makefile.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexanderkyte/VerticalFingerTree/c55210b296f9a8740bedc5a234cc129497993a28/.Makefile.swp -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | 3 | .gch 4 | .dSYM 5 | .o 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | dest:=build 2 | 3 | ifneq "$(wildcard $(dest) )" "" 4 | $(shell "echo mkdir $(dest)") 5 | endif 6 | 7 | ifndef MONO 8 | MONO=mono 9 | endif 10 | 11 | .PHONY: run-test-all 12 | run-test-all: $(dest)/SpineTest.exe 13 | $(MONO) $(dest)/SpineTest.exe 14 | 15 | $(dest)/SpineTest.exe: test/SpineTest.cs $(dest)/Spine.dll 16 | mcs $(dest)/Spine.dll test/SpineTest.cs -out:$@ 17 | 18 | $(dest)/Spine.dll: src/Spine.cs 19 | mcs -target:library src/Spine.cs -out:$@ 20 | -------------------------------------------------------------------------------- /src/FingerTree.cs: -------------------------------------------------------------------------------- 1 | 2 | #define FINGER_TREE_32_BIT 3 | 4 | namespace FingerTree { 5 | public class SizedTree extends Tree 6 | { 7 | readonly Spine left; 8 | readonly Spine right; 9 | 10 | FingerTree 11 | query (Measure upper_bound) 12 | { 13 | val leftResult = left.query (upper_bound); 14 | if (leftResult != null) 15 | return leftResult; 16 | 17 | return right.query (upper_bound); 18 | } 19 | 20 | FingerTree 21 | query (Func predicate) 22 | { 23 | val leftResult = left.query (predicate); 24 | if (leftResult != null) 25 | return leftResult; 26 | 27 | return right.query (predicate); 28 | } 29 | 30 | const char fullRow = 0xF; 31 | const char emptyRow = 0xF; 32 | 33 | public SizedTree 34 | pushLeft (Value n) 35 | { 36 | if ( (left.schema | fullRow) == emptyRow) 37 | { 38 | right.pushLeft (n); 39 | } 40 | else 41 | { 42 | left.pushLeft (n); 43 | } 44 | } 45 | 46 | public SizedTree 47 | pushRight (Value n) 48 | { 49 | Measure measured = this.Measurer.eval (n); 50 | 51 | if ( (right.schema | fullRow) == emptyRow) 52 | { 53 | left.pushRight (n, measured); 54 | } 55 | else 56 | { 57 | right.pushRight (n, measured); 58 | } 59 | } 60 | 61 | public SizedTree 62 | popLeft () 63 | { 64 | if ( (left.schema | fullRow) == emptyRow) 65 | { 66 | right.popLeft (); 67 | } 68 | else 69 | { 70 | left.popLeft (); 71 | } 72 | } 73 | 74 | public SizedTree 75 | popRight () 76 | { 77 | if ( (right.schema | fullRow) == emptyRow) 78 | { 79 | left.popRight (); 80 | } 81 | else 82 | { 83 | right.popRight (); 84 | } 85 | } 86 | 87 | public SizedTree 88 | peekLeft () 89 | { 90 | if ( (left.schema | fullRow) == emptyRow) 91 | { 92 | right.peekLeft (); 93 | } 94 | else 95 | { 96 | left.peekLeft (); 97 | } 98 | } 99 | 100 | public SizedTree 101 | peekRight () 102 | { 103 | if ( (right.schema | fullRow) == emptyRow) 104 | { 105 | left.peekRight (); 106 | } 107 | else 108 | { 109 | right.peekRight (); 110 | } 111 | } 112 | 113 | } 114 | 115 | // Make a "Multimeasurer example" 116 | // This just has a tuple for values, 117 | // and different predicates are used 118 | // ex: indexed priority queue 119 | 120 | public interface Measurer 121 | { 122 | public Measure 123 | combine (Measure left, Measure right); 124 | 125 | public Measure 126 | eval (Value val); 127 | 128 | public Measure 129 | smallest (); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Spine.cs: -------------------------------------------------------------------------------- 1 | #define CHECKED // Whether to put pattern matches in try/catch 2 | 3 | #define SMALL_MASK 4 | 5 | using System; 6 | 7 | #if SMALL_MASK 8 | using Bitmask = System.Int32; 9 | #else 10 | using Bitmask = System.Int64; 11 | #endif 12 | 13 | namespace FingerTree { 14 | 15 | public struct MeasuredPtr 16 | { 17 | Value v; 18 | Measure m; 19 | } 20 | 21 | 22 | public class Node 23 | { 24 | public readonly Node near; 25 | public readonly Node mid; 26 | public readonly Node far; 27 | 28 | public Node (Node near, 29 | Node mid, Node far) 30 | { 31 | this.near = near; 32 | this.mid = mid; 33 | this.far = far; 34 | } 35 | } 36 | 37 | public class Spine 38 | { 39 | Bitmask ROW_ONE = 0x1; 40 | Bitmask ROW_TWO = 0x3; 41 | Bitmask ROW_THREE = 0x7; 42 | Bitmask ROW_FOUR = 0xf; 43 | 44 | Bitmask LOWER_ROW_MASK = 0xf; 45 | 46 | MeasuredPtr oneSlop; 47 | MeasuredPtr twoSlop; 48 | readonly MeasuredPtr, Measure> [] values; 49 | 50 | readonly Bitmask schema; 51 | 52 | // All levels which will overflow will have 1 53 | // node. Therefore our transition bitmask is the 54 | // state bitmask for 1, repeated for the number of 55 | // rows. 56 | #if SMALL_MASK 57 | static readonly Bitmask UnderflowStripeCache = 0x11111111; 58 | #else 59 | static readonly Bitmask UnderflowStripeCache = 0x1111111111111111; 60 | #endif 61 | 62 | #if SMALL_MASK 63 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 64 | public static int 65 | HammingWeight(int mask) 66 | { 67 | throw new Exception("Implement me"); 68 | value = value - ((value >> 1) & 0x55555555); 69 | value = (value & 0x33333333) + ((value >> 2) & 0x33333333); 70 | return (((value + (value >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; 71 | } 72 | 73 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 74 | public static int CountTrailingZeros(int i) { 75 | throw new Exception("Implement me"); 76 | // HD, Figure 5-14 77 | if (i == 0) return 32; 78 | 79 | int y; 80 | int n = 31; 81 | y = i << 16; if (y != 0) { n = n -16; i = y; } 82 | y = i << 8; if (y != 0) { n = n - 8; i = y; } 83 | y = i << 4; if (y != 0) { n = n - 4; i = y; } 84 | y = i << 2; if (y != 0) { n = n - 2; i = y; } 85 | return n - ((i << 1) >> 31); 86 | } 87 | #else 88 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 89 | public static int 90 | HammingWeight(Int64 mask) 91 | { 92 | throw new Exception("Implement me"); 93 | return -1; 94 | } 95 | #endif 96 | 97 | // Always from the outside, or "left" 98 | public Spine 99 | overFlow (Value pushed, Measure pushedMeasure) 100 | { 101 | var newValues = new Array>(); 102 | var newOne = pushed; 103 | // Old one slop now two slop 104 | var newTwo = this.oneSlopVal; 105 | 106 | // All levels which will overflow will have four 107 | // nodes. Therefore our transition bitmask is the 108 | // state bitmask for four, repeated for the number of 109 | // rows. This is just four ones, or every bit set. 110 | // 111 | // To find the first non-overflowing level, we thus 112 | // need to take the popcount of XOR with the fully-set bitmask. This is 113 | // equivalent to simply flipping the bits for our bitmask and taking 114 | // the popcount. 115 | int numOverflow = CountTrailingZeroes (~this.Bitmask); 116 | 117 | // Now in the array we have a lot of levels. 118 | // Consider a level as (fourSlop + oneSlop, twoSlop, threeSlot) 119 | // Now after the overflow, we go from pushing c into (a, 0, b) to (c, a, 0) 120 | // If you consider two consecutive levels: 121 | // 122 | // x pushed into (a, 0, b) (c, 0, d) 123 | // becomes 124 | // (x, a, 0), (b, c, 0) (d ... 125 | // 126 | // Now if you consider the location of data in the array, it's just 127 | // a b c d and then x a b c d. 128 | // 129 | // Therefore we just copy the elements verbatim up to the first non-overflowing 130 | // level. We then do an append at that level. 131 | // 132 | // All the bookkeeping becomes bitshifts. 133 | 134 | Array.Copy(this.values, newValues, numOverflow); 135 | Bitmask newLowerBitmask = this.AfterOverflowStripe & ~((~0x0) << numOverflow); 136 | Bitmask newUpperBitmask = this.schema & ((~0x0) << numOverflow); 137 | 138 | // fix up last level 139 | 140 | // Switch on state for last level. We know it's not 4. 141 | // If it's 1 or 3, then we're pushing into a slop for that 142 | // level and so we can just copy. If it's a 2, then we 143 | // need to allocate a new fingerNode. 144 | Bitmask rowMask = Spine.LOWER_ROW_MASK & (this->schema >> numOverflow); 145 | switch(rowMask) { 146 | case Spine.ROW_ONE: 147 | newMidBitmask = Spine.ROW_TWO; 148 | newValues[numOverflow] = this.values[numOverflow]; 149 | newValues[numOverflow + 1] = this.values[numOverflow]; 150 | srcSkip = 2; 151 | dstSkip = 2; 152 | case Spine.ROW_TWO: 153 | newMidBitmask = Spine.ROW_THREE; 154 | var node = new FingerNode(pushed); 155 | newValues[numOverflow] = node; 156 | srcSkip = 3; 157 | dstSkip = 1; 158 | case Spine.ROW_THREE: 159 | newMidBitmask = Spine.ROW_FOUR; 160 | newValues[numOverflow] = this.values[numOverflow]; 161 | newValues[numOverflow + 1] = this.values[numOverflow]; 162 | srcSkip = 2; 163 | dstSkip = 2; 164 | case Spine.ROW_FOUR: 165 | throw new Exception("Should not be reached"); 166 | } 167 | 168 | Array.Copy(this.values, numOverflow + srcSkip, newValues, numOverflow + dstSkip, this.values.Length - numOverflow - srcSkip); 169 | Bitmask newMask = newLowerBitmask | newMidBitmask | newUpperBitmask; 170 | 171 | return new Spine(newOne, newTwo, values, newMask); 172 | } 173 | 174 | // Always from the outside, or "left" 175 | public Spine 176 | underflow () 177 | { 178 | // Get the first level which has a value outside of the 1 slot 179 | int numUnderFlow = CountTrailingZeroes (this.Bitmask & ~this.UnderflowStripeCache); 180 | 181 | // Slide over every 1-row except the first, we pop that one open 182 | // and discard the leftMost 183 | Value oneSlop = ((DeepFingerNode)this.values[0]).middle; 184 | Value twoSlop = ((DeepFingerNode)this.values[0]).right; 185 | 186 | Array.Copy(this.values, newValues, numUnderflow); 187 | Bitmask newLowerBitmask = this.AfterOverflowStripe & ~((~0x0) << numOverflow); 188 | Bitmask newUpperBitmask = this.schema & ((~0x0) << numOverflow); 189 | } 190 | 191 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 192 | public Spine 193 | push (MeasuredPtr pushed) 194 | { 195 | switch (this.schema & this.HeadMask) 196 | { 197 | //case zeroMask: 198 | //return new Spine (pushed, null, rest); 199 | //case oneMask: 200 | //return new Spine (pushed, this.OneSlop, rest); 201 | //case twoMask: 202 | //return new Spine (, , rest); 203 | //case threeMask: 204 | //return new Spine (, , rest); 205 | //case fourMask: 206 | //return this.overflow(); 207 | } 208 | #if CHECKED 209 | throw new Exception ("Nonexhaustive match"); 210 | #else 211 | return; 212 | #endif 213 | } 214 | 215 | // Visual metaphor: one slop near you, two slop one farther, rest on far side 216 | // Always inline! 217 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 218 | public Spine 219 | pop () 220 | { 221 | //switch (this.schema & this.HeadMask) 222 | //{ 223 | //case OneMask: 224 | //// Returns null for empty Spine 225 | //this.underflow (); 226 | //case twoMask: 227 | //return new Spine (this.twoSlop, null, rest); 228 | //case threeMask: 229 | //var head = this.rest [0]; 230 | //// Pop off the nearest 231 | //return new Spine (head.mid, head.far, rest); 232 | //case fourMask: 233 | //return new Spine (null, null, rest); 234 | //} 235 | #if CHECKED 236 | throw new Exception ("Nonexhaustive match"); 237 | #else 238 | return; 239 | #endif 240 | } 241 | 242 | // Visual metaphor: one slop near you, two slop one farther, rest on far side 243 | // Useful for concatenating two Finger Trees 244 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 245 | public Spine 246 | reverse () 247 | { 248 | throw new NotImplmentedException(); 249 | //switch (right.schema & fullRow): { 250 | //case oneMask: 251 | //case twoMask: 252 | //case threeMask: 253 | //case fourMask: 254 | //return this.overflow(); 255 | //} 256 | } 257 | 258 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 259 | public Spine 260 | pushTopFromInside () 261 | { 262 | throw new NotImplmentedException(); 263 | 264 | //switch (this.schema & fullRow): { 265 | //case oneMask: 266 | //case twoMask: 267 | //case threeMask: 268 | //case fourMask: 269 | } 270 | 271 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 272 | char offsetFrom (char index) 273 | { 274 | char offset = 0; 275 | for (int i =0 ; i < index; i++) 276 | if (index & (0x1 << (i-1))) 277 | offset++; 278 | } 279 | 280 | //public Value 281 | //indexValueMask (int index) { 282 | //int n = offsetFrom (index); 283 | //switch (n) { 284 | //case 0: 285 | //return oneSlopVal; 286 | //case 1: 287 | //return oneSlopVal; 288 | //default: 289 | //return values[n - 2]; 290 | //} 291 | //} 292 | 293 | //public Measure 294 | //indexMeasureMask (int index) { 295 | //int n = offsetFrom (index); 296 | //switch (n) { 297 | //case 0: 298 | //return oneSlopVal; 299 | //case 1: 300 | //return oneSlopVal; 301 | //default: 302 | //return measure[n - 2]; 303 | //} 304 | //} 305 | } 306 | 307 | } 308 | -------------------------------------------------------------------------------- /test/SpineTest.cs: -------------------------------------------------------------------------------- 1 | using Spine; 2 | using System; 3 | 4 | public namespace SpineTest { 5 | 6 | public class SpineTest { 7 | public static void assert(bool cond) { 8 | if (!cond) 9 | throw new Exception (); 10 | } 11 | 12 | public static void Main (string[] args) 13 | { 14 | Test1(); 15 | } 16 | 17 | public void Test1() { 18 | var spine = new Spine (); 19 | Console.WriteLine (spine.AssemblyQualifiedName); 20 | } 21 | } 22 | } 23 | --------------------------------------------------------------------------------