diff --git a/src/PersistentOrderedMap.mo b/src/PersistentOrderedMap.mo index d13e7efd..a4e310d0 100644 --- a/src/PersistentOrderedMap.mo +++ b/src/PersistentOrderedMap.mo @@ -42,8 +42,8 @@ module { /// The keys have the generic type `K` and the values the generic type `V`. /// Leaves are considered implicitly black. public type Map = { - #red : (Map, (K, V), Map); - #black : (Map, (K, V), Map); + #red : (Map, K, V, Map); + #black : (Map, K, V, Map); #leaf }; @@ -62,13 +62,14 @@ module { /// Example: /// ```motoko /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; /// /// let mapOps = Map.MapOps(Nat.compare); /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); /// - /// Debug.print(debug_show(Iter.toArray(Map.entries(rbMap)))); + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(rbMap)))); /// /// // [(0, "Zero"), (1, "One"), (2, "Two")] /// ``` @@ -88,8 +89,9 @@ module { /// Example: /// ```motoko /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; /// /// let mapOps = Map.MapOps(Nat.compare); /// var rbMap = Map.empty(); @@ -98,7 +100,7 @@ module { /// rbMap := mapOps.put(rbMap, 2, "Two"); /// rbMap := mapOps.put(rbMap, 1, "One"); /// - /// Debug.print(debug_show(Iter.toArray(Map.entries(rbMap)))); + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(rbMap)))); /// /// // [(0, "Zero"), (1, "One"), (2, "Two")] /// ``` @@ -118,22 +120,23 @@ module { /// Example: /// ```motoko /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; /// /// let mapOps = Map.MapOps(Nat.compare); /// let rbMap0 = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); /// /// let (rbMap1, old1) = mapOps.replace(rbMap0, 0, "Nil"); /// - /// Debug.print(debug_show(Iter.toArray(Map.entries(rbMap1)))); + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(rbMap1)))); /// Debug.print(debug_show(old1)); /// // [(0, "Nil"), (1, "One"), (2, "Two")] /// // ?"Zero" /// /// let (rbMap2, old2) = mapOps.replace(rbMap0, 3, "Three"); /// - /// Debug.print(debug_show(Iter.toArray(Map.entries(rbMap2)))); + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(rbMap2)))); /// Debug.print(debug_show(old2)); /// // [(0, "Zero"), (1, "One"), (2, "Two"), (3, "Three")] /// // null @@ -156,8 +159,9 @@ module { /// Example: /// ```motoko /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" + /// import Nat "mo:base/Nat"; /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; /// /// let mapOps = Map.MapOps(Nat.compare); /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); @@ -169,7 +173,7 @@ module { /// /// let newRbMap = mapOps.mapFilter(rbMap, f); /// - /// Debug.print(debug_show(Iter.toArray(Map.entries(newRbMap)))); + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(newRbMap)))); /// /// // [(1, "Twenty One"), (2, "Twenty Two")] /// ``` @@ -188,7 +192,8 @@ module { /// Example: /// ```motoko /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" + /// import Nat "mo:base/Nat"; + /// import Debug "mo:base/Debug"; /// /// let mapOps = Map.MapOps(Nat.compare); /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); @@ -215,13 +220,14 @@ module { /// Example: /// ```motoko /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" + /// import Nat "mo:base/Nat"; + /// import Debug "mo:base/Debug"; /// /// let mapOps = Map.MapOps(Nat.compare); /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); /// - /// Debug.print(debug_show(Iter.toArray(Map.entries(mapOps.delete(rbMap, 1))))); - /// Debug.print(debug_show(Iter.toArray(Map.entries(mapOps.delete(rbMap, 42))))); + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(mapOps.delete(rbMap, 1))))); + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(mapOps.delete(rbMap, 42))))); /// /// // [(0, "Zero"), (2, "Two")] /// // [(0, "Zero"), (1, "One"), (2, "Two")] @@ -242,22 +248,23 @@ module { /// Example: /// ```motoko /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; /// /// let mapOps = Map.MapOps(Nat.compare); /// let rbMap0 = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); /// /// let (rbMap1, old1) = mapOps.remove(rbMap0, 0); /// - /// Debug.print(debug_show(Iter.toArray(Map.entries(rbMap1)))); + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(rbMap1)))); /// Debug.print(debug_show(old1)); /// // [(1, "One"), (2, "Two")] /// // ?"Zero" /// /// let (rbMap2, old2) = mapOps.remove(rbMap0, 42); /// - /// Debug.print(debug_show(Iter.toArray(Map.entries(rbMap2)))); + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(rbMap2)))); /// Debug.print(debug_show(old2)); /// // [(0, "Zero"), (1, "One"), (2, "Two")] /// // null @@ -272,73 +279,296 @@ module { public func remove(rbMap : Map, key : K) : (Map, ?V) = Internal.remove(rbMap, compare, key); - }; + /// Create a new empty map. + /// + /// Example: + /// ```motoko + /// import Map "mo:base/PersistentOrderedMap"; + /// import Nat "mo:base/Nat"; + /// import Debug "mo:base/Debug"; + /// + /// let mapOps = Map.MapOps(Nat.compare); + /// + /// let rbMap = mapOps.empty(); + /// + /// Debug.print(debug_show(mapOps.size(rbMap))); + /// + /// // 0 + /// ``` + /// + /// Cost of empty map creation + /// Runtime: `O(1)`. + /// Space: `O(1)` + public func empty() : Map = #leaf; - /// Create a new empty map. - /// - /// Example: - /// ```motoko - /// import Map "mo:base/PersistentOrderedMap"; - /// - /// let rbMap = Map.empty(); - /// - /// Debug.print(debug_show(Map.size(rbMap))); - /// - /// // 0 - /// ``` - /// - /// Cost of empty map creation - /// Runtime: `O(1)`. - /// Space: `O(1)` - public func empty() : Map = #leaf; + /// Get an iterator for the entries of the `rbMap`, in ascending (`#fwd`) or descending (`#bwd`) order as specified by `direction`. + /// The iterator takes a snapshot view of the map and is not affected by concurrent modifications. + /// + /// Example: + /// ```motoko + /// import Map "mo:base/PersistentOrderedMap"; + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; + /// + /// let mapOps = Map.MapOps(Nat.compare); + /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); + /// + /// Debug.print(debug_show(Iter.toArray(mapOps.iter(rbMap, #fwd)))); + /// Debug.print(debug_show(Iter.toArray(mapOps.iter(rbMap, #bwd)))); + /// + /// // [(0, "Zero"), (1, "One"), (2, "Two")] + /// // [(2, "Two"), (1, "One"), (0, "Zero")] + /// ``` + /// + /// Cost of iteration over all elements: + /// Runtime: `O(n)`. + /// Space: `O(log(n))` retained memory plus garbage, see the note below. + /// where `n` denotes the number of key-value entries stored in the map. + /// + /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. + public func iter(rbMap : Map, direction : Direction) : I.Iter<(K, V)> + = Internal.iter(rbMap, direction); - type IterRep = List.List<{ #tr : Map; #xy : (K, V) }>; + /// Returns an Iterator (`Iter`) over the key-value pairs in the map. + /// Iterator provides a single method `next()`, which returns + /// pairs in ascending order by keys, or `null` when out of pairs to iterate over. + /// + /// Example: + /// ```motoko + /// import Map "mo:base/PersistentOrderedMap"; + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; + /// + /// let mapOps = Map.MapOps(Nat.compare); + /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); + /// + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(rbMap)))); + /// + /// + /// // [(0, "Zero"), (1, "One"), (2, "Two")] + /// ``` + /// Cost of iteration over all elements: + /// Runtime: `O(n)`. + /// Space: `O(log(n))` retained memory plus garbage, see the note below. + /// where `n` denotes the number of key-value entries stored in the map. + /// + /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. + public func entries(m : Map) : I.Iter<(K, V)> = iter(m, #fwd); - public type Direction = { #fwd; #bwd }; + /// Returns an Iterator (`Iter`) over the keys of the map. + /// Iterator provides a single method `next()`, which returns + /// keys in ascending order, or `null` when out of keys to iterate over. + /// + /// Example: + /// ```motoko + /// import Map "mo:base/PersistentOrderedMap"; + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; + /// + /// let mapOps = Map.MapOps(Nat.compare); + /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); + /// + /// Debug.print(debug_show(Iter.toArray(mapOps.keys(rbMap)))); + /// + /// // [0, 1, 2] + /// ``` + /// Cost of iteration over all elements: + /// Runtime: `O(n)`. + /// Space: `O(log(n))` retained memory plus garbage, see the note below. + /// where `n` denotes the number of key-value entries stored in the map. + /// + /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. + public func keys(m : Map) : I.Iter + = I.map(entries(m), func(kv : (K, V)) : K {kv.0}); - /// Get an iterator for the entries of the `rbMap`, in ascending (`#fwd`) or descending (`#bwd`) order as specified by `direction`. - /// The iterator takes a snapshot view of the map and is not affected by concurrent modifications. - /// - /// Example: - /// ```motoko - /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" - /// - /// let mapOps = Map.MapOps(Nat.compare); - /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); - /// - /// Debug.print(debug_show(Iter.toArray(Map.iter(rbMap, #fwd)))); - /// Debug.print(debug_show(Iter.toArray(Map.iter(rbMap, #bwd)))); - /// - /// // [(0, "Zero"), (1, "One"), (2, "Two")] - /// // [(2, "Two"), (1, "One"), (0, "Zero")] - /// ``` - /// - /// Cost of iteration over all elements: - /// Runtime: `O(n)`. - /// Space: `O(log(n))` retained memory plus garbage, see the note below. - /// where `n` denotes the number of key-value entries stored in the map. - /// - /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. - public func iter(rbMap : Map, direction : Direction) : I.Iter<(K, V)> { - let turnLeftFirst : MapTraverser - = func (l, xy, r, ts) { ?(#tr(l), ?(#xy(xy), ?(#tr(r), ts))) }; - let turnRightFirst : MapTraverser - = func (l, xy, r, ts) { ?(#tr(r), ?(#xy(xy), ?(#tr(l), ts))) }; + /// Returns an Iterator (`Iter`) over the values of the map. + /// Iterator provides a single method `next()`, which returns + /// values in no specific order, or `null` when out of values to iterate over. + /// + /// Example: + /// ```motoko + /// import Map "mo:base/PersistentOrderedMap"; + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; + /// + /// let mapOps = Map.MapOps(Nat.compare); + /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); + /// + /// Debug.print(debug_show(Iter.toArray(mapOps.vals(rbMap)))); + /// + /// // ["Zero", "One", "Two"] + /// ``` + /// Cost of iteration over all elements: + /// Runtime: `O(n)`. + /// Space: `O(log(n))` retained memory plus garbage, see the note below. + /// where `n` denotes the number of key-value entries stored in the map. + /// + /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. + public func vals(m : Map) : I.Iter + = I.map(entries(m), func(kv : (K, V)) : V {kv.1}); - switch direction { - case (#fwd) IterMap(rbMap, turnLeftFirst); - case (#bwd) IterMap(rbMap, turnRightFirst) - } + /// Creates a new map by applying `f` to each entry in `rbMap`. Each entry + /// `(k, v)` in the old map is transformed into a new entry `(k, v2)`, where + /// the new value `v2` is created by applying `f` to `(k, v)`. + /// + /// Example: + /// ```motoko + /// import Map "mo:base/PersistentOrderedMap"; + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; + /// + /// let mapOps = Map.MapOps(Nat.compare); + /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); + /// + /// func f(key : Nat, _val : Text) : Nat = key * 2; + /// + /// let resMap = mapOps.map(rbMap, f); + /// + /// Debug.print(debug_show(Iter.toArray(mapOps.entries(resMap)))); + /// + /// // [(0, 0), (1, 2), (2, 4)] + /// ``` + /// + /// Cost of mapping all the elements: + /// Runtime: `O(n)`. + /// Space: `O(n)` retained memory + /// where `n` denotes the number of key-value entries stored in the map. + public func map(rbMap : Map, f : (K, V1) -> V2) : Map + = Internal.map(rbMap, f); + + /// Determine the size of the tree as the number of key-value entries. + /// + /// Example: + /// ```motoko + /// import Map "mo:base/PersistentOrderedMap"; + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; + /// + /// let mapOps = Map.MapOps(Nat.compare); + /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); + /// + /// Debug.print(debug_show(mapOps.size(rbMap))); + /// + /// // 3 + /// ``` + /// + /// Runtime: `O(n)`. + /// Space: `O(1)`. + /// where `n` denotes the number of key-value entries stored in the tree. + public func size(rbMap : Map) : Nat + = Internal.size(rbMap); + + /// Collapses the elements in `rbMap` into a single value by starting with `base` + /// and progressively combining keys and values into `base` with `combine`. Iteration runs + /// left to right. + /// + /// Example: + /// ```motoko + /// import Map "mo:base/PersistentOrderedMap"; + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; + /// + /// let mapOps = Map.MapOps(Nat.compare); + /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); + /// + /// func folder(key : Nat, val : Text, accum : (Nat, Text)) : ((Nat, Text)) + /// = (key + accum.0, accum.1 # val); + /// + /// Debug.print(debug_show(mapOps.foldLeft(rbMap, (0, ""), folder))); + /// + /// // (3, "ZeroOneTwo") + /// ``` + /// + /// Cost of iteration over all elements: + /// Runtime: `O(n)`. + /// Space: depends on `combine` function plus garbage, see the note below. + /// where `n` denotes the number of key-value entries stored in the map. + /// + /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. + public func foldLeft( + rbMap : Map, + base : Accum, + combine : (K, Value, Accum) -> Accum + ) : Accum + = Internal.foldLeft(rbMap, base, combine); + + /// Collapses the elements in `rbMap` into a single value by starting with `base` + /// and progressively combining keys and values into `base` with `combine`. Iteration runs + /// right to left. + /// + /// Example: + /// ```motoko + /// import Map "mo:base/PersistentOrderedMap"; + /// import Nat "mo:base/Nat"; + /// import Iter "mo:base/Iter"; + /// import Debug "mo:base/Debug"; + /// + /// let mapOps = Map.MapOps(Nat.compare); + /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); + /// + /// func folder(key : Nat, val : Text, accum : (Nat, Text)) : ((Nat, Text)) + /// = (key + accum.0, accum.1 # val); + /// + /// Debug.print(debug_show(mapOps.foldRight(rbMap, (0, ""), folder))); + /// + /// // (3, "TwoOneZero") + /// ``` + /// + /// Cost of iteration over all elements: + /// Runtime: `O(n)`. + /// Space: depends on `combine` function plus garbage, see the note below. + /// where `n` denotes the number of key-value entries stored in the map. + /// + /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. + public func foldRight( + rbMap : Map, + base : Accum, + combine : (K, Value, Accum) -> Accum + ) : Accum + = Internal.foldRight(rbMap, base, combine); }; - type MapTraverser = (Map, (K, V), Map, IterRep) -> IterRep; + public type Direction = { #fwd; #bwd }; + + module Internal { + + public func fromIter(i : I.Iter<(K,V)>, compare : (K, K) -> O.Order) : Map + { + var map = #leaf : Map; + for(val in i) { + map := put(map, compare, val.0, val.1); + }; + map + }; + + type IterRep = List.List<{ #tr : Map; #xy : (K, V) }>; - class IterMap(rbMap : Map, mapTraverser : MapTraverser) { - var trees : IterRep = ?(#tr(rbMap), null); - public func next() : ?(K, V) { + public func iter(rbMap : Map, direction : Direction) : I.Iter<(K, V)> { + let turnLeftFirst : MapTraverser + = func (l, x, y, r, ts) { ?(#tr(l), ?(#xy(x, y), ?(#tr(r), ts))) }; + + let turnRightFirst : MapTraverser + = func (l, x, y, r, ts) { ?(#tr(r), ?(#xy(x, y), ?(#tr(l), ts))) }; + + switch direction { + case (#fwd) IterMap(rbMap, turnLeftFirst); + case (#bwd) IterMap(rbMap, turnRightFirst) + } + }; + + type MapTraverser = (Map, K, V, Map, IterRep) -> IterRep; + + class IterMap(rbMap : Map, mapTraverser : MapTraverser) { + var trees : IterRep = ?(#tr(rbMap), null); + public func next() : ?(K, V) { switch (trees) { case (null) { null }; case (?(#tr(#leaf), ts)) { @@ -349,277 +579,76 @@ module { trees := ts; ?xy }; - case (?(#tr(#red(l, xy, r)), ts)) { - trees := mapTraverser(l, xy, r, ts); + case (?(#tr(#red(l, x, y, r)), ts)) { + trees := mapTraverser(l, x, y, r, ts); next() }; - case (?(#tr(#black(l, xy, r)), ts)) { - trees := mapTraverser(l, xy, r, ts); + case (?(#tr(#black(l, x, y, r)), ts)) { + trees := mapTraverser(l, x, y, r, ts); next() } } } - }; - - /// Returns an Iterator (`Iter`) over the key-value pairs in the map. - /// Iterator provides a single method `next()`, which returns - /// pairs in ascending order by keys, or `null` when out of pairs to iterate over. - /// - /// Example: - /// ```motoko - /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" - /// - /// let mapOps = Map.MapOps(Nat.compare); - /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); - /// - /// Debug.print(debug_show(Iter.toArray(Map.entries(rbMap)))); - /// - /// - /// // [(0, "Zero"), (1, "One"), (2, "Two")] - /// ``` - /// Cost of iteration over all elements: - /// Runtime: `O(n)`. - /// Space: `O(log(n))` retained memory plus garbage, see the note below. - /// where `n` denotes the number of key-value entries stored in the map. - /// - /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. - public func entries(m : Map) : I.Iter<(K, V)> = iter(m, #fwd); - - /// Returns an Iterator (`Iter`) over the keys of the map. - /// Iterator provides a single method `next()`, which returns - /// keys in ascending order, or `null` when out of keys to iterate over. - /// - /// Example: - /// ```motoko - /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" - /// - /// let mapOps = Map.MapOps(Nat.compare); - /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); - /// - /// Debug.print(debug_show(Iter.toArray(Map.keys(rbMap)))); - /// - /// // [0, 1, 2] - /// ``` - /// Cost of iteration over all elements: - /// Runtime: `O(n)`. - /// Space: `O(log(n))` retained memory plus garbage, see the note below. - /// where `n` denotes the number of key-value entries stored in the map. - /// - /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. - public func keys(m : Map) : I.Iter - = I.map(entries(m), func(kv : (K, V)) : K {kv.0}); - - /// Returns an Iterator (`Iter`) over the values of the map. - /// Iterator provides a single method `next()`, which returns - /// values in no specific order, or `null` when out of values to iterate over. - /// - /// Example: - /// ```motoko - /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" - /// - /// let mapOps = Map.MapOps(Nat.compare); - /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); - /// - /// Debug.print(debug_show(Iter.toArray(Map.vals(rbMap)))); - /// - /// // ["Zero", "One", "Two"] - /// ``` - /// Cost of iteration over all elements: - /// Runtime: `O(n)`. - /// Space: `O(log(n))` retained memory plus garbage, see the note below. - /// where `n` denotes the number of key-value entries stored in the map. - /// - /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. - public func vals(m : Map) : I.Iter - = I.map(entries(m), func(kv : (K, V)) : V {kv.1}); - - /// Creates a new map by applying `f` to each entry in `rbMap`. Each entry - /// `(k, v)` in the old map is transformed into a new entry `(k, v2)`, where - /// the new value `v2` is created by applying `f` to `(k, v)`. - /// - /// Example: - /// ```motoko - /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" - /// - /// let mapOps = Map.MapOps(Nat.compare); - /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); - /// - /// func f(key : Nat, _val : Text) : Nat = key * 2; - /// - /// let resMap = Map.map(rbMap, f); - /// - /// Debug.print(debug_show(Iter.toArray(Map.entries(resMap)))); - /// - /// // [(0, 0), (1, 2), (2, 4)] - /// ``` - /// - /// Cost of mapping all the elements: - /// Runtime: `O(n)`. - /// Space: `O(n)` retained memory - /// where `n` denotes the number of key-value entries stored in the map. - public func map(rbMap : Map, f : (K, V1) -> V2) : Map { - func mapRec(m : Map) : Map { - switch m { - case (#leaf) { #leaf }; - case (#red(l, xy, r)) { - #red(mapRec l, (xy.0, f xy), mapRec r) - }; - case (#black(l, xy, r)) { - #black(mapRec l, (xy.0, f xy), mapRec r) - }; - } }; - mapRec(rbMap) - }; - /// Determine the size of the tree as the number of key-value entries. - /// - /// Example: - /// ```motoko - /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" - /// - /// let mapOps = Map.MapOps(Nat.compare); - /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); - /// - /// Debug.print(debug_show(Map.size(rbMap))); - /// - /// // 3 - /// ``` - /// - /// Runtime: `O(n)`. - /// Space: `O(1)`. - /// where `n` denotes the number of key-value entries stored in the tree. - public func size(t : Map) : Nat { - switch t { - case (#red(l, _, r)) { - size(l) + size(r) + 1 - }; - case (#black(l, _, r)) { - size(l) + size(r) + 1 - }; - case (#leaf) { 0 } - } - }; - - /// Collapses the elements in `rbMap` into a single value by starting with `base` - /// and progressively combining keys and values into `base` with `combine`. Iteration runs - /// left to right. - /// - /// Example: - /// ```motoko - /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" - /// - /// let mapOps = Map.MapOps(Nat.compare); - /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); - /// - /// func folder(key : Nat, val : Text, accum : (Nat, Text)) : ((Nat, Text)) - /// = (key + accum.0, accum.1 # val); - /// - /// Debug.print(debug_show(Map.foldLeft(rbMap, (0, ""), folder))); - /// - /// // (3, "ZeroOneTwo") - /// ``` - /// - /// Cost of iteration over all elements: - /// Runtime: `O(n)`. - /// Space: depends on `combine` function plus garbage, see the note below. - /// where `n` denotes the number of key-value entries stored in the map. - /// - /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. - public func foldLeft( - rbMap : Map, - base : Accum, - combine : (Key, Value, Accum) -> Accum - ) : Accum - { - switch (rbMap) { - case (#leaf) { base }; - case (#red(l, (k, v), r)) { - let left = foldLeft(l, base, combine); - let middle = combine(k, v, left); - foldLeft(r, middle, combine) + public func map(rbMap : Map, f : (K, V1) -> V2) : Map { + func mapRec(m : Map) : Map { + switch m { + case (#leaf) { #leaf }; + case (#red(l, x, y, r)) { + #red(mapRec l, x, f(x,y), mapRec r) + }; + case (#black(l, x, y, r)) { + #black(mapRec l, x, f(x, y), mapRec r) + }; + } }; - case (#black(l, (k, v), r)) { - let left = foldLeft(l, base, combine); - let middle = combine(k, v, left); - foldLeft(r, middle, combine) - } - } - }; + mapRec(rbMap) + }; - /// Collapses the elements in `rbMap` into a single value by starting with `base` - /// and progressively combining keys and values into `base` with `combine`. Iteration runs - /// right to left. - /// - /// Example: - /// ```motoko - /// import Map "mo:base/PersistentOrderedMap"; - /// import Nat "mo:base/Nat" - /// import Iter "mo:base/Iter" - /// - /// let mapOps = Map.MapOps(Nat.compare); - /// let rbMap = mapOps.fromIter(Iter.fromArray([(0, "Zero"), (2, "Two"), (1, "One")])); - /// - /// func folder(key : Nat, val : Text, accum : (Nat, Text)) : ((Nat, Text)) - /// = (key + accum.0, accum.1 # val); - /// - /// Debug.print(debug_show(Map.foldRight(rbMap, (0, ""), folder))); - /// - /// // (3, "TwoOneZero") - /// ``` - /// - /// Cost of iteration over all elements: - /// Runtime: `O(n)`. - /// Space: depends on `combine` function plus garbage, see the note below. - /// where `n` denotes the number of key-value entries stored in the map. - /// - /// Note: Full map iteration creates `O(n)` temporary objects that will be collected as garbage. - public func foldRight( - rbMap : Map, - base : Accum, - combine : (Key, Value, Accum) -> Accum - ) : Accum - { - switch (rbMap) { - case (#leaf) { base }; - case (#red(l, (k, v), r)) { - let right = foldRight(r, base, combine); - let middle = combine(k, v, right); - foldRight(l, middle, combine) - }; - case (#black(l, (k, v), r)) { - let right = foldRight(r, base, combine); - let middle = combine(k, v, right); - foldRight(l, middle, combine) + public func foldLeft( + rbMap : Map, + base : Accum, + combine : (Key, Value, Accum) -> Accum + ) : Accum + { + switch (rbMap) { + case (#leaf) { base }; + case (#red(l, k, v, r)) { + let left = foldLeft(l, base, combine); + let middle = combine(k, v, left); + foldLeft(r, middle, combine) + }; + case (#black(l, k, v, r)) { + let left = foldLeft(l, base, combine); + let middle = combine(k, v, left); + foldLeft(r, middle, combine) + } } - } - }; - - - public module Internal { + }; - public func fromIter(i : I.Iter<(K,V)>, compare : (K, K) -> O.Order) : Map + public func foldRight( + rbMap : Map, + base : Accum, + combine : (Key, Value, Accum) -> Accum + ) : Accum { - var map = #leaf : Map; - for(val in i) { - map := put(map, compare, val.0, val.1); - }; - map + switch (rbMap) { + case (#leaf) { base }; + case (#red(l, k, v, r)) { + let right = foldRight(r, base, combine); + let middle = combine(k, v, right); + foldRight(l, middle, combine) + }; + case (#black(l, k, v, r)) { + let right = foldRight(r, base, combine); + let middle = combine(k, v, right); + foldRight(l, middle, combine) + } + } }; - public func mapFilter(t : Map, compare : (K, K) -> O.Order, f : (K, V1) -> ?V2) : Map{ + public func mapFilter(rbMap : Map, compare : (K, K) -> O.Order, f : (K, V1) -> ?V2) : Map{ func combine(key : K, value1 : V1, acc : Map) : Map { switch (f(key, value1)){ case null { acc }; @@ -628,22 +657,34 @@ module { } } }; - foldLeft(t, #leaf, combine) + foldLeft(rbMap, #leaf, combine) + }; + + public func size(t : Map) : Nat { + switch t { + case (#red(l, _, _, r)) { + size(l) + size(r) + 1 + }; + case (#black(l, _, _, r)) { + size(l) + size(r) + 1 + }; + case (#leaf) { 0 } + } }; public func get(t : Map, compare : (K, K) -> O.Order, x : K) : ?V { switch t { - case (#red(l, xy, r)) { - switch (compare(x, xy.0)) { + case (#red(l, x1, y1, r)) { + switch (compare(x, x1)) { case (#less) { get(l, compare, x) }; - case (#equal) { ?xy.1 }; + case (#equal) { ?y1 }; case (#greater) { get(r, compare, x) } } }; - case (#black(l, xy, r)) { - switch (compare(x, xy.0)) { + case (#black(l, x1, y1, r)) { + switch (compare(x, x1)) { case (#less) { get(l, compare, x) }; - case (#equal) { ?xy.1 }; + case (#equal) { ?y1 }; case (#greater) { get(r, compare, x) } } }; @@ -653,8 +694,8 @@ module { func redden(t : Map) : Map { switch t { - case (#black (l, xy, r)) { - (#red (l, xy, r)) + case (#black (l, x, y, r)) { + (#red (l, x, y, r)) }; case _ { Debug.trap "RBTree.red" @@ -662,42 +703,42 @@ module { } }; - func lbalance(left : Map, xy : (K,V), right : Map) : Map { + func lbalance(left : Map, x : K, y : V, right : Map) : Map { switch (left, right) { - case (#red(#red(l1, xy1, r1), xy2, r2), r) { + case (#red(#red(l1, x1, y1, r1), x2, y2, r2), r) { #red( - #black(l1, xy1, r1), - xy2, - #black(r2, xy, r)) + #black(l1, x1, y1, r1), + x2, y2, + #black(r2, x, y, r)) }; - case (#red(l1, xy1, #red(l2, xy2, r2)), r) { + case (#red(l1, x1, y1, #red(l2, x2, y2, r2)), r) { #red( - #black(l1, xy1, l2), - xy2, - #black(r2, xy, r)) + #black(l1, x1, y1, l2), + x2, y2, + #black(r2, x, y, r)) }; case _ { - #black(left, xy, right) + #black(left, x, y, right) } } }; - func rbalance(left : Map, xy : (K,V), right : Map) : Map { + func rbalance(left : Map, x : K, y : V, right : Map) : Map { switch (left, right) { - case (l, #red(l1, xy1, #red(l2, xy2, r2))) { + case (l, #red(l1, x1, y1, #red(l2, x2, y2, r2))) { #red( - #black(l, xy, l1), - xy1, - #black(l2, xy2, r2)) + #black(l, x, y, l1), + x1, y1, + #black(l2, x2, y2, r2)) }; - case (l, #red(#red(l1, xy1, r1), xy2, r2)) { + case (l, #red(#red(l1, x1, y1, r1), x2, y2, r2)) { #red( - #black(l, xy, l1), - xy1, - #black(r1, xy2, r2)) + #black(l, x, y, l1), + x1, y1, + #black(r1, x2, y2, r2)) }; case _ { - #black(left, xy, right) + #black(left, x, y, right) }; } }; @@ -714,42 +755,42 @@ module { : Map{ func ins(tree : Map) : Map { switch tree { - case (#black(left, xy, right)) { - switch (compare (key, xy.0)) { + case (#black(left, x, y, right)) { + switch (compare (key, x)) { case (#less) { - lbalance(ins left, xy, right) + lbalance(ins left, x, y, right) }; case (#greater) { - rbalance(left, xy, ins right) + rbalance(left, x, y, ins right) }; case (#equal) { - let newVal = onClash({ new = val; old = xy.1 }); - #black(left, (key,newVal), right) + let newVal = onClash({ new = val; old = y }); + #black(left, key, newVal, right) } } }; - case (#red(left, xy, right)) { - switch (compare (key, xy.0)) { + case (#red(left, x, y, right)) { + switch (compare (key, x)) { case (#less) { - #red(ins left, xy, right) + #red(ins left, x, y, right) }; case (#greater) { - #red(left, xy, ins right) + #red(left, x, y, ins right) }; case (#equal) { - let newVal = onClash { new = val; old = xy.1 }; - #red(left, (key,newVal), right) + let newVal = onClash { new = val; old = y }; + #red(left, key, newVal, right) } } }; case (#leaf) { - #red(#leaf, (key,val), #leaf) + #red(#leaf, key, val, #leaf) } }; }; switch (ins m) { - case (#red(left, xy, right)) { - #black(left, xy, right); + case (#red(left, x, y, right)) { + #black(left, x, y, right); }; case other { other }; }; @@ -780,43 +821,43 @@ module { ) : Map = replace(m, compare, key, val).0; - func balLeft(left : Map, xy : (K,V), right : Map) : Map { + func balLeft(left : Map, x : K, y : V, right : Map) : Map { switch (left, right) { - case (#red(l1, xy1, r1), r) { + case (#red(l1, x1, y1, r1), r) { #red( - #black(l1, xy1, r1), - xy, + #black(l1, x1, y1, r1), + x, y, r) }; - case (_, #black(l2, xy2, r2)) { - rbalance(left, xy, #red(l2, xy2, r2)) + case (_, #black(l2, x2, y2, r2)) { + rbalance(left, x, y, #red(l2, x2, y2, r2)) }; - case (_, #red(#black(l2, xy2, r2), xy3, r3)) { + case (_, #red(#black(l2, x2, y2, r2), x3, y3, r3)) { #red( - #black(left, xy, l2), - xy2, - rbalance(r2, xy3, redden r3)) + #black(left, x, y, l2), + x2, y2, + rbalance(r2, x3, y3, redden r3)) }; case _ { Debug.trap "balLeft" }; } }; - func balRight(left : Map, xy : (K,V), right : Map) : Map { + func balRight(left : Map, x : K, y : V, right : Map) : Map { switch (left, right) { - case (l, #red(l1, xy1, r1)) { + case (l, #red(l1, x1, y1, r1)) { #red( l, - xy, - #black(l1, xy1, r1)) + x, y, + #black(l1, x1, y1, r1)) }; - case (#black(l1, xy1, r1), r) { - lbalance(#red(l1, xy1, r1), xy, r); + case (#black(l1, x1, y1, r1), r) { + lbalance(#red(l1, x1, y1, r1), x, y, r); }; - case (#red(l1, xy1, #black(l2, xy2, r2)), r3) { + case (#red(l1, x1, y1, #black(l2, x2, y2, r2)), r3) { #red( - lbalance(redden l1, xy1, l2), - xy2, - #black(r2, xy, r3)) + lbalance(redden l1, x1, y1, l2), + x2, y2, + #black(r2, x, y, r3)) }; case _ { Debug.trap "balRight" }; } @@ -826,39 +867,39 @@ module { switch (left, right) { case (#leaf, _) { right }; case (_, #leaf) { left }; - case (#red (l1, xy1, r1), - #red (l2, xy2, r2)) { + case (#red (l1, x1, y1, r1), + #red (l2, x2, y2, r2)) { switch (append (r1, l2)) { - case (#red (l3, xy3, r3)) { + case (#red (l3, x3, y3, r3)) { #red( - #red(l1, xy1, l3), - xy3, - #red(r3, xy2, r2)) + #red(l1, x1, y1, l3), + x3, y3, + #red(r3, x2, y2, r2)) }; case r1l2 { - #red(l1, xy1, #red(r1l2, xy2, r2)) + #red(l1, x1, y1, #red(r1l2, x2, y2, r2)) } } }; - case (t1, #red(l2, xy2, r2)) { - #red(append(t1, l2), xy2, r2) + case (t1, #red(l2, x2, y2, r2)) { + #red(append(t1, l2), x2, y2, r2) }; - case (#red(l1, xy1, r1), t2) { - #red(l1, xy1, append(r1, t2)) + case (#red(l1, x1, y1, r1), t2) { + #red(l1, x1, y1, append(r1, t2)) }; - case (#black(l1, xy1, r1), #black (l2, xy2, r2)) { + case (#black(l1, x1, y1, r1), #black (l2, x2, y2, r2)) { switch (append (r1, l2)) { - case (#red (l3, xy3, r3)) { + case (#red (l3, x3, y3, r3)) { #red( - #black(l1, xy1, l3), - xy3, - #black(r3, xy2, r2)) + #black(l1, x1, y1, l3), + x3, y3, + #black(r3, x2, y2, r2)) }; case r1l2 { balLeft ( l1, - xy1, - #black(r1l2, xy2, r2) + x1, y1, + #black(r1l2, x2, y2, r2) ) } } @@ -871,43 +912,43 @@ module { public func remove(tree : Map, compare : (K, K) -> O.Order, x : K) : (Map, ?V) { var y0 : ?V = null; - func delNode(left : Map, xy : (K, V), right : Map) : Map { - switch (compare (x, xy.0)) { + func delNode(left : Map, x1 : K, y1 : V, right : Map) : Map { + switch (compare (x, x1)) { case (#less) { let newLeft = del left; switch left { - case (#black(_, _, _)) { - balLeft(newLeft, xy, right) + case (#black(_, _, _, _)) { + balLeft(newLeft, x1, y1, right) }; case _ { - #red(newLeft, xy, right) + #red(newLeft, x1, y1, right) } } }; case (#greater) { let newRight = del right; switch right { - case (#black(_, _, _)) { - balRight(left, xy, newRight) + case (#black(_, _, _, _)) { + balRight(left, x1, y1, newRight) }; case _ { - #red(left, xy, newRight) + #red(left, x1, y1, newRight) } } }; case (#equal) { - y0 := ?xy.1; + y0 := ?y1; append(left, right) }; } }; func del(tree : Map) : Map { switch tree { - case (#red(left, xy, right)) { - delNode(left, xy, right) + case (#red(left, x, y, right)) { + delNode(left, x, y, right) }; - case (#black(left, xy, right)) { - delNode(left, xy, right) + case (#black(left, x, y, right)) { + delNode(left, x, y, right) }; case (#leaf) { tree @@ -915,8 +956,8 @@ module { }; }; switch (del(tree)) { - case (#red(left, xy, right)) { - (#black(left, xy, right), y0); + case (#red(left, x, y, right)) { + (#black(left, x, y, right), y0); }; case other { (other, y0) }; }; diff --git a/test/PersistentOrderedMap.prop.test.mo b/test/PersistentOrderedMap.prop.test.mo index b2b553cd..d0c12609 100644 --- a/test/PersistentOrderedMap.prop.test.mo +++ b/test/PersistentOrderedMap.prop.test.mo @@ -17,11 +17,11 @@ let { run; test; suite } = Suite; class MapMatcher(expected : Map.Map) : M.Matcher> { public func describeMismatch(actual : Map.Map, _description : M.Description) { - Debug.print(debug_show (Iter.toArray(Map.entries(actual))) # " should be " # debug_show (Iter.toArray(Map.entries(expected)))) + Debug.print(debug_show (Iter.toArray(natMap.entries(actual))) # " should be " # debug_show (Iter.toArray(natMap.entries(expected)))) }; public func matches(actual : Map.Map) : Bool { - Iter.toArray(Map.entries(actual)) == Iter.toArray(Map.entries(expected)) + Iter.toArray(natMap.entries(actual)) == Iter.toArray(natMap.entries(expected)) } }; @@ -39,7 +39,7 @@ object Random { }; public func nextEntries(range: (Nat, Nat), size: Nat): [(Nat, Text)] { - Array.tabulate<(Nat, Text)>(size, func(_ix) { + Array.tabulate<(Nat, Text)>(size, func(_ix) { let key = nextNat(range); (key, debug_show(key)) } ) } }; @@ -51,8 +51,8 @@ func mapGen(samples_number: Nat, size: Nat, range: (Nat, Nat)): Iter.Iter { n += 1; - if (n > samples_number) { - null + if (n > samples_number) { + null } else { ?natMap.fromIter(Random.nextEntries(range, size).vals()) } @@ -66,10 +66,10 @@ func run_all_props(range: (Nat, Nat), size: Nat, map_samples: Nat, query_samples var error_msg: Text = ""; test(name, do { var error = true; - label stop for(map in mapGen(map_samples, size, range)) { + label stop for(map in mapGen(map_samples, size, range)) { if (not f(map)) { error_msg := "Property \"" # name # "\" failed\n"; - error_msg #= "\n m: " # debug_show(Iter.toArray(Map.entries(map))); + error_msg #= "\n m: " # debug_show(Iter.toArray(natMap.entries(map))); break stop; } }; @@ -84,7 +84,7 @@ func run_all_props(range: (Nat, Nat), size: Nat, map_samples: Nat, query_samples let key = Random.nextNat(range); if (not f(map, key)) { error_msg #= "Property \"" # name # "\" failed"; - error_msg #= "\n m: " # debug_show(Iter.toArray(Map.entries(map))); + error_msg #= "\n m: " # debug_show(Iter.toArray(natMap.entries(map))); error_msg #= "\n k: " # debug_show(key); break stop; } @@ -95,12 +95,12 @@ func run_all_props(range: (Nat, Nat), size: Nat, map_samples: Nat, query_samples }; run( suite("Property tests", - [ + [ suite("empty", [ test("get(empty(), k) == null", label res : Bool { for (_query_ix in Iter.range(0, query_samples-1)) { let k = Random.nextNat(range); - if(natMap.get(Map.empty(), k) != null) + if(natMap.get(natMap.empty(), k) != null) break res(false); }; true; @@ -116,7 +116,7 @@ func run_all_props(range: (Nat, Nat), size: Nat, map_samples: Nat, query_samples natMap.get(natMap.put(natMap.put(m, k, v1), k, v2), k) == v2 }), ]), - + suite("replace", [ prop_with_key("replace(m, k, v).0 == put(m, k, v)", func (m, k) { natMap.replace(m, k, "v").0 == natMap.put(m, k, "v") @@ -156,7 +156,7 @@ func run_all_props(range: (Nat, Nat), size: Nat, map_samples: Nat, query_samples MapMatcher(m2).matches(m1) }), prop_with_key("remove(put(m, k, v), k).1 == ?v", func (m, k) { - natMap.remove(natMap.put(m, k, "v"), k).1 == ?"v" + natMap.remove(natMap.put(m, k, "v"), k).1 == ?"v" }), prop_with_key("remove(remove(m, k).0, k).1 == null", func (m, k) { natMap.remove(natMap.remove(m, k).0, k).1 == null @@ -170,71 +170,71 @@ func run_all_props(range: (Nat, Nat), size: Nat, map_samples: Nat, query_samples suite("size", [ prop_with_key("size(put(m, k, v)) == size(m) + int(get(m, k) == null)", func (m, k) { - Map.size(natMap.put(m, k, "v")) == Map.size(m) + (if (natMap.get(m, k) == null) {1} else {0}) + natMap.size(natMap.put(m, k, "v")) == natMap.size(m) + (if (natMap.get(m, k) == null) {1} else {0}) }), prop_with_key("size(delete(m, k)) + int(get(m, k) != null) == size(m)", func (m, k) { - Map.size(natMap.delete(m, k)) + (if (natMap.get(m, k) != null) {1} else {0}) == Map.size(m) + natMap.size(natMap.delete(m, k)) + (if (natMap.get(m, k) != null) {1} else {0}) == natMap.size(m) }) ]), - + suite("iter,keys,vals,entries", [ prop("fromIter(iter(m, #fwd)) == m", func (m) { - MapMatcher(m).matches(natMap.fromIter(Map.iter(m, #fwd))) + MapMatcher(m).matches(natMap.fromIter(natMap.iter(m, #fwd))) }), prop("fromIter(iter(m, #bwd)) == m", func (m) { - MapMatcher(m).matches(natMap.fromIter(Map.iter(m, #bwd))) + MapMatcher(m).matches(natMap.fromIter(natMap.iter(m, #bwd))) }), prop("iter(m, #fwd) = zip(key(m), vals(m))", func (m) { - let k = Map.keys(m); - let v = Map.vals(m); - for (e in Map.iter(m, #fwd)) { + let k = natMap.keys(m); + let v = natMap.vals(m); + for (e in natMap.iter(m, #fwd)) { if (e.0 != k.next() or e.1 != v.next()) return false; }; return true; }), prop("entries(m) == iter(m, #fwd)", func (m) { - let it = Map.iter(m, #fwd); - for (e in Map.entries(m)) { + let it = natMap.iter(m, #fwd); + for (e in natMap.entries(m)) { if (it.next() != e) return false; }; return true }) ]), - + suite("mapFilter", [ prop_with_key("get(mapFilter(m, (!=k)), k) == null", func (m, k) { - natMap.get(natMap.mapFilter(m, + natMap.get(natMap.mapFilter(m, func (ki, vi) { if (ki != k) {?vi} else {null}}), k) == null }), prop_with_key("get(mapFilter(put(m, k, v), (==k)), k) == ?v", func (m, k) { - natMap.get(natMap.mapFilter(natMap.put(m, k, "v"), + natMap.get(natMap.mapFilter(natMap.put(m, k, "v"), func (ki, vi) { if (ki == k) {?vi} else {null}}), k) == ?"v" }) ]), - + suite("map", [ prop("map(m, id) == m", func (m) { - MapMatcher(m).matches(Map.map(m, func (k, v) {v})) + MapMatcher(m).matches(natMap.map(m, func (k, v) {v})) }) ]), - + suite("folds", [ prop("foldLeft as iter(#fwd)", func (m) { - let it = Map.iter(m, #fwd); - Map.foldLeft(m, true, func (k, v, acc) {acc and it.next() == ?(k, v)}) + let it = natMap.iter(m, #fwd); + natMap.foldLeft(m, true, func (k, v, acc) {acc and it.next() == ?(k, v)}) }), prop("foldRight as iter(#bwd)", func(m) { - let it = Map.iter(m, #bwd); - Map.foldRight(m, true, func (k, v, acc) {acc and it.next() == ?(k, v)}) + let it = natMap.iter(m, #bwd); + natMap.foldRight(m, true, func (k, v, acc) {acc and it.next() == ?(k, v)}) }) ]), - ])) + ])) }; run_all_props((1, 3), 0, 1, 10); run_all_props((1, 5), 5, 100, 100); run_all_props((1, 10), 10, 100, 100); run_all_props((1, 100), 20, 100, 100); -run_all_props((1, 1000), 100, 100, 100); \ No newline at end of file +run_all_props((1, 1000), 100, 100, 100); diff --git a/test/PersistentOrderedMap.test.mo b/test/PersistentOrderedMap.test.mo index 0e6794c6..adcf9799 100644 --- a/test/PersistentOrderedMap.test.mo +++ b/test/PersistentOrderedMap.test.mo @@ -14,18 +14,18 @@ let { run; test; suite } = Suite; let entryTestable = T.tuple2Testable(T.natTestable, T.textTestable); +let natMapOps = Map.MapOps(Nat.compare); + class MapMatcher(expected : [(Nat, Text)]) : M.Matcher> { public func describeMismatch(actual : Map.Map, _description : M.Description) { - Debug.print(debug_show (Iter.toArray(Map.entries(actual))) # " should be " # debug_show (expected)) + Debug.print(debug_show (Iter.toArray(natMapOps.entries(actual))) # " should be " # debug_show (expected)) }; public func matches(actual : Map.Map) : Bool { - Iter.toArray(Map.entries(actual)) == expected + Iter.toArray(natMapOps.entries(actual)) == expected } }; -let natMapOps = Map.MapOps(Nat.compare); - func checkMap(rbMap : Map.Map) { ignore blackDepth(rbMap) }; @@ -41,13 +41,13 @@ func blackDepth(node : Map.Map) : Nat { }; switch node { case (#leaf) 0; - case (#red(left, (key, _), right)) { + case (#red(left, key, _, right)) { let leftBlacks = checkNode(left, key, right); assert (not isRed(left)); assert (not isRed(right)); leftBlacks }; - case (#black(left, (key, _), right)) { + case (#black(left, key, _, right)) { checkNode(left, key, right) + 1 } } @@ -56,7 +56,7 @@ func blackDepth(node : Map.Map) : Nat { func isRed(node : Map.Map) : Bool { switch node { - case (#red(_, _, _)) true; + case (#red(_, _, _, _)) true; case _ false } }; @@ -64,10 +64,10 @@ func isRed(node : Map.Map) : Bool { func checkKey(node : Map.Map, isValid : Nat -> Bool) { switch node { case (#leaf) {}; - case (#red( _, (key, _), _)) { + case (#red( _, key, _, _)) { assert (isValid(key)) }; - case (#black( _, (key, _), _)) { + case (#black( _, key, _, _)) { assert (isValid(key)) } } @@ -88,7 +88,7 @@ func getAll(rbTree : Map.Map, keys : [Nat]) { func clear(initialRbMap : Map.Map) : Map.Map { var rbMap = initialRbMap; - for ((key, value) in Map.entries(initialRbMap)) { + for ((key, value) in natMapOps.entries(initialRbMap)) { // stable iteration assert (value == debug_show (key)); let (newMap, result) = natMapOps.remove(rbMap, key); @@ -125,7 +125,7 @@ func ifKeyLessThan(threshold : Nat, f : (Nat, Text) -> Text) : (Nat, Text) -> ?T /* --------------------------------------- */ var buildTestMap = func() : Map.Map { - Map.empty() + natMapOps.empty() }; run( @@ -134,32 +134,32 @@ run( [ test( "size", - Map.size(buildTestMap()), + natMapOps.size(buildTestMap()), M.equals(T.nat(0)) ), test( "iterate forward", - Iter.toArray(Map.iter(buildTestMap(), #fwd)), + Iter.toArray(natMapOps.iter(buildTestMap(), #fwd)), M.equals(T.array<(Nat, Text)>(entryTestable, [])) ), test( "iterate backward", - Iter.toArray(Map.iter(buildTestMap(), #bwd)), + Iter.toArray(natMapOps.iter(buildTestMap(), #bwd)), M.equals(T.array<(Nat, Text)>(entryTestable, [])) ), test( "entries", - Iter.toArray(Map.entries(buildTestMap())), + Iter.toArray(natMapOps.entries(buildTestMap())), M.equals(T.array<(Nat, Text)>(entryTestable, [])) ), test( "keys", - Iter.toArray(Map.keys(buildTestMap())), + Iter.toArray(natMapOps.keys(buildTestMap())), M.equals(T.array(T.natTestable, [])) ), test( "vals", - Iter.toArray(Map.vals(buildTestMap())), + Iter.toArray(natMapOps.vals(buildTestMap())), M.equals(T.array(T.textTestable, [])) ), test( @@ -189,27 +189,27 @@ run( ), test( "empty right fold keys", - Map.foldRight(buildTestMap(), "", concatenateKeys), + natMapOps.foldRight(buildTestMap(), "", concatenateKeys), M.equals(T.text("")) ), test( "empty left fold keys", - Map.foldLeft(buildTestMap(), "", concatenateKeys), + natMapOps.foldLeft(buildTestMap(), "", concatenateKeys), M.equals(T.text("")) ), test( "empty right fold values", - Map.foldRight(buildTestMap(), "", concatenateValues), + natMapOps.foldRight(buildTestMap(), "", concatenateValues), M.equals(T.text("")) ), test( "empty left fold values", - Map.foldLeft(buildTestMap(), "", concatenateValues), + natMapOps.foldLeft(buildTestMap(), "", concatenateValues), M.equals(T.text("")) ), test( "traverse empty map", - Map.map(buildTestMap(), multiplyKeyAndConcat), + natMapOps.map(buildTestMap(), multiplyKeyAndConcat), MapMatcher([]) ), test( @@ -224,7 +224,7 @@ run( /* --------------------------------------- */ buildTestMap := func() : Map.Map { - insert(Map.empty(), 0); + insert(natMapOps.empty(), 0); }; var expected = expectedEntries([0]); @@ -235,32 +235,32 @@ run( [ test( "size", - Map.size(buildTestMap()), + natMapOps.size(buildTestMap()), M.equals(T.nat(1)) ), test( "iterate forward", - Iter.toArray(Map.iter(buildTestMap(), #fwd)), + Iter.toArray(natMapOps.iter(buildTestMap(), #fwd)), M.equals(T.array<(Nat, Text)>(entryTestable, expected)) ), test( "iterate backward", - Iter.toArray(Map.iter(buildTestMap(), #bwd)), + Iter.toArray(natMapOps.iter(buildTestMap(), #bwd)), M.equals(T.array<(Nat, Text)>(entryTestable, expected)) ), test( "entries", - Iter.toArray(Map.entries(buildTestMap())), + Iter.toArray(natMapOps.entries(buildTestMap())), M.equals(T.array<(Nat, Text)>(entryTestable, expected)) ), test( "keys", - Iter.toArray(Map.keys(buildTestMap())), + Iter.toArray(natMapOps.keys(buildTestMap())), M.equals(T.array(T.natTestable, [0])) ), test( "vals", - Iter.toArray(Map.vals(buildTestMap())), + Iter.toArray(natMapOps.vals(buildTestMap())), M.equals(T.array(T.textTestable, ["0"])) ), test( @@ -303,27 +303,27 @@ run( ), test( "right fold keys", - Map.foldRight(buildTestMap(), "", concatenateKeys), + natMapOps.foldRight(buildTestMap(), "", concatenateKeys), M.equals(T.text("0")) ), test( "left fold keys", - Map.foldLeft(buildTestMap(), "", concatenateKeys), + natMapOps.foldLeft(buildTestMap(), "", concatenateKeys), M.equals(T.text("0")) ), test( "right fold values", - Map.foldRight(buildTestMap(), "", concatenateValues), + natMapOps.foldRight(buildTestMap(), "", concatenateValues), M.equals(T.text("0")) ), test( "left fold values", - Map.foldLeft(buildTestMap(), "", concatenateValues), + natMapOps.foldLeft(buildTestMap(), "", concatenateValues), M.equals(T.text("0")) ), test( "traverse map", - Map.map(buildTestMap(), multiplyKeyAndConcat), + natMapOps.map(buildTestMap(), multiplyKeyAndConcat), MapMatcher([(0, "00")]) ), test( @@ -348,7 +348,7 @@ func rebalanceTests(buildTestMap : () -> Map.Map) : [Suite.Suite] = [ test( "size", - Map.size(buildTestMap()), + natMapOps.size(buildTestMap()), M.equals(T.nat(3)) ), test( @@ -358,27 +358,27 @@ func rebalanceTests(buildTestMap : () -> Map.Map) : [Suite.Suite] = ), test( "iterate forward", - Iter.toArray(Map.iter(buildTestMap(), #fwd)), + Iter.toArray(natMapOps.iter(buildTestMap(), #fwd)), M.equals(T.array<(Nat, Text)>(entryTestable, expected)) ), test( "iterate backward", - Iter.toArray(Map.iter(buildTestMap(), #bwd)), + Iter.toArray(natMapOps.iter(buildTestMap(), #bwd)), M.equals(T.array<(Nat, Text)>(entryTestable, Array.reverse(expected))) ), test( "entries", - Iter.toArray(Map.entries(buildTestMap())), + Iter.toArray(natMapOps.entries(buildTestMap())), M.equals(T.array<(Nat, Text)>(entryTestable, expected)) ), test( "keys", - Iter.toArray(Map.keys(buildTestMap())), + Iter.toArray(natMapOps.keys(buildTestMap())), M.equals(T.array(T.natTestable, [0, 1, 2])) ), test( "vals", - Iter.toArray(Map.vals(buildTestMap())), + Iter.toArray(natMapOps.vals(buildTestMap())), M.equals(T.array(T.textTestable, ["0", "1", "2"])) ), test( @@ -402,27 +402,27 @@ func rebalanceTests(buildTestMap : () -> Map.Map) : [Suite.Suite] = ), test( "right fold keys", - Map.foldRight(buildTestMap(), "", concatenateKeys), + natMapOps.foldRight(buildTestMap(), "", concatenateKeys), M.equals(T.text("210")) ), test( "left fold keys", - Map.foldLeft(buildTestMap(), "", concatenateKeys), + natMapOps.foldLeft(buildTestMap(), "", concatenateKeys), M.equals(T.text("012")) ), test( "right fold values", - Map.foldRight(buildTestMap(), "", concatenateValues), + natMapOps.foldRight(buildTestMap(), "", concatenateValues), M.equals(T.text("210")) ), test( "left fold values", - Map.foldLeft(buildTestMap(), "", concatenateValues), + natMapOps.foldLeft(buildTestMap(), "", concatenateValues), M.equals(T.text("012")) ), test( "traverse map", - Map.map(buildTestMap(), multiplyKeyAndConcat), + natMapOps.map(buildTestMap(), multiplyKeyAndConcat), MapMatcher([(0, "00"), (1, "21"), (2, "42")]) ), test( @@ -443,7 +443,7 @@ func rebalanceTests(buildTestMap : () -> Map.Map) : [Suite.Suite] = ]; buildTestMap := func() : Map.Map { - var rbMap = Map.empty() : Map.Map; + var rbMap = natMapOps.empty() : Map.Map; rbMap := insert(rbMap, 2); rbMap := insert(rbMap, 1); rbMap := insert(rbMap, 0); @@ -455,7 +455,7 @@ run(suite("rebalance left, left", rebalanceTests(buildTestMap))); /* --------------------------------------- */ buildTestMap := func() : Map.Map { - var rbMap = Map.empty() : Map.Map; + var rbMap = natMapOps.empty() : Map.Map; rbMap := insert(rbMap, 2); rbMap := insert(rbMap, 0); rbMap := insert(rbMap, 1); @@ -467,7 +467,7 @@ run(suite("rebalance left, right", rebalanceTests(buildTestMap))); /* --------------------------------------- */ buildTestMap := func() : Map.Map { - var rbMap = Map.empty() : Map.Map; + var rbMap = natMapOps.empty() : Map.Map; rbMap := insert(rbMap, 0); rbMap := insert(rbMap, 2); rbMap := insert(rbMap, 1); @@ -479,7 +479,7 @@ run(suite("rebalance right, left", rebalanceTests(buildTestMap))); /* --------------------------------------- */ buildTestMap := func() : Map.Map { - var rbMap = Map.empty() : Map.Map; + var rbMap = natMapOps.empty() : Map.Map; rbMap := insert(rbMap, 0); rbMap := insert(rbMap, 1); rbMap := insert(rbMap, 2);