@grain/stdlib 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,498 @@
1
+ /**
2
+ * @module ImmutableSet: An ImmutableSet is a collection of unique values. Operations on an ImmutableSet do not mutate the set's internal state.
3
+ * @example import ImmutableSet from "immutableset"
4
+ *
5
+ * @since v0.5.4
6
+ */
7
+
8
+ import List from "list"
9
+ import Array from "array"
10
+
11
+ // implementation based on the paper "Implementing Sets Efficiently in a
12
+ // Functional Language" by Stephen Adams
13
+
14
+ record Node<a> {
15
+ key: a,
16
+ size: Number,
17
+ left: ImmutableSet<a>,
18
+ right: ImmutableSet<a>,
19
+ },
20
+ /**
21
+ * @section Types: Type declarations included in the ImmutableSet module.
22
+ */
23
+ enum ImmutableSet<a> {
24
+ Empty,
25
+ Tree(Node<a>),
26
+ }
27
+
28
+ /**
29
+ * @section Values: Functions and constants for working with ImmutableSets.
30
+ */
31
+
32
+ // semi-arbitrary value chosen for algorithm for determining when to balance
33
+ // trees; no tree can have a left subtree containing this number of times
34
+ // more elements than its right subtree or vice versa
35
+ let weight = 4
36
+
37
+ /**
38
+ * An empty set
39
+ *
40
+ * @since v0.5.4
41
+ */
42
+ export let empty = Empty
43
+
44
+ // returns the minimum value in a tree
45
+ let rec min = node => {
46
+ match (node) {
47
+ Tree({ key, left: Empty, _ }) => key,
48
+ Tree({ left, _ }) => min(left),
49
+ Empty => fail "Impossible: min of empty element in ImmutableSet",
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Provides the count of values within the set.
55
+ *
56
+ * @param set: The set to inspect
57
+ * @returns The count of elements in the set
58
+ *
59
+ * @since v0.5.4
60
+ */
61
+ export let size = set => {
62
+ match (set) {
63
+ Empty => 0,
64
+ Tree({ size, _ }) => size,
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Determines if the set contains no elements.
70
+ *
71
+ * @param set: The set to inspect
72
+ * @returns `true` if the given set is empty or `false` otherwise
73
+ *
74
+ * @since v0.5.4
75
+ */
76
+ export let isEmpty = set => {
77
+ match (set) {
78
+ Empty => true,
79
+ Tree(_) => false,
80
+ }
81
+ }
82
+
83
+ let unwrapTree = node => {
84
+ match (node) {
85
+ Empty => fail "Impossible: ImmutableSet unwrapTree got an empty tree node",
86
+ Tree(tree) => tree,
87
+ }
88
+ }
89
+
90
+ // helper function for creating a tree node with correct size from
91
+ // two balanced trees
92
+ let makeNode = (key, left, right) => {
93
+ Tree({ key, size: 1 + size(left) + size(right), left, right })
94
+ }
95
+
96
+ // note: see Figure 1 of paper referenced above for visual illustration of
97
+ // the rotations below
98
+
99
+ // node rotation moving the left subtree of the right node to the left side
100
+ let singleL = (key, left, right) => {
101
+ let { key: rKey, left: rl, right: rr, _ } = unwrapTree(right)
102
+ makeNode(rKey, makeNode(key, left, rl), rr)
103
+ }
104
+
105
+ // node rotation moving left child of right tree to the root
106
+ let doubleL = (key, left, right) => {
107
+ let { key: rKey, left: rl, right: rr, _ } = unwrapTree(right)
108
+ let { key: rlKey, left: rll, right: rlr, _ } = unwrapTree(rl)
109
+ makeNode(rlKey, makeNode(key, left, rll), makeNode(rKey, rlr, rr))
110
+ }
111
+
112
+ // node rotation moving the right subtree of the left node to the right side
113
+ let singleR = (key, left, right) => {
114
+ let { key: lKey, left: ll, right: lr, _ } = unwrapTree(left)
115
+ makeNode(lKey, ll, makeNode(key, lr, right))
116
+ }
117
+
118
+ // node rotation moving right child of left tree to the root
119
+ let doubleR = (key, left, right) => {
120
+ let { key: lKey, left: ll, right: lr, _ } = unwrapTree(left)
121
+ let { key: lrKey, left: lrl, right: lrr, _ } = unwrapTree(lr)
122
+ makeNode(lrKey, makeNode(lKey, ll, lrl), makeNode(key, lrr, right))
123
+ }
124
+
125
+ // creates a new node after either the left or right trees have just had an
126
+ // element inserted or removed from them, maintaining balance in the tree
127
+ let balancedNode = (key, left, right) => {
128
+ let makeNodeFn = if (size(left) + size(right) < 2) {
129
+ makeNode
130
+ } else if (size(right) > weight * size(left)) {
131
+ // if the right tree is too much larger than the left then move part of
132
+ // the right tree to the left side
133
+ let { left: rl, right: rr, _ } = unwrapTree(right)
134
+ if (size(rl) < size(rr)) singleL else doubleL
135
+ } else if (size(left) > weight * size(right)) {
136
+ // if the left tree is too much larger than the right then move part of
137
+ // the left tree to the right side
138
+ let { left: ll, right: lr, _ } = unwrapTree(left)
139
+ if (size(lr) < size(ll)) singleR else doubleR
140
+ } else {
141
+ // if neither tree is too much larger than the other then simply create
142
+ // a new node
143
+ makeNode
144
+ }
145
+
146
+ makeNodeFn(key, left, right)
147
+ }
148
+
149
+ /**
150
+ * Produces a new set by inserting the given value into the set. If the value
151
+ * already exists, the new set will have the same elements as the input set.
152
+ *
153
+ * @param key: The value to add
154
+ * @param set: The base set
155
+ * @returns A new set containing the new element
156
+ *
157
+ * @since v0.5.4
158
+ */
159
+ export let rec add = (key, set) => {
160
+ match (set) {
161
+ Empty => Tree({ key, size: 1, left: Empty, right: Empty }),
162
+ Tree({ key: nodeKey, left, right, _ }) => {
163
+ match (compare(key, nodeKey)) {
164
+ cmp when cmp < 0 => balancedNode(nodeKey, add(key, left), right),
165
+ cmp when cmp > 0 => balancedNode(nodeKey, left, add(key, right)),
166
+ _ => makeNode(key, left, right),
167
+ }
168
+ },
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Determines if the set contains the given value.
174
+ *
175
+ * @param key: The value to search for
176
+ * @param set: The set to search
177
+ * @returns `true` if the set contains the given value or `false` otherwise
178
+ *
179
+ * @since v0.5.4
180
+ */
181
+ export let rec contains = (key, set) => {
182
+ match (set) {
183
+ Empty => false,
184
+ Tree({ key: nodeKey, left, right, _ }) => {
185
+ match (compare(key, nodeKey)) {
186
+ cmp when cmp < 0 => contains(key, left),
187
+ cmp when cmp > 0 => contains(key, right),
188
+ _ => true,
189
+ }
190
+ },
191
+ }
192
+ }
193
+
194
+ // removes the minimum element from a tree
195
+ let rec removeMin = node => {
196
+ match (node) {
197
+ Tree({ left: Empty, right, _ }) => right,
198
+ Tree({ key, left, right, _ }) => balancedNode(key, removeMin(left), right),
199
+ _ => fail "Impossible: ImmutableSet removeMin on empty node",
200
+ }
201
+ }
202
+
203
+ // helper function for removing a node by creating a new node containing the
204
+ // removed node's left and right subtrees
205
+ let removeInner = (left, right) => {
206
+ match ((left, right)) {
207
+ (Empty, node) | (node, Empty) => node,
208
+ (left, right) => {
209
+ balancedNode(min(right), left, removeMin(right))
210
+ },
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Produces a new set without the given element. If the value doesn't exist in
216
+ * the set, the set will be returned unmodified.
217
+ *
218
+ * @param key: The value to exclude
219
+ * @param set: The set to exclude from
220
+ * @returns A new set without the excluded element
221
+ *
222
+ * @since v0.5.4
223
+ */
224
+ export let rec remove = (key, set) => {
225
+ match (set) {
226
+ Empty => Empty,
227
+ Tree({ key: nodeKey, left, right, _ }) => {
228
+ match (compare(key, nodeKey)) {
229
+ cmp when cmp < 0 => balancedNode(nodeKey, remove(key, left), right),
230
+ cmp when cmp > 0 => balancedNode(nodeKey, left, remove(key, right)),
231
+ _ => removeInner(left, right),
232
+ }
233
+ },
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Iterates the set, calling an iterator function on each element.
239
+ *
240
+ * @param fn: The iterator function to call with each element
241
+ * @param set: The set to iterate
242
+ *
243
+ * @since v0.5.4
244
+ */
245
+ export let forEach = (fn, set) => {
246
+ let rec forEachInner = node => {
247
+ match (node) {
248
+ Empty => void,
249
+ Tree({ key, left, right, _ }) => {
250
+ forEachInner(left)
251
+ fn(key): Void
252
+ forEachInner(right)
253
+ },
254
+ }
255
+ }
256
+ forEachInner(set)
257
+ }
258
+
259
+ /**
260
+ * Combines all elements of a set using a reducer function.
261
+ *
262
+ * @param fn: The reducer function to call on each element, where the value returned will be the next accumulator value
263
+ * @param init: The initial value to use for the accumulator on the first iteration
264
+ * @param set: The set to iterate
265
+ * @returns The final accumulator returned from `fn`
266
+ *
267
+ * @since v0.5.4
268
+ */
269
+ export let reduce = (fn, init, set) => {
270
+ let rec reduceInner = (acc, node) => {
271
+ match (node) {
272
+ Empty => acc,
273
+ Tree({ key, left, right, _ }) => {
274
+ let newAcc = fn(reduceInner(acc, left), key)
275
+ reduceInner(newAcc, right)
276
+ },
277
+ }
278
+ }
279
+ reduceInner(init, set)
280
+ }
281
+
282
+ // joins two trees with a value, preserving the BST property of left children
283
+ // being less the node and right children being greater than the node
284
+ let rec concat3 = (key, left, right) => {
285
+ match ((left, right)) {
286
+ (Empty, node) | (node, Empty) => add(key, node),
287
+ (Tree(left) as leftOpt, Tree(right) as rightOpt) => {
288
+ if (weight * left.size < right.size) {
289
+ balancedNode(right.key, concat3(key, leftOpt, right.left), right.right)
290
+ } else if (weight * right.size < left.size) {
291
+ balancedNode(left.key, left.left, concat3(key, left.right, rightOpt))
292
+ } else {
293
+ makeNode(key, leftOpt, rightOpt)
294
+ }
295
+ },
296
+ }
297
+ }
298
+
299
+ // returns a tree containing all of the nodes in the input tree whose values
300
+ // are less than the given value
301
+ let rec splitLt = (splitKey, node) => {
302
+ match (node) {
303
+ Empty => Empty,
304
+ Tree({ key, left, right, _ }) => {
305
+ match (compare(key, splitKey)) {
306
+ // we want this node, join it to the output
307
+ cmp when cmp < 0 => concat3(key, left, splitLt(splitKey, right)),
308
+ cmp when cmp > 0 => splitLt(splitKey, left),
309
+ _ => left,
310
+ }
311
+ },
312
+ }
313
+ }
314
+
315
+ // returns a tree containing all of the nodes in the input tree whose values
316
+ // are greater than the given value
317
+ let rec splitGt = (splitKey, node) => {
318
+ match (node) {
319
+ Empty => Empty,
320
+ Tree({ key, left, right, _ }) => {
321
+ match (compare(key, splitKey)) {
322
+ // we want this node, join it to the output
323
+ cmp when cmp > 0 => concat3(key, splitGt(splitKey, left), right),
324
+ cmp when cmp < 0 => splitGt(splitKey, right),
325
+ _ => right,
326
+ }
327
+ },
328
+ }
329
+ }
330
+
331
+ // concatenates two trees of arbitrary size
332
+ let concat = (node1, node2) => {
333
+ match (node2) {
334
+ Empty => node1,
335
+ _ => concat3(min(node2), node1, removeMin(node2)),
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Produces a new set without the elements from the input set where a predicate function returns `false`.
341
+ *
342
+ * @param fn: The predicate function to indicate which elements to exclude from the set, where returning `false` indicates the value should be excluded
343
+ * @param set: The set to iterate
344
+ * @returns A new set excluding the elements not fulfilling the predicate
345
+ *
346
+ * @since v0.5.4
347
+ */
348
+ export let filter = (fn, set) => {
349
+ let rec filterInner = node => {
350
+ match (node) {
351
+ Empty => Empty,
352
+ Tree({ key, left, right, _ }) => {
353
+ if (fn(key)) {
354
+ concat3(key, filterInner(left), filterInner(right))
355
+ } else {
356
+ concat(filterInner(left), filterInner(right))
357
+ }
358
+ },
359
+ }
360
+ }
361
+ filterInner(set)
362
+ }
363
+
364
+ /**
365
+ * Produces a new set without the elements from the input set where a predicate function returns `true`.
366
+ *
367
+ * @param fn: The predicate function to indicate which elements to exclude from the set, where returning `true` indicates the value should be excluded
368
+ * @param set: The set to iterate
369
+ * @returns A new set excluding the elements fulfilling the predicate
370
+ *
371
+ * @since v0.5.4
372
+ */
373
+ export let reject = (fn, set) => {
374
+ filter(key => !fn(key), set)
375
+ }
376
+
377
+ /**
378
+ * Combines two sets into a single set containing all elements from both sets.
379
+ *
380
+ * @param set1: The first set to combine
381
+ * @param set2: The second set to combine
382
+ * @returns A set containing all elements of both sets
383
+ *
384
+ * @since v0.5.4
385
+ */
386
+ export let rec union = (set1, set2) => {
387
+ match ((set1, set2)) {
388
+ (Empty, node) | (node, Empty) => node,
389
+ (node1, Tree(node2)) => {
390
+ let l = splitLt(node2.key, node1)
391
+ let r = splitGt(node2.key, node1)
392
+ concat3(node2.key, union(l, node2.left), union(r, node2.right))
393
+ },
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Combines two sets into a single set containing only the elements not shared between both sets.
399
+ *
400
+ * @param set1: The first set to combine
401
+ * @param set2: The second set to combine
402
+ * @returns A set containing only unshared elements from both sets
403
+ *
404
+ * @since v0.5.4
405
+ */
406
+ export let diff = (set1, set2) => {
407
+ let rec diffInner = (node1, node2) => {
408
+ match ((node1, node2)) {
409
+ (Empty, node) | (node, Empty) => node,
410
+ (node1, Tree(node2)) => {
411
+ let l = splitLt(node2.key, node1)
412
+ let r = splitGt(node2.key, node1)
413
+ concat(diffInner(l, node2.left), diffInner(r, node2.right))
414
+ },
415
+ }
416
+ }
417
+ union(diffInner(set1, set2), diffInner(set2, set1))
418
+ }
419
+
420
+ /**
421
+ * Combines two sets into a single set containing only the elements shared between both sets.
422
+ *
423
+ * @param set1: The first set to combine
424
+ * @param set2: The second set to combine
425
+ * @returns A set containing only shared elements from both sets
426
+ *
427
+ * @since v0.5.4
428
+ */
429
+ export let rec intersect = (set1, set2) => {
430
+ match ((set1, set2)) {
431
+ (Empty, _) | (_, Empty) => Empty,
432
+ (node1, Tree(node2)) => {
433
+ let l = splitLt(node2.key, node1)
434
+ let r = splitGt(node2.key, node1)
435
+ if (contains(node2.key, node1)) {
436
+ concat3(node2.key, intersect(l, node2.left), intersect(r, node2.right))
437
+ } else {
438
+ concat(intersect(l, node2.left), intersect(r, node2.right))
439
+ }
440
+ },
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Creates a set from a list.
446
+ *
447
+ * @param list: The list to convert
448
+ * @returns A set containing all list values
449
+ *
450
+ * @since v0.5.4
451
+ */
452
+ export let fromList = list => {
453
+ List.reduce((set, key) => add(key, set), empty, list)
454
+ }
455
+
456
+ /**
457
+ * Converts a set into a list of its elements.
458
+ *
459
+ * @param set: The set to convert
460
+ * @returns A list containing all set values
461
+ *
462
+ * @since v0.5.4
463
+ */
464
+ export let toList = set => {
465
+ let rec toListInner = (acc, node) => {
466
+ match (node) {
467
+ Empty => acc,
468
+ Tree({ key, left, right, _ }) => {
469
+ toListInner([key, ...toListInner(acc, right)], left)
470
+ },
471
+ }
472
+ }
473
+ toListInner([], set)
474
+ }
475
+
476
+ /**
477
+ * Creates a set from an array.
478
+ *
479
+ * @param array: The array to convert
480
+ * @returns A set containing all array values
481
+ *
482
+ * @since v0.5.4
483
+ */
484
+ export let fromArray = array => {
485
+ Array.reduce((set, key) => add(key, set), empty, array)
486
+ }
487
+
488
+ /**
489
+ * Converts a set into an array of its elements.
490
+ *
491
+ * @param set: The set to convert
492
+ * @returns An array containing all set values
493
+ *
494
+ * @since v0.5.4
495
+ */
496
+ export let toArray = set => {
497
+ Array.fromList(toList(set))
498
+ }