diff --git a/src/pure/List.mo b/src/pure/List.mo index 5598f42b0..0db774929 100644 --- a/src/pure/List.mo +++ b/src/pure/List.mo @@ -58,10 +58,12 @@ module { /// Runtime: O(size) /// /// Space: O(1) - public func size(list : List) : Nat = switch list { - case null 0; - case (?(_, t)) 1 + size t - }; + public func size(list : List) : Nat = ( + func go(n : Nat, list : List) : Nat = switch list { + case (?(_, t)) go(n + 1, t); + case null n + } + )(0, list); /// Check whether the list contains a given value. Uses the provided equality function to compare values. /// @@ -96,8 +98,8 @@ module { /// /// Space: O(1) public func get(list : List, n : Nat) : ?T = switch list { - case null null; - case (?(h, t)) if (n == 0) ?h else get(t, n - 1 : Nat) + case (?(h, t)) if (n == 0) ?h else get(t, n - 1 : Nat); + case null null }; /// Add `item` to the head of `list`, and return the new list. @@ -153,13 +155,12 @@ module { /// Runtime: O(size) /// /// Space: O(size) - public func reverse(list : List) : List { + public func reverse(list : List) : List = ( func go(acc : List, list : List) : List = switch list { - case null acc; - case (?(h, t)) go(?(h, acc), t) - }; - go(null, list) - }; + case (?(h, t)) go(?(h, acc), t); + case null acc + } + )(null, list); /// Call the given function for its side effect, with each list element in turn. /// @@ -176,8 +177,8 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func forEach(list : List, f : T -> ()) = switch list { - case null (); - case (?(h, t)) { f h; forEach(t, f) } + case (?(h, t)) { f h; forEach(t, f) }; + case null () }; /// Call the given function `f` on each list element and collect the results @@ -254,16 +255,16 @@ module { /// Space: O(size) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. - public func mapResult(list : List, f : T -> Result.Result) : Result.Result, E> = switch list { - case null #ok null; - case (?(h, t)) { - switch (f h, mapResult(t, f)) { - case (#ok r, #ok l) #ok(?(r, l)); - case (#err e, _) #err e; - case (_, #err e) #err e + + public func mapResult(list : List, f : T -> Result.Result) : Result.Result, E> = ( + func rev(acc : List, list : List, f : T -> Result.Result) : Result.Result, E> = switch list { + case null #ok acc; + case (?(h, t)) switch (f h) { + case (#ok fh) rev(?(fh, acc), t, f); + case (#err e) #err e } } - }; + )(null, list, f) |> Result.mapOk(_, func(l : List) : List = reverse l); /// Create two new lists from the results of a given function (`f`). /// The first list only includes the elements for which the given @@ -302,11 +303,26 @@ module { /// Runtime: O(size(l)) /// /// Space: O(size(l)) - public func concat(list1 : List, list2 : List) : List = switch list1 { - case null list2; - case (?(h, t)) ?(h, concat(t, list2)) - }; + public func concat(list1 : List, list2 : List) : List = ( + func revAppend(l : List, m : List) : List = switch l { + case (?(h, t)) revAppend(t, ?(h, m)); + case null m + } + )(reverse list1, list2); + /// Flatten, or repatedly concatenate, an iterator of lists as a list. + /// + /// Example: + /// ```motoko include=initialize + /// List.join( + /// [ ?(0, ?(1, ?(2, null))), + /// ?(3, ?(4, ?(5, null))) ] |> Iter.fromArray _) + /// ); // => ?(0, ?(1, ?(2, ?(3, ?(4, ?(5, null)))))) + /// ``` + /// + /// Runtime: O(size*size) + /// + /// Space: O(size*size) public func join(list : Iter.Iter>) : List { let ?l = list.next() else return null; let ls = join list; @@ -363,8 +379,8 @@ module { /// /// Space: O(1) public func drop(list : List, n : Nat) : List = if (n == 0) list else switch list { - case null null; - case (?(_h, t)) drop(t, n - 1 : Nat) + case (?(_, t)) drop(t, n - 1 : Nat); + case null null }; /// Collapses the elements in `list` into a single value by starting with `base` @@ -485,7 +501,7 @@ module { /// Merge two ordered lists into a single ordered list. /// This function requires both list to be ordered as specified - /// by the given relation `lessThanOrEqual`. + /// by the given relation `compare`. /// /// Example: /// ```motoko include=initialize @@ -493,7 +509,7 @@ module { /// List.merge( /// ?(1, ?(2, ?(4, null))), /// ?(2, ?(4, ?(6, null))), - /// func (n1, n2) = n1 <= n2 + /// Nat.compare /// ); // => ?(1, ?(2, ?(2, ?(4, ?(4, ?(6, null))))))), /// ``` /// @@ -503,14 +519,14 @@ module { /// /// *Runtime and space assumes that `lessThanOrEqual` runs in O(1) time and space. // TODO: replace by merge taking a compare : (T, T) -> Order.Order function? - public func merge(list1 : List, list2 : List, lessThanOrEqual : (T, T) -> Bool) : List = switch (list1, list2) { + public func merge(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : List = switch (list1, list2) { case (?(h1, t1), ?(h2, t2)) { - if (lessThanOrEqual(h1, h2)) { - ?(h1, merge(t1, list2, lessThanOrEqual)) - } else if (lessThanOrEqual(h2, h1)) { - ?(h2, merge(list1, t2, lessThanOrEqual)) + if (compare(h1, h2) != #greater) { + ?(h1, merge(t1, list2, compare)) + } else if (compare(h2, h1) != #greater) { + ?(h2, merge(list1, t2, compare)) } else { - ?(h1, ?(h2, merge(list1, list2, lessThanOrEqual))) + ?(h1, ?(h2, merge(list1, list2, compare))) } }; case (null, _) list2; @@ -529,10 +545,10 @@ module { /// /// Space: O(1) /// - /// *Runtime and space assumes that `equalFunc` runs in O(1) time and space. - public func equal(list1 : List, list2 : List, equalFunc : (T, T) -> Bool) : Bool = switch (list1, list2) { + /// *Runtime and space assumes that `equalItem` runs in O(1) time and space. + public func equal(list1 : List, list2 : List, equalItem : (T, T) -> Bool) : Bool = switch (list1, list2) { case (null, null) true; - case (?(h1, t1), ?(h2, t2)) equalFunc(h1, h2) and equal(t1, t2, equalFunc); + case (?(h1, t1), ?(h2, t2)) equalItem(h1, h2) and equal(t1, t2, equalItem); case _ false }; @@ -554,18 +570,14 @@ module { /// Space: O(1) /// /// *Runtime and space assumes that argument `compare` runs in O(1) time and space. - public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order { - type Order = Order.Order; - func go(list1 : List, list2 : List, comp : (T, T) -> Order) : Order = switch (list1, list2) { - case (?(h1, t1), ?(h2, t2)) switch (comp(h1, h2)) { - case (#equal) go(t1, t2, comp); - case o o - }; - case (null, null) #equal; - case (null, _) #less; - case _ #greater + public func compare(list1 : List, list2 : List, compareItem : (T, T) -> Order.Order) : Order.Order = switch (list1, list2) { + case (?(h1, t1), ?(h2, t2)) switch (compareItem(h1, h2)) { + case (#equal) compare(t1, t2, compareItem); + case o o }; - go(list1, list2, compare) // FIXME: only needed because of above shadowing + case (null, null) #equal; + case (null, _) #less; + case _ #greater }; /// Generate a list based on a length and a function that maps from @@ -585,8 +597,13 @@ module { /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func tabulate(n : Nat, f : Nat -> T) : List { - func go(at : Nat, n : Nat) : List = if (n == 0) null else ?(f at, go(at + 1, n - 1)); - go(0, n) + var i = 0; + var l : List = null; + while (i < n) { + l := ?(f i, l); + i += 1 + }; + reverse l }; /// Create a list with exactly one element. @@ -616,7 +633,15 @@ module { /// Runtime: O(n) /// /// Space: O(n) - public func repeat(item : T, n : Nat) : List = if (n == 0) null else ?(item, repeat(item, n - 1 : Nat)); + public func repeat(item : T, n : Nat) : List { + var res : List = null; + var i : Int = n; + while (i != 0) { + i -= 1; + res := ?(item, res) + }; + res + }; /// Create a list of pairs from a pair of lists. /// @@ -797,23 +822,27 @@ module { /// Runtime: O(size) /// /// Space: O(size) - public func fromIter(iter : Iter.Iter) : List = switch (iter.next()) { - case null null; - case (?item) ?(item, fromIter iter) + public func fromIter(iter : Iter.Iter) : List { + var result : List = null; + Iter.forEach( + iter, + func x = result := ?(x, result) + ); + reverse result }; public func toText(list : List, f : T -> Text) : Text { var text = "["; - var first = false; + var first = true; forEach( list, func(item : T) { if first { - text #= ", " + first := false } else { - first := true + text #= ", " }; - text #= f(item) + text #= f item } ); text # "]" diff --git a/test/pure/List.test.mo b/test/pure/List.test.mo index 547417cfe..917ff3ce9 100644 --- a/test/pure/List.test.mo +++ b/test/pure/List.test.mo @@ -7,7 +7,6 @@ import Nat "../../src/Nat"; import Order "../../src/Order"; import Debug "../../src/Debug"; import Int "../../src/Int"; -import Iter "../../src/Iter"; import Result "../../src/Result"; /* @@ -16,7 +15,6 @@ FIXME: (CHECK these) * flatten is quadratic * Array.mo doesn't implement `all`, `any`, `compare` -* merge takes lte predicate of type (T,T)-> Bool, not comparison of type: (T,T) -> Ord * split is not tail recursive and calls redundant helpers TODO: @@ -85,11 +83,6 @@ assert (List.size(l1) == 0); assert (List.size(l2) == 1); assert (List.size(l3) == 2); -// ## List functions -assert (List.size(l1) == 0); -assert (List.size(l2) == 1); -assert (List.size(l3) == 2); - do { Debug.print(" flatten"); @@ -178,6 +171,14 @@ func listRes(itm : Result.Result, Text>) : T.TestableItem { + public let item = (); + public func display(()) : Text = "()"; + public func equals((), ()) : Bool = true +}; + +let hugeList = List.repeat('Y', 100_000); + let mapResult = Suite.suite( "mapResult", [ @@ -200,6 +201,12 @@ let mapResult = Suite.suite( "fail last", List.mapResult(?(1, ?(2, ?(-3, null))), makeNatural), M.equals(listRes(#err("-3 is not a natural number."))) + ), + Suite.test( + "large", + List.mapResult(hugeList, func _ = #ok) + |> Result.mapOk, List.List<()>, ()>(_, func _ = null), + M.equals(T.result, ()>(T.listTestable<()> unit, unit, #ok null)) ) ] ); @@ -245,9 +252,9 @@ let tabulate = Suite.suite( ), Suite.test( "large-list", - List.tabulate(10000, func i = 0), + List.tabulate(100_000, func _ = 'Y'), M.equals( - T.list(T.natTestable, List.repeat(0, 10000)) + T.list(T.charTestable, hugeList) ) ) ] @@ -275,6 +282,11 @@ let concat = Suite.suite( M.equals( T.list(T.natTestable, List.tabulate(20000, func i = i)) ) + ), + Suite.test( + "huge-list", + List.concat(hugeList, List.singleton 'N') |> List.last _, + M.equals(T.optional(T.charTestable, ?'N')) ) ] ); @@ -398,6 +410,11 @@ let size = Suite.suite( "threesome", List.size(?(1, ?(2, ?(3, null)))), M.equals(T.nat(3)) + ), + Suite.test( + "many", + List.size hugeList, + M.equals(T.nat 100_000) ) ] ); @@ -413,7 +430,7 @@ let get = Suite.suite( Suite.test( "singleton-0", List.get(?(3, null), 0), - M.equals(T.optional(T.natTestable, ?3 : ?Nat)) + M.equals(T.optional(T.natTestable, ?3)) ), Suite.test( "singleton-1", @@ -428,12 +445,12 @@ let get = Suite.suite( Suite.test( "threesome-0", List.get(?(1, ?(2, ?(3, null))), 0), - M.equals(T.optional(T.natTestable, ?1 : ?Nat)) + M.equals(T.optional(T.natTestable, ?1)) ), Suite.test( "threesome-1", List.get(?(1, ?(2, ?(3, null))), 1), - M.equals(T.optional(T.natTestable, ?2 : ?Nat)) + M.equals(T.optional(T.natTestable, ?2)) ), Suite.test( "threesome-3", @@ -444,6 +461,16 @@ let get = Suite.suite( "threesome-4", List.get(?(1, ?(2, ?(3, null))), 4), M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + Suite.test( + "many", + List.get(hugeList, 99_999), + M.equals(T.optional(T.charTestable, ?'Y')) + ), + Suite.test( + "past many", + List.get(hugeList, 100_000), + M.equals(T.optional(T.charTestable, null : ?Char)) ) ] ); @@ -455,7 +482,6 @@ let reverse = Suite.suite( "empty list", List.reverse(List.empty()), M.equals(T.list(T.natTestable, null : List.List)) - ), Suite.test( "singleton", @@ -466,6 +492,11 @@ let reverse = Suite.suite( "threesome", List.reverse(?(1, ?(2, ?(3, null)))), M.equals(T.list(T.natTestable, ?(3, ?(2, ?(1, null))))) + ), + Suite.test( + "many", + List.reverse hugeList |> List.size _, + M.equals(T.nat 100_000) ) ] ); @@ -499,6 +530,15 @@ let forEach = Suite.suite( t }, M.equals(T.text("123")) + ), + Suite.test( + "many", + do { + var c = 0; + List.forEach(hugeList, func _ = c += 1); + c + }, + M.equals(T.nat 100_000) ) ] ); @@ -965,6 +1005,11 @@ let all = Suite.suite( "all empty", List.all(null, func x = x >= 1), M.equals(T.bool(true)) + ), + Suite.test( + "many", + List.all(hugeList, func c = c == 'Y'), + M.equals(T.bool true) ) ] ); @@ -986,6 +1031,11 @@ let any = Suite.suite( "empty", List.any(null, func x = true), M.equals(T.bool(false)) + ), + Suite.test( + "many", + List.any(hugeList, func c = c != 'Y'), + M.equals(T.bool false) ) ] ); @@ -998,7 +1048,7 @@ let merge = Suite.suite( List.merge( List.tabulate(10, func i = 2 * i), List.tabulate(10, func i = 2 * i + 1), - func(i, j) { i <= j } + Nat.compare ), M.equals( T.list(T.natTestable, List.tabulate(20, func i = i)) @@ -1021,7 +1071,7 @@ let merge = Suite.suite( { 2 * i } else { 2 * i + 1 } } ), - func(i, j) { i <= j } + Nat.compare ), M.equals( T.list(T.natTestable, List.tabulate(20, func i = i)) @@ -1033,7 +1083,7 @@ let merge = Suite.suite( List.merge( List.tabulate(10, func i = 2 * i), List.tabulate(10, func i = 2 * i), - func(i, j) { i <= j } + Nat.compare ), M.equals( T.list(T.natTestable, List.tabulate(20, func i = 2 * (i / 2))) @@ -1045,7 +1095,7 @@ let merge = Suite.suite( List.merge( List.tabulate(1000, func i = 2 * i), List.tabulate(1000, func i = 2 * i + 1), - func(i, j) { i <= j } + Nat.compare ), M.equals( T.list(T.natTestable, List.tabulate(2000, func i = i)) @@ -1493,6 +1543,40 @@ let chunks = Suite.suite( ] ); +let fromIter = Suite.suite( + "fromIter", + [ + Suite.test( + "small", + List.fromIter(Nat.range(0, 100)), + M.equals( + T.list( + T.natTestable, + List.tabulate(100, func i = i) + ) + ) + ), + Suite.test( + "large", + List.fromIter(Nat.range(0, 100_000)) |> List.size _, + M.equals(T.nat 100_000) + ) + ] +); + +let toText = Suite.suite( + "toText", + [ + Suite.test( + "small", + List.toText(?(0, ?(1, null)), Nat.toText), + M.equals( + T.text "[0, 1]" + ) + ) + ] +); + Suite.run( Suite.suite( "List", @@ -1528,7 +1612,9 @@ Suite.run( zipWith, zip, split, - chunks + chunks, + fromIter, + toText ] ) ) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index a0dfc232b..1bd1b6fd3 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -1211,12 +1211,12 @@ "public func all(list : List, f : T -> Bool) : Bool", "public func any(list : List, f : T -> Bool) : Bool", "public func chunks(list : List, n : Nat) : List>", - "public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order", + "public func compare(list1 : List, list2 : List, compareItem : (T, T) -> Order.Order) : Order.Order", "public func concat(list1 : List, list2 : List) : List", "public func contains(list : List, equal : (T, T) -> Bool, item : T) : Bool", "public func drop(list : List, n : Nat) : List", "public func empty() : List", - "public func equal(list1 : List, list2 : List, equalFunc : (T, T) -> Bool) : Bool", + "public func equal(list1 : List, list2 : List, equalItem : (T, T) -> Bool) : Bool", "public func filter(list : List, f : T -> Bool) : List", "public func filterMap(list : List, f : T -> ?R) : List", "public func find(list : List, f : T -> Bool) : ?T", @@ -1234,7 +1234,7 @@ "public type List", "public func map(list : List, f : T1 -> T2) : List", "public func mapResult(list : List, f : T -> Result.Result) : Result.Result, E>", - "public func merge(list1 : List, list2 : List, lessThanOrEqual : (T, T) -> Bool) : List", + "public func merge(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : List", "public func partition(list : List, f : T -> Bool) : (List, List)", "public func pop(list : List) : (?T, List)", "public func push(list : List, item : T) : List",