Skip to content

Commit 4c8591d

Browse files
gok99LPTKAnsonYeung
authored
FingerTreeLists and LazyFingerTreeLists (#319)
Co-authored-by: Lionel Parreaux <[email protected]> Co-authored-by: AnsonYeung <[email protected]>
1 parent 3e574d5 commit 4c8591d

File tree

14 files changed

+1164
-116
lines changed

14 files changed

+1164
-116
lines changed
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
import "./Option.mls"
2+
import "./Iter.mls"
3+
4+
open Option
5+
open Iter
6+
open FingerTreeList
7+
8+
fun normIdx(idx, len) =
9+
if idx < 0 then idx + len
10+
else idx
11+
12+
// Follows the semantics of Array.prototype.slice indices:
13+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
14+
fun normIdxSlice(i, len) =
15+
if i < 0 and
16+
i >= -len then len + i
17+
else 0
18+
else i
19+
20+
type Node[T] = Branch2[T] | Branch3[T]
21+
data class Branch2[T](size: Int, e1: T, e2: T)
22+
data class Branch3[T](size: Int, e1: T, e2: T, e3: T)
23+
24+
fun getNodeSize(node) = if node is
25+
Branch2(s, _, _) then s
26+
Branch3(s, _, _, _) then s
27+
_ then 1
28+
29+
object Nil
30+
data class View[T](e1: T, rest: FingerTree[T])
31+
32+
class FingerTree[T] extends IterableBase with
33+
fun iterator() =
34+
let current = this
35+
Iterator of () => if popFront(current) is
36+
Nil then Result.Done
37+
View(head, tail) then
38+
set current = tail
39+
Result.Next of head
40+
fun toString() = "[" + this joined(", ") + "]"
41+
fun length = FingerTreeList.length(this)
42+
fun at(i) = get(i)(this)
43+
fun concat(other) = FingerTreeList.concat(this, other)
44+
fun slice(beg, end) =
45+
let len = this.length
46+
set beg = normIdxSlice(beg, len)
47+
set end = normIdxSlice(end, len)
48+
if beg >= len do
49+
return Empty
50+
if end >= len do
51+
set end = len
52+
if end <= beg do
53+
return Empty
54+
FingerTreeList.dropLeftRight(beg, len - end)(this)
55+
56+
object Empty extends FingerTree[T] with
57+
fun toString() = "[" + this joined(", ") + "]"
58+
59+
class Single[T](val e: T) extends FingerTree[T] with
60+
fun toString() = "[" + this joined(", ") + "]"
61+
62+
// prefix and suffix are at most length 4
63+
class Deep[T](val size: Int, val prefix: Array[T], val middle: FingerTree[Node[T]], val suffix: Array[T]) extends FingerTree[T] with
64+
fun toString() = "[" + [...this].join(", ") + "]"
65+
66+
// amortized O(log n)
67+
fun concatMiddle(ft1, middle, ft2) = if [ft1, middle, ft2] is
68+
[Empty, [], right] then right
69+
[Empty, [x, ...xs], right] then cons(x, concatMiddle(Empty, xs, right))
70+
[Single(y), xs, right] then cons(y, concatMiddle(Empty, xs, right))
71+
[left, [], Empty] then left
72+
[left, [...xs, x], Empty] then snoc(concatMiddle(left, xs, Empty), x)
73+
[left, xs, Single(y)] then snoc(concatMiddle(left, xs, Empty), y)
74+
[Deep(s1, pre1, dt1, ay1), middle, Deep(s2, ax2, dt2, post2)] then
75+
Deep(s1 + s2 + middle.length, pre1, concatMiddle(dt1, toNodes([...ay1, ...middle, ...ax2]), dt2), post2)
76+
77+
// arr should be at most size 8, and at least size 2
78+
fun toNodes(arr) = if arr is
79+
[] then []
80+
[a, b, c, d] then [Branch2(getNodeSize(a) + getNodeSize(b), a, b), Branch2(getNodeSize(c) + getNodeSize(d), c, d)]
81+
[a, b, c, ...rest] then [Branch3(getNodeSize(a) + getNodeSize(b) + getNodeSize(c), a, b, c), ...toNodes(rest)]
82+
[a, b, ...rest] then [Branch2(getNodeSize(a) + getNodeSize(b), a, b), ...toNodes(rest)]
83+
84+
// O(1)
85+
fun arrayOfNode(node) = if node is
86+
Branch2(_, a, b) then [a, b]
87+
Branch3(_, a, b, c) then [a, b, c]
88+
89+
// affix should be at most size 4
90+
fun getAffixSize(arr) = if arr is
91+
[] then 0
92+
[a, ...rest] then getNodeSize(a) + getAffixSize(rest)
93+
94+
fun ithAffixElement(idx, arr) = if arr is
95+
[] then None
96+
[a, ...rest] then if idx < getNodeSize(a)
97+
then ithNode(idx, a)
98+
else ithAffixElement(idx - getNodeSize(a), rest)
99+
100+
fun ithNode(idx, node) = if node is
101+
Branch2(s, a, b) and
102+
idx >= 0 && idx < s and
103+
idx < getNodeSize(a) then ithNode(idx, a)
104+
else ithNode(idx - getNodeSize(a), b)
105+
else None
106+
Branch3(s, a, b, c) and
107+
idx >= 0 && idx < s and
108+
idx < getNodeSize(a) then ithNode(idx, a)
109+
idx < getNodeSize(a) + getNodeSize(b) then ithNode(idx - getNodeSize(a), b)
110+
else ithNode(idx - getNodeSize(a) - getNodeSize(b), c)
111+
else None
112+
a and idx == 0 then Some(a)
113+
else None
114+
115+
fun repeatPopFront(n)(xs) =
116+
if n is
117+
0 then xs
118+
_ then if popFront(xs) is
119+
Nil then xs
120+
View(_, rest) then repeatPopFront(n - 1)(rest)
121+
122+
fun repeatPopBack(n)(xs) =
123+
if n is
124+
0 then xs
125+
_ then if popBack(xs) is
126+
Nil then xs
127+
View(_, rest) then repeatPopBack(n - 1)(rest)
128+
129+
object SpliceMarker
130+
131+
132+
module FingerTreeList with...
133+
134+
135+
fun toFingerTree(xs) = if xs is
136+
FingerTree then xs
137+
else mk(...xs) // assume that the input is iterable if not a finger tree
138+
139+
// amortized O(1)
140+
fun (+:) cons(x, xs) = if toFingerTree(xs) is
141+
Empty then Single(x)
142+
Single(y) then Deep(getNodeSize(y) + getNodeSize(x), [x], Empty, [y])
143+
Deep(s, ax, dt, ay) then if (ax.length >= 4)
144+
then Deep(s + getNodeSize(x), [x, ax.0], cons(Branch3(
145+
getNodeSize(ax.1) + getNodeSize(ax.2) + getNodeSize(ax.3),
146+
ax.1, ax.2, ax.3), dt), ay)
147+
else Deep(s + getNodeSize(x), [x, ...ax], dt, ay)
148+
149+
// amortized O(1)
150+
fun (:+) snoc(xs, x) = if toFingerTree(xs) is
151+
Empty then Single(x)
152+
Single(y) then Deep(getNodeSize(y) + getNodeSize(x), [y], Empty, [x])
153+
Deep(s, ax, dt, ay) then if (ay.length >= 4)
154+
then Deep(s + getNodeSize(x), ax, snoc(dt, Branch3(
155+
getNodeSize(ay.0) + getNodeSize(ay.1) + getNodeSize(ay.2),
156+
ay.0, ay.1, ay.2)), [ay.3, x])
157+
else Deep(s + getNodeSize(x), ax, dt, [...ay, x])
158+
159+
fun mk(...args) = args.reduce(
160+
(acc, x) => snoc(acc, x),
161+
Empty)
162+
163+
fun isEmpty = case
164+
Empty then true
165+
_ then false
166+
167+
// O(1)
168+
fun length = case
169+
Array as a then a.length
170+
Empty then 0
171+
Single(x) then getNodeSize(x)
172+
Deep(s, _, _, _) then s
173+
174+
// amortized O(1)
175+
fun popFront(ft) = if toFingerTree(ft) is
176+
Empty then Nil
177+
Single(x) then View(x, Empty)
178+
Deep(s, [x], dt, ay) then if (popFront(dt)) is
179+
Nil then if ay is
180+
[y] then View(x, Single(y))
181+
[...front, y] then View(x, Deep(s - getNodeSize(x), front, Empty, [y]))
182+
View(x', dt') then
183+
View(x, Deep(s - getNodeSize(x), arrayOfNode(x'), dt', ay))
184+
Deep(s, [x, ...ax], dt, ay) then
185+
View(x, Deep(s - getNodeSize(x), ax, dt, ay))
186+
187+
// amortized O(1)
188+
fun popBack(ft) = if toFingerTree(ft) is
189+
Empty then Nil
190+
Single(x) then View(x, Empty)
191+
Deep(s, ax, dt, [x]) then if popBack(dt) is
192+
Nil then if ax is
193+
[y] then View(x, Single(y))
194+
[y, ...front] then View(x, Deep(s - getNodeSize(x), [y], Empty, front))
195+
View(x', dt') then
196+
View(x, Deep(s - getNodeSize(x), ax, dt', arrayOfNode(x')))
197+
Deep(s, ax, dt, [...ay, y]) then
198+
View(y, Deep(s - getNodeSize(y), ax, dt, ay))
199+
200+
// amortized O(log n)
201+
fun (++) concat(ft1, ft2) = concatMiddle(toFingerTree(ft1), [], toFingerTree(ft2))
202+
203+
// amortized O(log n)
204+
fun ith(idx) = case
205+
Empty then None
206+
Single(x) then ithNode(idx, x)
207+
Deep(s, ax, dt, ay) and
208+
idx >= 0 && idx < s and
209+
idx < getAffixSize(ax) then ithAffixElement(idx, ax)
210+
idx < getAffixSize(ax) + length(dt) then ith(idx - getAffixSize(ax))(dt)
211+
else ithAffixElement(idx - getAffixSize(ax) - length(dt), ay)
212+
else None
213+
214+
fun get(idx) = case
215+
Array as a then a.at(idx)
216+
FingerTree as lft then if ith(normIdx(idx, length(lft)))(lft) is
217+
None then throw "get: index out of bounds"
218+
Some(x) then x
219+
220+
fun toArray(ft) = [...ft]
221+
222+
/// amortized O(beg + end)
223+
fun dropLeftRight(beg, end)(und) = if beg < 0 || end < 0 || beg + end > length(und)
224+
then throw "dropLeftRight: index out of bounds"
225+
else repeatPopFront(beg)(repeatPopBack(end)(toFingerTree(und)))
226+
227+
fun isFingerTree(xs) = if xs is
228+
FingerTree then true
229+
else false
230+
231+
// The following functions are not meant for users; it's meant referred to by Runtime.mls
232+
// TODO: use `FingerTreeList.internals.concat` when this is implemented
233+
234+
fun __markerConcat(...arr) =
235+
let acc = FingerTreeList.mk()
236+
let idx = 0
237+
while idx < arr.length do
238+
if arr.[idx] is SpliceMarker then
239+
let ftl = arr.[idx + 1]
240+
set acc = acc ++ toFingerTree(ftl)
241+
set idx = idx + 2
242+
else
243+
set acc = acc :+ arr.[idx]
244+
set idx = idx + 1
245+
acc
246+
247+
val __split = SpliceMarker

hkmc2/shared/src/test/mlscript-compile/LazyArray.mls

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import "./Iter.mls"
32

43
open Iter
@@ -10,7 +9,7 @@ fun normIdx(i, len) =
109
else i
1110

1211
fun enforceArray = case
13-
Array | String | Str | TypedArray | LazyArr as a then a
12+
LazyArr | Array | String | Str | TypedArray as a then a
1413
ow then throw Error("Expected an Array, got: " + ow.toString())
1514

1615
fun concatHelper(a, l, ...args) =
@@ -155,11 +154,11 @@ module LazyArray with...
155154

156155
fun mk(...args) = View of args, 0, 0
157156
fun concat(...args) = concatHelper(mut [], 0, ...args)
158-
fun slice(beg, fin)(xs) =
157+
fun dropLeftRight(beg, fin)(xs) =
159158
if beg < 0 || fin < 0 do
160-
throw RangeError("LazyArray.slice: indices must be non-negative")
159+
throw RangeError("LazyArray.dropLeftRight: indices must be non-negative")
161160
if beg > xs.length || fin > xs.length do
162-
throw RangeError("LazyArray.slice: indices out of bounds")
161+
throw RangeError("LazyArray.dropLeftRight: indices out of bounds")
163162
sliceHelper(beg, xs.length - fin, xs)
164163

165164
fun equals(xs, ys) =

0 commit comments

Comments
 (0)