on master help sign in
Remove Array for now, migrate to vector branch

I got around to properly doing my research on various implementations for immutable vectors. It is not clear to me that the RRB-Trees are going to be the best trade-off for the typical uses seen in Elm, so I want to try the Clojure approach as well and race them.

commitc4080e0e4d70180d94b5ce964c33fdf01296a53e authorEvan Czaplicki <info@elm-lang.org> dateThu 3 Apr 2014, 03:12 (13 years ago) parent4eb24f2
2 files changed−673
libraries/Array.elm−179 libraries/Native/Array.js−494
libraries/Array.elmdeleted−179
@@ -1,179 +0,0 @@
1−module Array where
2−
3−{-| A library for fast immutable arrays. The elements in an array must have the
4−same type. The arrays are implemented in Relaxed Radix Balanced-Trees for fast
5−reads, updates, and appends.
6−
7−# Creating Arrays
8−@docs empty, repeat, initialize, fromList
9−
10−# Basics
11−@docs length, push, append
12−
13−# Get and Set
14−@docs get, getMaybe, getWithDefault, set
15−
16−# Taking Arrays Apart
17−@docs toList, toIndexedList, slice
18−
19−# Mapping and Folding
20−@docs map, indexedMap, foldl, foldr
21−-}
22−
23−import Native.Array
24−import Basics (..)
25−import Maybe (..)
26−import List
27−
28−data Array a = Array
29−
30−-- TODO: make this a native function
31−{-| Initialize an array. `initialize n f` creates an array of length `n` with
32−the element at index `i` initialized to the result of `(f i)`.
33−
34− initialize 4 id == fromList [0,1,2,3]
35− initialize 4 (\n -> n*n) == fromList [0,1,4,9]
36− initialize 4 (always 0) == fromList [0,0,0,0]
37−-}
38−initialize : Int -> (Int -> a) -> Array a
39−initialize n initializer =
40− fromList <| List.map initializer [ 0 .. n - 1 ]
41−
42−{-| Creates an array with a given length, filled with a default element.
43−
44− repeat 5 0 == fromList [0,0,0,0,0]
45− repeat 3 "cat" == fromList ["cat","cat","cat"]
46−
47−Notice that `repeat 3 x` is the same as `initialize 3 (always x)`.
48−-}
49−repeat : Int -> a -> Array a
50−repeat n e = initialize n (always e)
51−
52−-- TODO: make this a native function.
53−{-| Create an array from a list. -}
54−fromList : [a] -> Array a
55−fromList xs = List.foldl Native.Array.push Native.Array.empty xs
56−
57−{-| Create a list of elements from an array.
58−
59− toList (fromList [3,5,8]) == [3,5,8]
60−-}
61−toList : Array a -> [a]
62−toList = Native.Array.toList
63−
64−-- TODO: make this a native function.
65−{-| Create an indexed list from an array. Each element of the array will be
66−paired with its index.
67−
68− toIndexedList (fromList ["cat","dog"]) == [(0,"cat"), (1,"dog")]
69−-}
70−toIndexedList : Array a -> [(Int, a)]
71−toIndexedList array =
72− List.zip [ 0 .. Native.Array.length array - 1 ] (Native.Array.toList array)
73−
74−{-| Apply a function on every element in an array.
75−
76− map sqrt (fromList [1,4,9]) == fromList [1,2,3]
77−-}
78−map : (a -> b) -> Array a -> Array b
79−map = Native.Array.map
80−
81−{-| Apply a function on every element with its index as first argument.
82−
83− indexedMap (*) (fromList [5,5,5]) == fromList [0,5,10]
84−-}
85−indexedMap : (Int -> a -> b) -> Array a -> Array b
86−indexedMap = Native.Array.indexedMap
87−
88−{-| Reduce an array from the left.
89−
90− foldl (::) [] (fromList [1,2,3]) == [3,2,1]
91−-}
92−foldl : (a -> b -> b) -> b -> Array a -> b
93−foldl = Native.Array.foldl
94−
95−{-| Reduce an array from the right.
96−
97− foldr (+) 0 (repeat 3 5) == 15
98−-}
99−foldr : (a -> b -> b) -> b -> Array a -> b
100−foldr = Native.Array.foldr
101−
102−{-| Return an empty array.
103−
104− length empty == 0
105−-}
106−empty : Array a
107−empty = Native.Array.empty
108−
109−{-| Push an element to the end of an array.
110−
111− push 3 (fromList [1,2]) == fromList [1,2,3]
112−-}
113−push : a -> Array a -> Array a
114−push = Native.Array.push
115−
116−{-| Return the element at the index. Breaks, if index is out of range. If the
117−array length is unkown, use withDefaultGet oder getWithDefault.
118−
119− get 2 (A.fromList [3,2,1]) == 1
120−-}
121−get : Int -> Array a -> a
122−get = Native.Array.get
123−
124−{-| Return Just the element at the index or Nothing if the index is out of range.
125−
126− getMaybe 2 (fromList [3,2,1]) == Just 2
127− getMaybe 5 (fromList [3,2,1]) == Nothing
128− getMaybe -1 (fromList [3,2,1]) == Nothing
129−-}
130−getMaybe : Int -> Array a -> Maybe a
131−getMaybe i array =
132− if 0 <= i && i < Native.Array.length array
133− then Just (Native.Array.get i array)
134− else Nothing
135−
136−{-| Get the element at the index. If the index is out of range, the given default
137−element is returned.
138−
139− getWithDefault 0 2 (fromList [3,2,1]) == 1
140− getWithDefault 0 5 (fromList [3,2,1]) == 0
141−-}
142−getWithDefault : a -> Int -> Array a -> a
143−getWithDefault default i array =
144− if 0 <= i && i < Native.Array.length array
145− then Native.Array.get i array
146− else default
147−
148−{-| Set the element at a particular index. Returns an updated array.
149−If the index is out of range, the array is unaltered.
150−
151− set 1 7 (fromList [1,2,3]) == fromList [1,7,3]
152−-}
153−set : Int -> a -> Array a -> Array a
154−set = Native.Array.set
155−
156−{-| Slice an array given a range. The selection is inclusive, so the last
157−element in the selection will also be in the new array. This may change in the
158−future. You can select from the end by giving a negative Int.
159−
160− slice 1 2 (fromList [0,1,2,3,4]) == fromList [1,2]
161− slice 1 -2 (fromList [0,1,2,3,4]) == fromList [1,2,3]
162− slice -3 -2 (fromList [0,1,2,3,4]) == fromList [2,3]
163−-}
164−slice : Int -> Int -> Array a -> Array a
165−slice = Native.Array.slice
166−
167−{-| Return the length of an array.
168−
169− length (fromList [1,2,3]) == 3
170−-}
171−length : Array a -> Int
172−length = Native.Array.length
173−
174−{-| Append two arrays to a new one.
175−
176− append (array 2 42) (array 3 81) == fromList [42,42,81,81,81]
177−-}
178−append : Array a -> Array a -> Array a
179−append = Native.Array.append
libraries/Native/Array.jsdeleted−494
@@ -1,494 +0,0 @@
1−Elm.Native.Array = {};
2−Elm.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;
8−
9− var List = Elm.Native.List.make(elm);
10−
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
17−
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;
23−
24− // An empty array.
25− var empty = { ctor:"_Array", height:0, table:new Array() };
26−
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− }
35−
36− function unsafeGet(i, array) {
37− if (array.height == 0) {
38− return array.table[i];
39− }
40− var slot = getSlot(i, array);
41− var offset = slot > 0 ? array.lengths[slot-1] : 0;
42− return unsafeGet(i - offset, array.table[slot]);
43− }
44−
45− // Sets the value at the index i. Only the nodes leading to i will get
46− // copied and updated.
47− function set(i, item, a) {
48− if (length(a) <= i) {
49− return a;
50− }
51− var newA = nodeCopy(a);
52− newA.table = a.table.slice();
53−
54− if (a.height == 0) {
55− newA.table[i] = item;
56− } else {
57− var slot = getSlot(i, a);
58− var sub = slot > 0 ? a.lengths[slot-1] : 0;
59− newA.table[slot] = set(i - sub, item, a.table[slot]);
60− }
61− return newA;
62− }
63−
64− // Pushes an item via push_ to the bottom right of a tree.
65− function push(item, a) {
66− var pushed = push_(item, a);
67− if (pushed !== null) {
68− return pushed;
69− }
70−
71− var newTree = create(item, a.height);
72− return siblise(a, newTree);
73− }
74−
75− // Recursively tries to push an item to the bottom-right most
76− // tree possible. If there is no space left for the item,
77− // null will be returned.
78− function push_(item, a) {
79− // Handle resursion stop at leaf level.
80− if (a.height == 0) {
81− if (a.table.length < M) {
82− var newA = { ctor:"_Array", height:0, table:a.table.slice() };
83− newA.table.push(item);
84− return newA;
85− } else {
86− return null;
87− }
88− }
89−
90− // Recursively push
91− var pushed = push_(item, botRight(a));
92−
93− // There was space in the bottom right tree, so the slot will
94− // be updated.
95− if (pushed != null) {
96− var newA = nodeCopy(a);
97− newA.table[newA.table.length - 1] = pushed;
98− newA.lengths[newA.lengths.length - 1]++;
99− return newA
100− }
101−
102− // When there was no space left, check if there is space left
103− // for a new slot with a tree which contains only the item
104− // at the bottom.
105− if (a.table.length < M) {
106− var newSlot = create(item, a.height - 1);
107− var newA = nodeCopy(a);
108− newA.table.push(newSlot);
109− newA.lengths.push(newA.lengths[newA.lengths.length - 1] + length(newSlot));
110− return newA
111− } else {
112− return null;
113− }
114− }
115−
116− // Converts an array into a list of elements.
117− function toList(a) {
118− return toList_(List.Nil, a);
119− }
120−
121− function toList_(list, a) {
122− for (var i = a.table.length - 1; i >= 0; i--) {
123− list = a.height == 0 ? List.Cons(a.table[i], list) : toList_(list, a.table[i]);
124− }
125− return list;
126− }
127−
128− // Maps a function over the elements of an array.
129− function map(f, a) {
130− var newA = { ctor:"_Array", height:a.height, table:new Array(a.table) };
131− if (a.height > 0) { newA.lengths = a.lengths; }
132− for (var i = 0; i < a.table.length; i++) {
133− newA.table[i] = a.height == 0 ? f(a.table[i]) : map(f, a.table[i]);
134− }
135− return newA;
136− }
137−
138− // Maps a function over the elements with their index as first argument.
139− function indexedMap(f, a) {
140− var newA = { ctor:"_Array", height:a.height, table:new Array(a.table) };
141− if (a.height > 0) { newA.lengths = a.lengths; }
142− for (var i = 0; i < a.table.length; i++) {
143− newA.table[i] = a.height == 0 ? A2(f, i, a.table[i]) : indexedMap(f, a.table[i]);
144− }
145− return newA;
146− }
147−
148− function foldl(f, b, a) {
149− for (var i = a.table.length - 1; i >= 0; i--) {
150− b = A2(f, a.height == 0 ? a.table[i] : foldl(f, b, a.table[i]), b);
151− }
152− return b;
153− }
154−
155− function foldr(f, b, a) {
156− for (var i = 0; i < a.table.length; i++) {
157− b = A2(f, a.height == 0 ? a.table[i] : foldr(f, b, a.table[i]), b);
158− }
159− return b;
160− }
161−
162− // Returns a sliced tree. "to" is inclusive, but this may change,
163− // when I understand, why e.g. JS does not handle it this way. :-)
164− // If "from" or "to" is negative, they will select from the end on.
165− // TODO: currently, it slices the right, then the left. This can be
166− // optimized.
167− function slice(from, to, a) {
168− if (from < 0) { from += length(a); }
169− if (to < 0) { to += length(a); }
170− return sliceLeft(from, sliceRight(to, a));
171− }
172−
173− function sliceRight(to, a) {
174− if (to == length(a)) {
175− return a;
176− }
177−
178− // Handle leaf level.
179− if (a.height == 0) {
180− var newA = { ctor:"_Array", height:0 };
181− newA.table = a.table.slice(0, to + 1);
182− return newA;
183− }
184−
185− // Slice the right recursively.
186− var right = getSlot(to, a);
187− var sliced = sliceRight(to - (right > 0 ? a.lengths[right - 1] : 0), a.table[right]);
188−
189− // Maybe the a node is not even needed, as sliced contains the whole slice.
190− if (right == 0) {
191− return sliced;
192− }
193−
194− // Create new node.
195− var newA = { ctor:"_Array", height:a.height
196− , table:a.table.slice(0, right + 1)
197− , lengths:a.lengths.slice(0, right + 1) };
198− newA.table[right] = sliced;
199− newA.lengths[right] = length(sliced) + (right > 0 ? newA.lengths[right - 1] : 0);
200− return newA;
201− }
202−
203− function sliceLeft(from, a) {
204− if (from == 0) {
205− return a;
206− }
207−
208− // Handle leaf level.
209− if (a.height == 0) {
210− var newA = { ctor:"_Array", height:0 };
211− newA.table = a.table.slice(from, a.table.length + 1);
212− return newA;
213− }
214−
215− // Slice the left recursively.
216− var left = getSlot(from, a);
217− var sliced = sliceLeft(from - (left > 0 ? a.lengths[left - 1] : 0), a.table[left]);
218−
219− // Maybe the a node is not even needed, as sliced contains the whole slice.
220− if (left == a.table.length - 1) {
221− return sliced;
222− }
223−
224− // Create new node.
225− var newA = { ctor:"_Array", height:a.height
226− , table:a.table.slice(left, a.table.length + 1)
227− , lengths:new Array(a.table.length - left) };
228− newA.table[left] = sliced;
229− var len = 0;
230− for (var i = 0; i < newA.table.length; i++) {
231− len += length(newA.table[i]);
232− newA.lengths[i] = len;
233− }
234−
235− return newA;
236− }
237−
238− // Appends two trees.
239− // TODO: Add support for appending trees of different sizes. Current
240− // behavior will just rise the lower tree and then append them.
241− function append(a,b) {
242− if (b.height > a.height) { return append(parentise(a, b.height), b); }
243− if (a.height > b.height) { return append(a, parentise(b, a.height)); }
244− if (a.height == 0) { return append(parentise(a, 1), parentise(b, 1)); }
245−
246− var c = append_(a, b);
247− if (c[1].table.length > 0) {
248− return siblise(c[0], c[1]);
249− } else {
250− return c[0];
251− }
252− }
253−
254− // Returns an array of two nodes. The second node _may_ be empty. This case
255− // needs to be handled by the function, that called append_. May be only
256− // called for trees with an minimal height of 1.
257− function append_(a, b) {
258− if (a.height == 1) {
259− // Check if balancing is needed and return based on that.
260− var toRemove = calcToRemove(a, b);
261− if (toRemove <= E) {
262− return [a,b];
263− }
264−
265− return shuffle(a, b, toRemove);
266− }
267−
268− var appended = append_(botRight(a), botLeft(b));
269− a = nodeCopy(a), b = nodeCopy(b);
270−
271− // Adjust the bottom right side of the new tree.
272− a.table[a.table.length - 1] = appended[0];
273− a.lengths[a.lengths.length - 1] = length(appended[0])
274− a.lengths[a.lengths.length - 1] += a.lengths.length > 1 ? a.lengths[a.lengths.length - 2] : 0;
275−
276− // Adjust the bottom left side of the new tree.
277− if (appended[1].table.length > 0) {
278− b.table[0] = appended[1];
279− b.lengths[0] = length(appended[1]);
280− for (var i = 1, len = length(b.table[0]); i < b.lengths.length; i++) {
281− len += length(b.table[i]);
282− b.lengths[i] = len;
283− }
284− } else {
285− b.table.shift();
286− for (var i = 1; i < b.lengths.length; i++) {
287− b.lengths[i] = b.lengths[i] - b.lengths[0];
288− }
289− b.lengths.shift();
290− }
291−
292− // Check if balancing is needed and return based on that.
293− var toRemove = calcToRemove(a, b);
294− if (toRemove <= E || b.table.length == 0) {
295− return [a,b];
296− }
297−
298− return shuffle(a, b, toRemove);
299− }
300−
301− // Returns the extra search steps for E. Refer to the paper.
302− function calcToRemove(a, b) {
303− var subLengths = 0;
304− for (var i = 0; i < a.table.length; i++) {
305− subLengths += a.table[i].table.length;
306− }
307− for (var i = 0; i < b.table.length; i++) {
308− subLengths += b.table[i].table.length;
309− }
310−
311− var toRemove = a.table.length + b.table.length
312− return toRemove - (Math.floor((subLengths - 1) / M) + 1);
313− }
314−
315− // get2, set2 and saveSlot are helpers for accessing elements over two arrays.
316− function get2(a, b, index) {
317− return index < a.length ? a[index] : b[index - a.length];
318− }
319−
320− function set2(a, b, index, value) {
321− if (index < a.length) {
322− a[index] = value;
323− } else {
324− b[index - a.length] = value;
325− }
326− }
327−
328− function saveSlot(a, b, index, slot) {
329− set2(a.table, b.table, index, slot);
330−
331− var l = (index == 0 || index == a.lengths.length) ?
332− 0 : get2(a.lengths, a.lengths, index - 1);
333− set2(a.lengths, b.lengths, index, l + length(slot));
334− }
335−
336− // Creates a node or leaf with a given length at their arrays for perfomance.
337− // Is only used by shuffle.
338− function createNode(h, length) {
339− if (length < 0) { length = 0; }
340− var a = { ctor:"_Array", height:h, table:new Array(length) };
341− if (h > 0) {
342− a.lengths = new Array(length);
343− }
344− return a;
345− }
346−
347− // Returns an array of two balanced nodes.
348− function shuffle(a, b, toRemove) {
349− var newA = createNode(a.height, Math.min(M, a.table.length + b.table.length - toRemove));
350− var newB = createNode(a.height, newA.table.length - (a.table.length + b.table.length - toRemove));
351−
352− // Skip the slots with size M. More precise: copy the slot references
353− // to the new node
354− var read = 0;
355− while (get2(a.table, b.table, read).table.length % M == 0) {
356− set2(newA.table, newB.table, read, get2(a.table, b.table, read));
357− set2(newA.lengths, newB.lengths, read, get2(a.lengths, b.lengths, read));
358− read++;
359− }
360−
361− // Pulling items from left to right, caching in a slot before writing
362− // it into the new nodes.
363− var write = read;
364− var slot = new createNode(a.height - 1, 0);
365− var from = 0;
366−
367− // If the current slot is still containing data, then there will be at
368− // least one more write, so we do not break this loop yet.
369− while (read - write - (slot.table.length > 0 ? 1 : 0) < toRemove) {
370− // Find out the max possible items for copying.
371− var source = get2(a.table, b.table, read);
372− var to = Math.min(M - slot.table.length, source.table.length)
373−
374− // Copy and adjust size table.
375− slot.table = slot.table.concat(source.table.slice(from, to));
376− if (slot.height > 0) {
377− var len = slot.lengths.length;
378− for (var i = len; i < len + to - from; i++) {
379− slot.lengths[i] = length(slot.table[i]);
380− slot.lengths[i] += (i > 0 ? slot.lengths[i - 1] : 0);
381− }
382− }
383−
384− from += to;
385−
386− // Only proceed to next slots[i] if the current one was
387− // fully copied.
388− if (source.table.length <= to) {
389− read++; from = 0;
390− }
391−
392− // Only create a new slot if the current one is filled up.
393− if (slot.table.length == M) {
394− saveSlot(newA, newB, write, slot);
395− slot = createNode(a.height - 1,0);
396− write++;
397− }
398− }
399−
400− // Cleanup after the loop. Copy the last slot into the new nodes.
401− if (slot.table.length > 0) {
402− saveSlot(newA, newB, write, slot);
403− write++;
404− }
405−
406− // Shift the untouched slots to the left
407− while (read < a.table.length + b.table.length ) {
408− saveSlot(newA, newB, write, get2(a.table, b.table, read));
409− read++; write++;
410− }
411−
412− return [newA, newB];
413− }
414−
415− // Navigation functions
416− function botRight(a) { return a.table[a.table.length - 1]; }
417− function botLeft(a) { return a.table[0]; }
418−
419− // Copies a node for updating. Note that you should not use this if
420− // only updating only one of "table" or "lengths" for performance reasons.
421− function nodeCopy(a) {
422− var newA = { ctor:"_Array", height:a.height
423− , table:a.table.slice() };
424− if (a.height > 0) { newA.lengths = a.lengths.slice(); }
425− return newA;
426− }
427−
428− // Returns how many items are in the tree.
429− function length(array) {
430− if (array.height == 0) {
431− return array.table.length;
432− } else {
433− return array.lengths[array.lengths.length - 1];
434− }
435− }
436−
437− // Calculates in which slot of "table" the item probably is, then
438− // find the exact slot via forward searching in "lengths". Returns the index.
439− function getSlot(i, a) {
440− var slot = Math.floor(i / (Math.pow(M, a.height)));
441− while (a.lengths[slot] <= i) {
442− slot++
443− }
444− return slot;
445− }
446−
447− // Recursively creates a tree with a given height containing
448− // only the given item.
449− function create(item, h) {
450− if (h == 0) {
451− return { ctor:"_Array", height:0
452− , table:[item] };
453− } else {
454− return { ctor:"_Array", height:h
455− , table:[create(item, h - 1)]
456− , lengths:[1] };
457− }
458− }
459−
460− // Recursively creates a tree that contains the given tree.
461− function parentise(tree, h) {
462− if (h == tree.height) {
463− return tree;
464− } else {
465− return { ctor:"_Array", height:h
466− , table:[parentise(tree, h - 1)]
467− , lengths:[length(tree)] };
468− }
469− }
470−
471− // Emphasizes blood brotherhood beneath two trees.
472− function siblise(a, b) {
473− return { ctor:"_Array", height:a.height + 1
474− , table:[a, b]
475− , lengths:[length(a), length(a) + length(b)] };
476− }
477−
478− Elm.Native.Array.values = {
479− empty:empty,
480− toList:toList,
481− append:F2(append),
482− push:F2(push),
483− slice:F3(slice),
484− get:F2(get),
485− set:F3(set),
486− map:F2(map),
487− indexedMap:F2(indexedMap),
488− foldl:F3(foldl),
489− foldr:F3(foldr),
490− length:length
491− };
492−
493− return elm.Native.Array.values = Elm.Native.Array.values;
494−}