on fb59626 help sign in
elk/libraries/Native/Array.js
594 lines 19 kB history raw
1Elm.Native.Array = {};
2Elm.Native.Array.make = function(elm) {
3 elm.Native = elm.Native || {};
4 elm.Native.Array = elm.Native.Array || {};
5 if (elm.Native.Array.values) return elm.Native.Array.values;
6 if ('values' in Elm.Native.Array)
7 return elm.Native.Array.values = Elm.Native.Array.values;
9 var List = Elm.Native.List.make(elm);
11 // A RRB-Tree has two distinct data types.
12 // Leaf -> "height" is always 0
13 // "table" is an array of elements
14 // Node -> "height" is always greater than 0
15 // "table" is an array of child nodes
16 // "lengths" is an array of accumulated lengths of the child nodes
18 // M is the maximal table size. 32 seems fast. E is the allowed increase
19 // of search steps when concatting to find an index. Lower values will
20 // decrease balancing, but will increase search steps.
21 var M = 32;
22 var E = 2;
24 // An empty array.
25 var empty = { ctor:"_Array", height:0, table:new Array() };
27 // Gets the value at index i recursively.
28 function get(i, array) {
29 if (i < 0 || i >= length(array)) {
30 throw new Error("Index " + i + " is out of range. Check the length of " +
31 "your array first or use getMaybe or getWithDefault.");
32 }
33 return unsafeGet(i, array);
34 }
36 function unsafeGet(i, array) {
37 for (var x = array.height; x > 0; x--) {
38 var slot = i >> (x * 5);
39 if (slot > 0) {
40 while (array.lengths[slot - 1] > i) { slot--; }
41 i -= array.lengths[slot - 1];
42 }
43 array = array.table[slot];
44 }
45 return array.table[i];
46 }
48 // Sets the value at the index i. Only the nodes leading to i will get
49 // copied and updated.
50 function set(i, item, a) {
51 if (length(a) <= i) {
52 return a;
53 }
54 var newA = nodeCopy(a);
56 if (a.height == 0) {
57 newA.table[i] = item;
58 } else {
59 var slot = getSlot(i, a);
60 var sub = slot > 0 ? a.lengths[slot-1] : 0;
61 newA.table[slot] = set(i - sub, item, a.table[slot]);
62 }
63 return newA;
64 }
66 function initialize(len, f) {
67 if (len == 0) { return empty; }
68 var h = Math.floor(Math.log(len)/Math.log(M));
69 return initialize_(f, h, 0, len);
70 }
72 function initialize_(f, h, from, to) {
73 if (h == 0) {
74 var table = new Array((to - from) % (M + 1));
75 for (var i = 0; i < table.length; i++) {
76 table[i] = f(from + i);
77 }
78 return { ctor:"_Array", height:0, table:table };
79 }
81 var step = Math.pow(M, h);
82 var table = new Array(Math.ceil((to - from) / step));
83 var lengths = new Array(table.length);
84 for (var i = 0; i < table.length; i++) {
85 table[i] = initialize_( f, h - 1, from + (i * step)
86 , Math.min(from + ((i + 1) * step), to));
87 lengths[i] = length(table[i]) + (i > 0 ? lengths[i-1] : 0);
88 }
89 return { ctor:"_Array", height:h, table:table, lengths:lengths };
90 }
92 function fromList(list) {
93 if (list == List.Nil) { return empty; }
95 // Allocate M sized blocks (table) and write list elements to it.
96 var table = new Array(M);
97 var nodes = new Array();
98 var i = 0;
100 while (list.ctor !== '[]') {
101 table[i] = list._0;
102 list = list._1;
103 i++;
105 // table is full, so we can push a leaf containing it into the
106 // next node.
107 if (i == M) {
108 fromListPush({ ctor:"_Array", height:0, table:table }
109 , nodes);
110 table = new Array(M);
111 i = 0;
112 }
113 }
115 // Maybe there is something left on the table.
116 if (i > 0) {
117 fromListPush({ ctor:"_Array", height:0, table:table.splice(0,i) }
118 , nodes);
119 }
121 // Go through all of the nodes and eventually push them into higher nodes.
122 for (var h = 0; h < nodes.length - 1; h++) {
123 if (nodes[h].table.length > 0) {
124 fromListPush(nodes[h], nodes);
125 }
126 }
128 var head = nodes[nodes.length - 1];
129 if (head.height > 0 && head.table.length == 1) {
130 return head.table[0];
131 } else {
132 return head;
133 }
134 }
136 // Push a node into a higher node as a child.
137 function fromListPush(toPush, nodes) {
138 var h = toPush.height;
140 // Maybe the node on this height does not exist.
141 if (nodes.length == h) {
142 nodes.push({ ctor:"_Array", height:h + 1
143 , table:new Array()
144 , lengths:new Array() });
145 }
147 nodes[h].table.push(toPush);
148 var len = length(toPush);
149 if (nodes[h].lengths.length > 0) {
150 len += nodes[h].lengths[nodes[h].lengths.length - 1];
151 }
152 nodes[h].lengths.push(len);
154 if (nodes[h].table.length == M) {
155 fromListPush(nodes[h], nodes);
156 nodes[h] = { ctor:"_Array", height:h + 1
157 , table:new Array()
158 , lengths:new Array() };
159 }
160 }
162 // Pushes an item via push_ to the bottom right of a tree.
163 function push(item, a) {
164 var pushed = push_(item, a);
165 if (pushed !== null) {
166 return pushed;
167 }
169 var newTree = create(item, a.height);
170 return siblise(a, newTree);
171 }
173 // Recursively tries to push an item to the bottom-right most
174 // tree possible. If there is no space left for the item,
175 // null will be returned.
176 function push_(item, a) {
177 // Handle resursion stop at leaf level.
178 if (a.height == 0) {
179 if (a.table.length < M) {
180 var newA = { ctor:"_Array", height:0, table:a.table.slice() };
181 newA.table.push(item);
182 return newA;
183 } else {
184 return null;
185 }
186 }
188 // Recursively push
189 var pushed = push_(item, botRight(a));
191 // There was space in the bottom right tree, so the slot will
192 // be updated.
193 if (pushed != null) {
194 var newA = nodeCopy(a);
195 newA.table[newA.table.length - 1] = pushed;
196 newA.lengths[newA.lengths.length - 1]++;
197 return newA
198 }
200 // When there was no space left, check if there is space left
201 // for a new slot with a tree which contains only the item
202 // at the bottom.
203 if (a.table.length < M) {
204 var newSlot = create(item, a.height - 1);
205 var newA = nodeCopy(a);
206 newA.table.push(newSlot);
207 newA.lengths.push(newA.lengths[newA.lengths.length - 1] + length(newSlot));
208 return newA
209 } else {
210 return null;
211 }
212 }
214 // Converts an array into a list of elements.
215 function toList(a) {
216 return toList_(List.Nil, a);
217 }
219 function toList_(list, a) {
220 for (var i = a.table.length - 1; i >= 0; i--) {
221 list = a.height == 0 ? List.Cons(a.table[i], list) : toList_(list, a.table[i]);
222 }
223 return list;
224 }
226 // Maps a function over the elements of an array.
227 function map(f, a) {
228 var newA = { ctor:"_Array", height:a.height, table:new Array(a.table) };
229 if (a.height > 0) { newA.lengths = a.lengths; }
230 for (var i = 0; i < a.table.length; i++) {
231 newA.table[i] = a.height == 0 ? f(a.table[i]) : map(f, a.table[i]);
232 }
233 return newA;
234 }
236 // Maps a function over the elements with their index as first argument.
237 function indexedMap(f, a) {
238 var newA = { ctor:"_Array", height:a.height, table:new Array(a.table) };
239 if (a.height > 0) { newA.lengths = a.lengths; }
240 for (var i = 0; i < a.table.length; i++) {
241 newA.table[i] = a.height == 0 ? A2(f, i, a.table[i]) : indexedMap(f, a.table[i]);
242 }
243 return newA;
244 }
246 function foldl(f, b, a) {
247 for (var i = a.table.length - 1; i >= 0; i--) {
248 b = A2(f, a.height == 0 ? a.table[i] : foldl(f, b, a.table[i]), b);
249 }
250 return b;
251 }
253 function foldr(f, b, a) {
254 for (var i = 0; i < a.table.length; i++) {
255 b = A2(f, a.height == 0 ? a.table[i] : foldr(f, b, a.table[i]), b);
256 }
257 return b;
258 }
260 // Returns a sliced tree. "to" is inclusive, but this may change,
261 // when I understand, why e.g. JS does not handle it this way. :-)
262 // If "from" or "to" is negative, they will select from the end on.
263 // TODO: currently, it slices the right, then the left. This can be
264 // optimized.
265 function slice(from, to, a) {
266 if (from < 0) { from += length(a); }
267 if (to < 0) { to += length(a); }
268 return sliceLeft(from, sliceRight(to, a));
269 }
271 function sliceRight(to, a) {
272 if (to == length(a)) {
273 return a;
274 }
276 // Handle leaf level.
277 if (a.height == 0) {
278 var newA = { ctor:"_Array", height:0 };
279 newA.table = a.table.slice(0, to + 1);
280 return newA;
281 }
283 // Slice the right recursively.
284 var right = getSlot(to, a);
285 var sliced = sliceRight(to - (right > 0 ? a.lengths[right - 1] : 0), a.table[right]);
287 // Maybe the a node is not even needed, as sliced contains the whole slice.
288 if (right == 0) {
289 return sliced;
290 }
292 // Create new node.
293 var newA = { ctor:"_Array", height:a.height
294 , table:a.table.slice(0, right + 1)
295 , lengths:a.lengths.slice(0, right + 1) };
296 newA.table[right] = sliced;
297 newA.lengths[right] = length(sliced) + (right > 0 ? newA.lengths[right - 1] : 0);
298 return newA;
299 }
301 function sliceLeft(from, a) {
302 if (from == 0) {
303 return a;
304 }
306 // Handle leaf level.
307 if (a.height == 0) {
308 var newA = { ctor:"_Array", height:0 };
309 newA.table = a.table.slice(from, a.table.length + 1);
310 return newA;
311 }
313 // Slice the left recursively.
314 var left = getSlot(from, a);
315 var sliced = sliceLeft(from - (left > 0 ? a.lengths[left - 1] : 0), a.table[left]);
317 // Maybe the a node is not even needed, as sliced contains the whole slice.
318 if (left == a.table.length - 1) {
319 return sliced;
320 }
322 // Create new node.
323 var newA = { ctor:"_Array", height:a.height
324 , table:a.table.slice(left, a.table.length + 1)
325 , lengths:new Array(a.table.length - left) };
326 newA.table[left] = sliced;
327 var len = 0;
328 for (var i = 0; i < newA.table.length; i++) {
329 len += length(newA.table[i]);
330 newA.lengths[i] = len;
331 }
333 return newA;
334 }
336 // Appends two trees.
337 // TODO: Add support for appending trees of different sizes. Current
338 // behavior will just rise the lower tree and then append them.
339 function append(a,b) {
340 if (b.height > a.height) { return append(parentise(a, b.height), b); }
341 if (a.height > b.height) { return append(a, parentise(b, a.height)); }
342 if (a.height == 0) { return append(parentise(a, 1), parentise(b, 1)); }
344 var c = append_(a, b);
345 if (c[1].table.length > 0) {
346 return siblise(c[0], c[1]);
347 } else {
348 return c[0];
349 }
350 }
352 // Returns an array of two nodes. The second node _may_ be empty. This case
353 // needs to be handled by the function, that called append_. May be only
354 // called for trees with an minimal height of 1.
355 function append_(a, b) {
356 if (a.height == 1) {
357 // Check if balancing is needed and return based on that.
358 var toRemove = calcToRemove(a, b);
359 if (toRemove <= E) {
360 return [a,b];
361 }
363 return shuffle(a, b, toRemove);
364 }
366 var appended = append_(botRight(a), botLeft(b));
367 a = nodeCopy(a), b = nodeCopy(b);
369 // Adjust the bottom right side of the new tree.
370 a.table[a.table.length - 1] = appended[0];
371 a.lengths[a.lengths.length - 1] = length(appended[0])
372 a.lengths[a.lengths.length - 1] += a.lengths.length > 1 ? a.lengths[a.lengths.length - 2] : 0;
374 // Adjust the bottom left side of the new tree.
375 if (appended[1].table.length > 0) {
376 b.table[0] = appended[1];
377 b.lengths[0] = length(appended[1]);
378 for (var i = 1, len = length(b.table[0]); i < b.lengths.length; i++) {
379 len += length(b.table[i]);
380 b.lengths[i] = len;
381 }
382 } else {
383 b.table.shift();
384 for (var i = 1; i < b.lengths.length; i++) {
385 b.lengths[i] = b.lengths[i] - b.lengths[0];
386 }
387 b.lengths.shift();
388 }
390 // Check if balancing is needed and return based on that.
391 var toRemove = calcToRemove(a, b);
392 if (toRemove <= E || b.table.length == 0) {
393 return [a,b];
394 }
396 return shuffle(a, b, toRemove);
397 }
399 // Returns the extra search steps for E. Refer to the paper.
400 function calcToRemove(a, b) {
401 var subLengths = 0;
402 for (var i = 0; i < a.table.length; i++) {
403 subLengths += a.table[i].table.length;
404 }
405 for (var i = 0; i < b.table.length; i++) {
406 subLengths += b.table[i].table.length;
407 }
409 var toRemove = a.table.length + b.table.length
410 return toRemove - (Math.floor((subLengths - 1) / M) + 1);
411 }
413 // get2, set2 and saveSlot are helpers for accessing elements over two arrays.
414 function get2(a, b, index) {
415 return index < a.length ? a[index] : b[index - a.length];
416 }
418 function set2(a, b, index, value) {
419 if (index < a.length) {
420 a[index] = value;
421 } else {
422 b[index - a.length] = value;
423 }
424 }
426 function saveSlot(a, b, index, slot) {
427 set2(a.table, b.table, index, slot);
429 var l = (index == 0 || index == a.lengths.length) ?
430 0 : get2(a.lengths, a.lengths, index - 1);
431 set2(a.lengths, b.lengths, index, l + length(slot));
432 }
434 // Creates a node or leaf with a given length at their arrays for perfomance.
435 // Is only used by shuffle.
436 function createNode(h, length) {
437 if (length < 0) { length = 0; }
438 var a = { ctor:"_Array", height:h, table:new Array(length) };
439 if (h > 0) {
440 a.lengths = new Array(length);
441 }
442 return a;
443 }
445 // Returns an array of two balanced nodes.
446 function shuffle(a, b, toRemove) {
447 var newA = createNode(a.height, Math.min(M, a.table.length + b.table.length - toRemove));
448 var newB = createNode(a.height, newA.table.length - (a.table.length + b.table.length - toRemove));
450 // Skip the slots with size M. More precise: copy the slot references
451 // to the new node
452 var read = 0;
453 while (get2(a.table, b.table, read).table.length % M == 0) {
454 set2(newA.table, newB.table, read, get2(a.table, b.table, read));
455 set2(newA.lengths, newB.lengths, read, get2(a.lengths, b.lengths, read));
456 read++;
457 }
459 // Pulling items from left to right, caching in a slot before writing
460 // it into the new nodes.
461 var write = read;
462 var slot = new createNode(a.height - 1, 0);
463 var from = 0;
465 // If the current slot is still containing data, then there will be at
466 // least one more write, so we do not break this loop yet.
467 while (read - write - (slot.table.length > 0 ? 1 : 0) < toRemove) {
468 // Find out the max possible items for copying.
469 var source = get2(a.table, b.table, read);
470 var to = Math.min(M - slot.table.length, source.table.length)
472 // Copy and adjust size table.
473 slot.table = slot.table.concat(source.table.slice(from, to));
474 if (slot.height > 0) {
475 var len = slot.lengths.length;
476 for (var i = len; i < len + to - from; i++) {
477 slot.lengths[i] = length(slot.table[i]);
478 slot.lengths[i] += (i > 0 ? slot.lengths[i - 1] : 0);
479 }
480 }
482 from += to;
484 // Only proceed to next slots[i] if the current one was
485 // fully copied.
486 if (source.table.length <= to) {
487 read++; from = 0;
488 }
490 // Only create a new slot if the current one is filled up.
491 if (slot.table.length == M) {
492 saveSlot(newA, newB, write, slot);
493 slot = createNode(a.height - 1,0);
494 write++;
495 }
496 }
498 // Cleanup after the loop. Copy the last slot into the new nodes.
499 if (slot.table.length > 0) {
500 saveSlot(newA, newB, write, slot);
501 write++;
502 }
504 // Shift the untouched slots to the left
505 while (read < a.table.length + b.table.length ) {
506 saveSlot(newA, newB, write, get2(a.table, b.table, read));
507 read++; write++;
508 }
510 return [newA, newB];
511 }
513 // Navigation functions
514 function botRight(a) { return a.table[a.table.length - 1]; }
515 function botLeft(a) { return a.table[0]; }
517 // Copies a node for updating. Note that you should not use this if
518 // only updating only one of "table" or "lengths" for performance reasons.
519 function nodeCopy(a) {
520 var newA = { ctor:"_Array", height:a.height
521 , table:a.table.slice() };
522 if (a.height > 0) { newA.lengths = a.lengths.slice(); }
523 return newA;
524 }
526 // Returns how many items are in the tree.
527 function length(array) {
528 if (array.height == 0) {
529 return array.table.length;
530 } else {
531 return array.lengths[array.lengths.length - 1];
532 }
533 }
535 // Calculates in which slot of "table" the item probably is, then
536 // find the exact slot via forward searching in "lengths". Returns the index.
537 function getSlot(i, a) {
538 var slot = i << (5 * a.height);
539 while (a.lengths[slot - 1] > i) {
540 slot++;
541 }
542 return slot;
543 }
545 // Recursively creates a tree with a given height containing
546 // only the given item.
547 function create(item, h) {
548 if (h == 0) {
549 return { ctor:"_Array", height:0
550 , table:[item] };
551 } else {
552 return { ctor:"_Array", height:h
553 , table:[create(item, h - 1)]
554 , lengths:[1] };
555 }
556 }
558 // Recursively creates a tree that contains the given tree.
559 function parentise(tree, h) {
560 if (h == tree.height) {
561 return tree;
562 } else {
563 return { ctor:"_Array", height:h
564 , table:[parentise(tree, h - 1)]
565 , lengths:[length(tree)] };
566 }
567 }
569 // Emphasizes blood brotherhood beneath two trees.
570 function siblise(a, b) {
571 return { ctor:"_Array", height:a.height + 1
572 , table:[a, b]
573 , lengths:[length(a), length(a) + length(b)] };
574 }
576 Elm.Native.Array.values = {
577 empty:empty,
578 fromList:fromList,
579 toList:toList,
580 initialize:F2(initialize),
581 append:F2(append),
582 push:F2(push),
583 slice:F3(slice),
584 get:F2(get),
585 set:F3(set),
586 map:F2(map),
587 indexedMap:F2(indexedMap),
588 foldl:F3(foldl),
589 foldr:F3(foldr),
590 length:length
591 };
593 return elm.Native.Array.values = Elm.Native.Array.values;