diff --git a/build.fsx b/build.fsx index d7009d6d3b..61e087cc5d 100644 --- a/build.fsx +++ b/build.fsx @@ -150,6 +150,14 @@ let buildLibraryIfNotExists() = if not (pathExists (baseDir "build/fable-library")) then buildLibrary() + // runFableWithArgs ("watch " + libDir) [ + // "--outDir " + buildDir + // "--fableLib " + buildDir + // "--exclude Fable.Core" + // "--define FX_NO_BIGINT" + // "--define FABLE_LIBRARY" + // ] + let buildLibraryTs() = let projectDir = "src/fable-library" let buildDirTs = "build/fable-library-ts" @@ -178,6 +186,7 @@ let testJsFast() = ] runFableWithArgs "src/fable-compiler-js/src" [ + "--forcePkgs" "--exclude Fable.Core" "--define LOCAL_TEST" ] diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 575f5cee7d..b1f37d3fdf 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -943,13 +943,13 @@ module Util = | [TransformExpr com ctx expr], None -> libCall com ctx r "List" "singleton" [|expr|] | exprs, None -> - [|makeArray com ctx exprs|] - |> libCall com ctx r "List" "ofArray" + [|List.rev exprs |> makeArray com ctx|] + |> libCall com ctx r "List" "newList" | [TransformExpr com ctx head], Some(TransformExpr com ctx tail) -> libCall com ctx r "List" "cons" [|head; tail|] | exprs, Some(TransformExpr com ctx tail) -> - [|makeArray com ctx exprs; tail|] - |> libCall com ctx r "List" "ofArrayWithTail" + [|List.rev exprs |> makeArray com ctx; tail|] + |> libCall com ctx r "List" "newListWithTail" | Fable.NewOption (value, t) -> match value with | Some (TransformExpr com ctx e) -> @@ -1204,11 +1204,11 @@ module Util = | Fable.ListHead -> // get range (com.TransformAsExpr(ctx, fableExpr)) "head" - libCall com ctx range "List" "head" [|com.TransformAsExpr(ctx, fableExpr)|] + libCall com ctx range "List" "head_" [|com.TransformAsExpr(ctx, fableExpr)|] | Fable.ListTail -> // get range (com.TransformAsExpr(ctx, fableExpr)) "tail" - libCall com ctx range "List" "tail" [|com.TransformAsExpr(ctx, fableExpr)|] + libCall com ctx range "List" "tail_" [|com.TransformAsExpr(ctx, fableExpr)|] | Fable.TupleIndex index -> match fableExpr with diff --git a/src/fable-library/List.fs b/src/fable-library/List.fs index 91e5ca0dbe..eea1f1ab71 100644 --- a/src/fable-library/List.fs +++ b/src/fable-library/List.fs @@ -12,48 +12,143 @@ module SR = let listsHadDifferentLengths = "The lists had different lengths." let notEnoughElements = "The input sequence has an insufficient number of elements." -[] -[] -type LinkedList<'T when 'T: comparison> = - { head: 'T; mutable tail: LinkedList<'T> option } - - static member Empty: 'T list = { head = Unchecked.defaultof<'T>; tail = None } - static member Cons (x: 'T, xs: 'T list) = { head = x; tail = Some xs } - - static member inline internal ConsNoTail (x: 'T) = { head = x; tail = None } - member inline internal xs.SetConsTail (t: 'T list) = xs.tail <- Some t - member inline internal xs.AppendConsNoTail (x: 'T) = - let t = List.ConsNoTail x - xs.SetConsTail t - t +[] +let private allocate (i: int): ResizeArray<'T> = jsNative - member xs.IsEmpty = xs.tail.IsNone - - member xs.Length = - let rec loop i xs = - match xs.tail with - | None -> i - | Some t -> loop (i + 1) t - loop 0 xs +// [] +// [] +[] +type ResizeList<'T>(count: int, values: ResizeArray<'T>, ?tail: ResizeList<'T>) = + // if count = 0 && Option.isSome tail then + // failwith "Unexpected, empty list with tail" + + member inline internal _.HiddenCount = count + member inline internal _.HiddenValues = values + member inline internal _.HiddenTail = tail + member inline _.IsEmpty = count <= 0 + + member _.Length = + match tail with + | Some tail -> count + tail.Length + | None -> count + + member internal xs.Add(x: 'T) = + if count = values.Count then + values.Add(x) + ResizeList<'T>(values.Count, values, ?tail=tail) + elif count = 0 then + ResizeList<'T>(1, ResizeArray [|x|]) + else + ResizeList<'T>(1, ResizeArray [|x|], xs) + + member internal xs.AddRange(ys: 'T ResizeArray) = + if count = values.Count then + values.AddRange(ys) + ResizeList<'T>(values.Count, values, ?tail=tail) + elif count = 0 then + ResizeList<'T>(ys.Count, ys) + else + ResizeList<'T>(ys.Count, ys, xs) + + member internal xs.Append(ys: 'T ResizeList) = + match count, tail with + | 0, _ -> ys + | _, None -> ResizeList<'T>(count, values, ys) + | _, Some _ -> + let values = allocate xs.Length + let mutable revIdx = values.Count + xs.Iterate(fun v -> + revIdx <- revIdx - 1 + values.[revIdx] <- v) + ResizeList<'T>(values.Count, values, ys) + + member internal _.Iterate f = + for i = count - 1 downto 0 do + f values.[i] + match tail with + | Some t -> t.Iterate f + | None -> () + + member internal _.IterateBack f = + match tail with + | Some t -> t.IterateBack f + | None -> () + for i = 0 to count - 1 do + f values.[i] + + member internal xs.DoWhile f = + let rec loop idx (xs: 'T ResizeList) = + if idx >= 0 && f xs.HiddenValues.[idx] then + let idx = idx - 1 + if idx < 0 then + match xs.HiddenTail with + | Some t -> loop (t.HiddenCount - 1) t + | None -> () + else loop idx xs + loop (count - 1) xs + + member internal xs.Reverse() = + let values = allocate xs.Length + let mutable i = -1 + xs.Iterate(fun v -> + i <- i + 1 + values.[i] <- v) + ResizeList<'T>.NewList(values) + + static member inline Singleton(x: 'T) = + ResizeList<'T>.NewList(ResizeArray [|x|]) + + static member inline NewList (values: ResizeArray<'T>) = + ResizeList(values.Count, values) + + static member inline NewList (count, values) = + ResizeList(count, values) + + static member inline Empty: ResizeList<'T> = + ResizeList<'T>.NewList(ResizeArray()) + + static member inline Cons (x: 'T, xs: 'T list) = xs.Add(x) + + member _.TryHead = + if count > 0 + then Some values.[count - 1] + else None member xs.Head = - match xs.tail with + match xs.TryHead with + | Some h -> h | None -> invalidArg "list" SR.inputListWasEmpty - | Some _ -> xs.head + + member _.TryTail = + if count > 1 then + ResizeList<'T>(count - 1, values, ?tail=tail) |> Some + elif count = 1 then + match tail with + | Some t -> Some t + | None -> ResizeList<'T>(count - 1, values) |> Some + else + None member xs.Tail = - match xs.tail with + match xs.TryTail with + | Some h -> h | None -> invalidArg "list" SR.inputListWasEmpty - | Some t -> t - member xs.Item with get (index) = - let rec loop i xs = - match xs.tail with + member inline internal _.HeadUnsafe = + values.[count - 1] + + member inline internal _.TailUnsafe = + if count = 1 && Option.isSome tail then tail.Value + else ResizeList<'T>(count - 1, values, ?tail=tail) + + member _.Item with get (index: int) = + let actualIndex = count - 1 - index + if actualIndex >= 0 then + values.[actualIndex] + else + match tail with | None -> invalidArg "index" SR.indexOutOfBounds - | Some t -> - if i = index then xs.head - else loop (i + 1) t - loop 0 xs + | Some t -> t.Item(index - count) override xs.ToString() = "[" + System.String.Join("; ", xs) + "]" @@ -63,27 +158,18 @@ type LinkedList<'T when 'T: comparison> = then true else let ys = other :?> 'T list - let rec loop xs ys = - match xs.tail, ys.tail with - | None, None -> true - | None, Some _ -> false - | Some _, None -> false - | Some xt, Some yt -> - if xs.head = ys.head - then loop xt yt - else false - loop xs ys + if xs.Length <> ys.Length then false + else Seq.forall2 Unchecked.equals xs ys override xs.GetHashCode() = let inline combineHash i x y = (x <<< 1) + y + 631 * i - let iMax = 18 // limit the hash - let rec loop i h (xs: 'T list) = - match xs.tail with - | None -> h - | Some t -> - if i > iMax then h - else loop (i + 1) (combineHash i h (hash xs.head)) t - loop 0 0 xs + let mutable h = 0 + let mutable i = -1 + xs.DoWhile(fun v -> + i <- i + 1 + h <- combineHash i h (Unchecked.hash v) + i < 18) // limit the hash count + h interface IJsonSerializable with member this.toJSON(_key) = @@ -91,47 +177,40 @@ type LinkedList<'T when 'T: comparison> = interface System.IComparable with member xs.CompareTo(other: obj) = - let ys = other :?> 'T list - let rec loop xs ys = - match xs.tail, ys.tail with - | None, None -> 0 - | None, Some _ -> -1 - | Some _, None -> 1 - | Some xt, Some yt -> - let c = compare xs.head ys.head - if c = 0 then loop xt yt else c - loop xs ys + Seq.compareWith Unchecked.compare xs (other :?> 'T list) interface System.Collections.Generic.IEnumerable<'T> with - member xs.GetEnumerator(): System.Collections.Generic.IEnumerator<'T> = - new ListEnumerator<'T>(xs) :> System.Collections.Generic.IEnumerator<'T> + member _.GetEnumerator(): System.Collections.Generic.IEnumerator<'T> = + let mutable curIdx = count + let mutable curValues = values + let mutable curTail = tail + { new System.Collections.Generic.IEnumerator<'T> with + member __.Current = curValues.[curIdx] + interface System.Collections.IEnumerator with + member __.Current = box curValues.[curIdx] + member __.MoveNext() = + curIdx <- curIdx - 1 + if curIdx < 0 then + match curTail with + | Some t -> + curIdx <- t.HiddenCount - 1 + curValues <- t.HiddenValues + curTail <- t.HiddenTail + curIdx >= 0 + | None -> false + else true + member __.Reset() = + curIdx <- count + curValues <- values + curTail <- tail + interface System.IDisposable with + member __.Dispose() = () } interface System.Collections.IEnumerable with member xs.GetEnumerator(): System.Collections.IEnumerator = ((xs :> System.Collections.Generic.IEnumerable<'T>).GetEnumerator() :> System.Collections.IEnumerator) -and ListEnumerator<'T when 'T: comparison>(xs: 'T list) = - let mutable it = xs - let mutable current = Unchecked.defaultof<'T> - interface System.Collections.Generic.IEnumerator<'T> with - member __.Current = current - interface System.Collections.IEnumerator with - member __.Current = box (current) - member __.MoveNext() = - match it.tail with - | None -> false - | Some t -> - current <- it.head - it <- t - true - member __.Reset() = - it <- xs - current <- Unchecked.defaultof<'T> - interface System.IDisposable with - member __.Dispose() = () - -and 'T list when 'T: comparison = LinkedList<'T> -and List<'T> when 'T: comparison = LinkedList<'T> +and 'T list = ResizeList<'T> // [] // [] @@ -139,11 +218,16 @@ and List<'T> when 'T: comparison = LinkedList<'T> let inline indexNotFound() = raise (System.Collections.Generic.KeyNotFoundException(SR.keyNotFoundAlt)) -let empty () = List.Empty +let newList values = ResizeList<'T>.NewList (values) + +let newListWithTail (xs: 'T ResizeArray) (tail: 'T list) = + tail.AddRange(xs) + +let empty () = ResizeList.Empty -let cons (x: 'T) (xs: 'T list) = List.Cons(x, xs) +let cons (x: 'T) (xs: 'T list) = ResizeList.Cons (x, xs) -let singleton x = List.Cons(x, List.Empty) +let singleton (x: 'T) = ResizeList.Singleton (x) let isEmpty (xs: 'T list) = xs.IsEmpty @@ -151,234 +235,182 @@ let length (xs: 'T list) = xs.Length let head (xs: 'T list) = xs.Head -let tryHead (xs: 'T list) = - if xs.IsEmpty then None - else Some xs.Head +let tryHead (xs: 'T list) = xs.TryHead let tail (xs: 'T list) = xs.Tail -let rec tryLast (xs: 'T list) = - if xs.IsEmpty then None - else - let t = xs.Tail - if t.IsEmpty then Some xs.Head - else tryLast t +let head_ (xs: 'T list) = xs.HeadUnsafe + +let tail_ (xs: 'T list) = xs.TailUnsafe + +// let (|Cons|Nil|) xs = +// if isEmpty xs then Nil +// else Cons (head xs, tail xs) + +let tryLast (xs: 'T list) = + if xs.Length > 0 + then Some xs.[xs.Length - 1] + else None let last (xs: 'T list) = match tryLast xs with - | Some x -> x - | None -> failwith SR.inputListWasEmpty + | Some h -> h + | None -> invalidArg "list" SR.inputListWasEmpty let compareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list): int = - let rec loop (xs: 'T list) (ys: 'T list) = - match xs.IsEmpty, ys.IsEmpty with - | true, true -> 0 - | true, false -> -1 - | false, true -> 1 - | false, false -> - let c = comparer xs.Head ys.Head - if c = 0 then loop xs.Tail ys.Tail else c - loop xs ys - -let toArray (xs: 'T list) = - let len = xs.Length - let res = Array.zeroCreate len - let rec loop i (xs: 'T list) = - if not xs.IsEmpty then - res.[i] <- xs.Head - loop (i + 1) xs.Tail - loop 0 xs - res - -// let rec fold (folder: 'State -> 'T -> 'State) (state: 'State) (xs: 'T list) = -// if xs.IsEmpty then state -// else fold folder (folder state xs.Head) xs.Tail - -let fold (folder: 'State -> 'T -> 'State) (state: 'State) (xs: 'T list) = + Seq.compareWith comparer xs ys + +let fold (folder: 'acc -> 'T -> 'acc) (state: 'acc) (xs: 'T list) = let mutable acc = state - let mutable xs = xs - while not xs.IsEmpty do - acc <- folder acc xs.Head - xs <- xs.Tail + xs.Iterate(fun v -> acc <- folder acc v) acc -let reverse (xs: 'T list) = - fold (fun acc x -> List.Cons(x, acc)) List.Empty xs - -let foldBack (folder: 'T -> 'State -> 'State) (xs: 'T list) (state: 'State) = - // fold (fun acc x -> folder x acc) state (reverse xs) - Array.foldBack folder (toArray xs) state - -let foldIndexed (folder: int -> 'State -> 'T -> 'State) (state: 'State) (xs: 'T list) = - let rec loop i acc (xs: 'T list) = - if xs.IsEmpty then acc - else loop (i + 1) (folder i acc xs.Head) xs.Tail - loop 0 state xs - -// let rec fold2 (folder: 'State -> 'T1 -> 'T2 -> 'State) (state: 'State) (xs: 'T1 list) (ys: 'T2 list) = -// if xs.IsEmpty || ys.IsEmpty then state -// else fold2 folder (folder state xs.Head ys.Head) xs.Tail ys.Tail - -let fold2 (folder: 'State -> 'T1 -> 'T2 -> 'State) (state: 'State) (xs: 'T1 list) (ys: 'T2 list) = +let foldBack (folder: 'T -> 'acc -> 'acc) (xs: 'T list) (state: 'acc) = let mutable acc = state - let mutable xs = xs - let mutable ys = ys - while not xs.IsEmpty && not ys.IsEmpty do - acc <- folder acc xs.Head ys.Head - xs <- xs.Tail - ys <- ys.Tail + xs.IterateBack(fun v -> acc <- folder v acc) acc -let foldBack2 (folder: 'T1 -> 'T2 -> 'State -> 'State) (xs: 'T1 list) (ys: 'T2 list) (state: 'State) = - // fold2 (fun acc x y -> folder x y acc) state (reverse xs) (reverse ys) - Array.foldBack2 folder (toArray xs) (toArray ys) state +let reverse (xs: 'a list) = + xs.Reverse() + +// One of the attempts to optimize but I'm not sure if it's much faster than JS Array.prototype.reverse +// If it is, we should use this as replacement of ResizeArray.Reverse +// https://stackoverflow.com/a/9113136 +let private reverseInPlace (xs: ResizeArray<'a>) = + let mutable left = 0 + let mutable right = 0 + let length = xs.Count + while left < length / 2 do + right <- length - 1 - left; + let temporary = xs.[left] + xs.[left] <- xs.[right] + xs.[right] <- temporary + left <- left + 1 + +let ofResizeArrayInPlace (xs: ResizeArray<'a>) = + reverseInPlace xs + ResizeList<'a>.NewList(xs) + +let toSeq (xs: 'a list): 'a seq = + xs :> System.Collections.Generic.IEnumerable<'a> + +let ofSeq (xs: 'a seq): 'a list = + // Seq.fold (fun acc x -> cons x acc) ResizeList.Empty xs + // |> ofResizeArrayInPlace + let values = ResizeArray(xs) + reverseInPlace values + values |> newList + +let concat (lists: seq<'a list>) = + (ResizeArray(), lists) + ||> Seq.fold (fold (fun acc x -> acc.Add(x); acc)) + |> ofResizeArrayInPlace + +let fold2 f (state: 'acc) (xs: 'a list) (ys: 'b list) = + Seq.fold2 f state xs ys + +let foldBack2 f (xs: 'a list) (ys: 'b list) (state: 'acc) = + Seq.foldBack2 f xs ys state + +let unfold (gen: 'acc -> ('T * 'acc) option) (state: 'acc) = + let rec loop st acc = + match gen st with + | None -> ofResizeArrayInPlace acc + | Some (x, st) -> acc.Add(x); loop st acc + loop state (ResizeArray()) + +let scan f (state: 'acc) (xs: 'a list) = + Seq.scan f state xs |> ofSeq + +let scanBack f (xs: 'a list) (state: 'acc) = + Seq.scanBack f xs state |> ofSeq + +let append (xs: 'a list) (ys: 'a list) = + xs.Append(ys) + +let collect (f: 'a -> 'b list) (xs: 'a list) = + Seq.collect f xs |> ofSeq + +let mapIndexed (f: int -> 'a -> 'b) (xs: 'a list) = + let values = allocate xs.Length + let mutable idx = -1 + let mutable revIdx = values.Count + xs.Iterate(fun v -> + idx <- idx + 1 + revIdx <- revIdx - 1 + values.[revIdx] <- f idx v) + ResizeList<'b>.NewList(values) + +let map (f: 'a -> 'b) (xs: 'a list) = + mapIndexed (fun _i x -> f x) xs + +let indexed (xs: 'a list) = + mapIndexed (fun i x -> (i, x)) xs -let unfold (gen: 'State -> ('T * 'State) option) (state: 'State) = - let rec loop acc (node: 'T list) = - match gen acc with - | None -> node - | Some (x, acc) -> loop acc (node.AppendConsNoTail x) - let root = List.Empty - let node = loop state root - node.SetConsTail List.Empty - root.Tail +let map2 f xs ys = + Seq.map2 f xs ys |> ofSeq -let iterate action xs = - fold (fun () x -> action x) () xs +let mapIndexed2 f xs ys = + Seq.mapi2 f xs ys |> ofSeq -let iterate2 action xs ys = - fold2 (fun () x y -> action x y) () xs ys +let map3 f xs ys zs = + Seq.map3 f xs ys zs |> ofSeq -let iterateIndexed action xs = - fold (fun i x -> action i x; i + 1) 0 xs |> ignore +let mapFold (f: 'S -> 'T -> 'R * 'S) s xs = + let folder (nxs: ResizeArray<_>, fs) x = + let nx, fs = f fs x + nxs.Add(nx) + nxs, fs + let nxs, s = fold folder (ResizeArray(), s) xs + ofResizeArrayInPlace nxs, s -let iterateIndexed2 action xs ys = - fold2 (fun i x y -> action i x y; i + 1) 0 xs ys |> ignore +let mapFoldBack (f: 'T -> 'S -> 'R * 'S) xs s = + mapFold (fun s v -> f v s) s (reverse xs) -let toSeq (xs: 'T list): 'T seq = - xs :> System.Collections.Generic.IEnumerable<'T> +let iterate f (xs: 'a list) = + xs.Iterate f -let ofArrayWithTail (xs: 'T[]) (tail: 'T list) = - let mutable res = tail - for i = xs.Length - 1 downto 0 do - res <- List.Cons(xs.[i], res) - res +let iterate2 f xs ys = + fold2 (fun () x y -> f x y) () xs ys -let ofArray (xs: 'T[]) = - ofArrayWithTail xs List.Empty - -let ofSeq (xs: seq<'T>): 'T list = - match xs with - | :? array<'T> as xs -> ofArray xs - | :? list<'T> as xs -> xs - | _ -> - let root = List.Empty - let mutable node = root - for x in xs do - node <- node.AppendConsNoTail x - node.SetConsTail List.Empty - root.Tail - -let concat (lists: seq<'T list>) = - let root = List.Empty - let mutable node = root - let action xs = node <- fold (fun acc x -> acc.AppendConsNoTail x) node xs - match lists with - | :? array<'T list> as xs -> Array.iter action xs - | :? list<'T list> as xs -> iterate action xs - | _ -> for xs in lists do action xs - node.SetConsTail List.Empty - root.Tail - -let scan (folder: 'State -> 'T -> 'State) (state: 'State) (xs: 'T list) = - let root = List.Empty - let mutable node = root.AppendConsNoTail state - let mutable acc = state - let mutable xs = xs - while not xs.IsEmpty do - acc <- folder acc xs.Head - node <- node.AppendConsNoTail acc - xs <- xs.Tail - node.SetConsTail List.Empty - root.Tail - -let scanBack (folder: 'T -> 'State -> 'State) (xs: 'T list) (state: 'State) = - Array.scanBack folder (toArray xs) state - |> ofArray - -let append (xs: 'T list) (ys: 'T list) = - fold (fun acc x -> List.Cons(x, acc)) ys (reverse xs) - -let collect (mapping: 'T -> 'U list) (xs: 'T list) = - let root = List.Empty - let mutable node = root - let mutable ys = xs - while not ys.IsEmpty do - let mutable zs = mapping ys.Head - while not zs.IsEmpty do - node <- node.AppendConsNoTail zs.Head - zs <- zs.Tail - ys <- ys.Tail - node.SetConsTail List.Empty - root.Tail - -let mapIndexed (mapping: int -> 'T -> 'U) (xs: 'T list) = - let root = List.Empty - let folder i (acc: 'U list) x = acc.AppendConsNoTail (mapping i x) - let node = foldIndexed folder root xs - node.SetConsTail List.Empty - root.Tail - -let map (mapping: 'T -> 'U) (xs: 'T list) = - let root = List.Empty - let folder (acc: 'U list) x = acc.AppendConsNoTail (mapping x) - let node = fold folder root xs - node.SetConsTail List.Empty - root.Tail - -let indexed xs = - mapIndexed (fun i x -> (i, x)) xs +let iterateIndexed f (xs: 'a list) = + let mutable i = -1 + xs.Iterate(fun v -> + i <- i + 1 + f i v) -let map2 (mapping: 'T1 -> 'T2 -> 'U) (xs: 'T1 list) (ys: 'T2 list) = - let root = List.Empty - let folder (acc: 'U list) x y = acc.AppendConsNoTail (mapping x y) - let node = fold2 folder root xs ys - node.SetConsTail List.Empty - root.Tail +let iterateIndexed2 f xs ys = + fold2 (fun i x y -> f i x y; i + 1) 0 xs ys |> ignore -let mapIndexed2 (mapping: int -> 'T1 -> 'T2 -> 'U) (xs: 'T1 list) (ys: 'T2 list) = - let rec loop i (acc: 'U list) (xs: 'T1 list) (ys: 'T2 list) = - if xs.IsEmpty || ys.IsEmpty then acc - else - let node = acc.AppendConsNoTail (mapping i xs.Head ys.Head) - loop (i + 1) node xs.Tail ys.Tail - let root = List.Empty - let node = loop 0 root xs ys - node.SetConsTail List.Empty - root.Tail - -let map3 (mapping: 'T1 -> 'T2 -> 'T3 -> 'U) (xs: 'T1 list) (ys: 'T2 list) (zs: 'T3 list) = - let rec loop (acc: 'U list) (xs: 'T1 list) (ys: 'T2 list) (zs: 'T3 list) = - if xs.IsEmpty || ys.IsEmpty || zs.IsEmpty then acc - else - let node = acc.AppendConsNoTail (mapping xs.Head ys.Head zs.Head) - loop node xs.Tail ys.Tail zs.Tail - let root = List.Empty - let node = loop root xs ys zs - node.SetConsTail List.Empty - root.Tail - -let mapFold (mapping: 'State -> 'T -> 'Result * 'State) (state: 'State) (xs: 'T list) = - let folder (node: 'Result list, st) x = - let r, st = mapping st x - node.AppendConsNoTail r, st - let root = List.Empty - let node, state = fold folder (root, state) xs - node.SetConsTail List.Empty - root.Tail, state - -let mapFoldBack (mapping: 'T -> 'State -> 'Result * 'State) (xs: 'T list) (state: 'State) = - mapFold (fun acc x -> mapping x acc) state (reverse xs) +let ofArray (xs: 'T[]) = + // let mutable res = ResizeList.Empty + // for i = xs.Length - 1 downto 0 do + // res <- cons xs.[i] res + // res + let values = ResizeArray(xs.Length) + let lastIndex = xs.Length - 1 + for i = lastIndex downto 0 do + values.[lastIndex - i] <- xs.[i] + values |> newList + +let tryPickIndexed (f: int -> 'a -> 'b option) (xs: 'a list) = + let mutable result = None + let mutable i = -1 + xs.DoWhile(fun v -> + i <- i + 1 + match f i v with + | Some r -> result <- Some r; false + | None -> true) + result + +let tryPickIndexedBack (f: int -> 'a -> 'b option) (xs: 'a list) = + let mutable result = None + let mutable i = xs.Length + xs.IterateBack(fun v -> + if Option.isNone result then + i <- i - 1 + result <- f i v) + result let tryPick f xs = let rec loop (xs: 'T list) = @@ -391,14 +423,21 @@ let tryPick f xs = let pick f xs = match tryPick f xs with + | None -> indexNotFound() | Some x -> x | None -> indexNotFound() -let tryFind f xs = - tryPick (fun x -> if f x then Some x else None) xs +let tryFindIndexedBack f xs = + tryPickIndexedBack (fun i x -> if f i x then Some x else None) xs + +let findIndexed f xs = + match tryFindIndexed f xs with + | None -> indexNotFound() + | Some x -> x -let find f xs = - match tryFind f xs with +let findIndexedBack f xs = + match tryFindIndexedBack f xs with + | None -> indexNotFound() | Some x -> x | None -> indexNotFound() @@ -406,92 +445,89 @@ let tryFindBack f xs = xs |> toArray |> Array.tryFindBack f let findBack f xs = - match tryFindBack f xs with - | Some x -> x - | None -> indexNotFound() + findIndexedBack (fun _ x -> f x) xs + +let tryFind f xs = + tryPickIndexed (fun _ x -> if f x then Some x else None) xs + +let tryFindBack f xs = + tryPickIndexedBack (fun _ x -> if f x then Some x else None) xs let tryFindIndex f xs: int option = - let rec loop i (xs: 'T list) = - if xs.IsEmpty then None - else - if f xs.Head - then Some i - else loop (i + 1) xs.Tail - loop 0 xs + tryPickIndexed (fun i x -> if f x then Some i else None) xs + +let tryFindIndexBack f xs: int option = + tryPickIndexedBack (fun i x -> if f x then Some i else None) xs let findIndex f xs: int = match tryFindIndex f xs with + | None -> indexNotFound() | Some x -> x | None -> indexNotFound() -let tryFindIndexBack f xs: int option = - xs |> toArray |> Array.tryFindIndexBack f - let findIndexBack f xs: int = match tryFindIndexBack f xs with - | Some x -> x | None -> indexNotFound() + | Some x -> x -let tryItem n (xs: 'T list) = - let rec loop i (xs: 'T list) = - if xs.IsEmpty then None - else - if i = n then Some xs.Head - else loop (i + 1) xs.Tail - loop 0 xs - -let item n (xs: 'T list) = xs.Item(n) - -let filter f (xs: 'T list) = - let root = List.Empty - let folder (acc: 'T list) x = - if f x then acc.AppendConsNoTail x else acc - let node = fold folder root xs - node.SetConsTail List.Empty - root.Tail - -let partition f (xs: 'T list) = - let root1, root2 = List.Empty, List.Empty - let folder (lacc: 'T list, racc: 'T list) x = +let tryItem index (xs: 'a list) = + if index >= 0 && index < xs.Length + then Some xs.[index] + else None + +let item index (xs: 'a list) = + match tryItem index xs with + | Some x -> x + | None -> invalidArg "index" SR.indexOutOfBounds + +let filter f xs = + (ResizeArray(), xs) + ||> fold (fun acc x -> if f x - then lacc.AppendConsNoTail x, racc - else lacc, racc.AppendConsNoTail x - let node1, node2 = fold folder (root1, root2) xs - node1.SetConsTail List.Empty - node2.SetConsTail List.Empty - root1.Tail, root2.Tail - -let choose f (xs: 'T list) = - let root = List.Empty - let folder (acc: 'T list) x = + then acc.Add(x); acc + else acc) + |> ofResizeArrayInPlace + +// TODO: Optimize this +let partition f xs = + fold (fun (lacc, racc) x -> + if f x then cons x lacc, racc + else lacc, cons x racc) (ResizeList.Empty, ResizeList.Empty) (reverse xs) + +let choose f xs = + (ResizeArray(), xs) + ||> fold (fun acc x -> match f x with - | Some y -> acc.AppendConsNoTail y - | None -> acc - let node = fold folder root xs - node.SetConsTail List.Empty - root.Tail + | Some y -> acc.Add(y); acc + | None -> acc) + |> ofResizeArrayInPlace let contains (value: 'T) (xs: 'T list) ([] eq: System.Collections.Generic.IEqualityComparer<'T>) = - tryFindIndex (fun v -> eq.Equals (value, v)) xs - |> Option.isSome + tryFindIndex (fun v -> eq.Equals (value, v)) xs |> Option.isSome -let initialize n (f: int -> 'T) = - let root = List.Empty - let mutable node = root - for i = 0 to n - 1 do - node <- node.AppendConsNoTail (f i) - node.SetConsTail List.Empty - root.Tail +let except (itemsToExclude: seq<'t>) (xs: 't list) ([] eq: System.Collections.Generic.IEqualityComparer<'t>): 't list = + if isEmpty xs then xs + else + let cached = System.Collections.Generic.HashSet(itemsToExclude, eq) + xs |> filter cached.Add + +let initialize n f = + let mutable j = 0 + let values = allocate n + for i = n - 1 downto 0 do + values.[i] <- f j + j <- j + 1 + values |> newList let replicate n x = initialize n (fun _ -> x) let reduce f (xs: 'T list) = - if xs.IsEmpty then invalidOp SR.inputListWasEmpty + if isEmpty xs then invalidArg "list" SR.inputListWasEmpty else fold f (head xs) (tail xs) -let reduceBack f (xs: 'T list) = - if xs.IsEmpty then invalidOp SR.inputListWasEmpty +let reduceBack f (xs: 't list) = + if isEmpty xs then invalidArg "list" SR.inputListWasEmpty else foldBack f (tail xs) (head xs) let forAll f xs = @@ -503,17 +539,18 @@ let forAll2 f xs ys = let exists f xs = tryFindIndex f xs |> Option.isSome -let rec exists2 (f: 'T1 -> 'T2 -> bool) (xs: 'T1 list) (ys: 'T2 list) = - match xs.IsEmpty, ys.IsEmpty with - | true, true -> false - | false, false -> f xs.Head ys.Head || exists2 f xs.Tail ys.Tail +let rec exists2 f xs ys = + match length xs, length ys with + | 0, 0 -> false + | x, y when x = y -> f (head xs) (head ys) || exists2 f (tail xs) (tail ys) | _ -> invalidArg "list2" SR.listsHadDifferentLengths +// TODO: Optimize this let unzip xs = - foldBack (fun (x, y) (lacc, racc) -> List.Cons(x, lacc), List.Cons(y, racc)) xs (List.Empty, List.Empty) + foldBack (fun (x, y) (lacc, racc) -> cons x lacc, cons y racc) xs (ResizeList.Empty, ResizeList.Empty) let unzip3 xs = - foldBack (fun (x, y, z) (lacc, macc, racc) -> List.Cons(x, lacc), List.Cons(y, macc), List.Cons(z, racc)) xs (List.Empty, List.Empty, List.Empty) + foldBack (fun (x, y, z) (lacc, macc, racc) -> cons x lacc, cons y macc, cons z racc) xs (ResizeList.Empty, ResizeList.Empty, ResizeList.Empty) let zip xs ys = map2 (fun x y -> x, y) xs ys @@ -521,21 +558,21 @@ let zip xs ys = let zip3 xs ys zs = map3 (fun x y z -> x, y, z) xs ys zs -let sortWith (comparer: 'T -> 'T -> int) (xs: 'T list) = - let arr = toArray xs - Array.sortInPlaceWith comparer arr // Note: In JS this sort is stable - arr |> ofArray +let sortWith (comparison: 'T -> 'T -> int) (xs: 'T list): 'T list = + let values = ResizeArray(xs) + values.Sort(System.Comparison<_>(comparison)) // should be a stable sort in JS + values |> ofResizeArrayInPlace -let sort (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'T>) = +let sort (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'T>): 'T list = sortWith (fun x y -> comparer.Compare(x, y)) xs -let sortBy (projection: 'T -> 'U) (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'U>) = +let sortBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a list = sortWith (fun x y -> comparer.Compare(projection x, projection y)) xs -let sortDescending (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'T>) = +let sortDescending (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'T>): 'T list = sortWith (fun x y -> comparer.Compare(x, y) * -1) xs -let sortByDescending (projection: 'T -> 'U) (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'U>) = +let sortByDescending (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a list = sortWith (fun x y -> comparer.Compare(projection x, projection y) * -1) xs let sum (xs: 'T list) ([] adder: IGenericAdder<'T>): 'T = @@ -544,153 +581,141 @@ let sum (xs: 'T list) ([] adder: IGenericAdder<'T>): 'T = let sumBy (f: 'T -> 'U) (xs: 'T list) ([] adder: IGenericAdder<'U>): 'U = fold (fun acc x -> adder.Add(acc, f x)) (adder.GetZero()) xs -let maxBy (projection: 'T -> 'U) xs ([] comparer: System.Collections.Generic.IComparer<'U>): 'T = +let maxBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a = reduce (fun x y -> if comparer.Compare(projection y, projection x) > 0 then y else x) xs -let max xs ([] comparer: System.Collections.Generic.IComparer<'T>): 'T = - reduce (fun x y -> if comparer.Compare(y, x) > 0 then y else x) xs +let max (li:'a list) ([] comparer: System.Collections.Generic.IComparer<'a>): 'a = + reduce (fun x y -> if comparer.Compare(y, x) > 0 then y else x) li -let minBy (projection: 'T -> 'U) xs ([] comparer: System.Collections.Generic.IComparer<'U>): 'T = +let minBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a = reduce (fun x y -> if comparer.Compare(projection y, projection x) > 0 then x else y) xs -let min (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'T>): 'T = +let min (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'a>): 'a = reduce (fun x y -> if comparer.Compare(y, x) > 0 then x else y) xs let average (xs: 'T list) ([] averager: IGenericAverager<'T>): 'T = - let mutable count = 0 - let folder acc x = count <- count + 1; averager.Add(acc, x) - let total = fold folder (averager.GetZero()) xs - averager.DivideByInt(total, count) + let total = fold (fun acc x -> averager.Add(acc, x)) (averager.GetZero()) xs + averager.DivideByInt(total, length xs) -let averageBy (f: 'T -> 'U) (xs: 'T list) ([] averager: IGenericAverager<'U>): 'U = - let mutable count = 0 - let inline folder acc x = count <- count + 1; averager.Add(acc, f x) - let total = fold folder (averager.GetZero()) xs - averager.DivideByInt(total, count) +let averageBy (f: 'T -> 'T2) (xs: 'T list) ([] averager: IGenericAverager<'T2>): 'T2 = + let total = fold (fun acc x -> averager.Add(acc, f x)) (averager.GetZero()) xs + averager.DivideByInt(total, length xs) let permute f (xs: 'T list) = - toArray xs - |> Array.permute f - |> ofArray + Seq.permute f xs |> ofSeq let chunkBySize (chunkSize: int) (xs: 'T list): 'T list list = - toArray xs - |> Array.chunkBySize chunkSize - |> Array.map ofArray - |> ofArray - -let allPairs (xs: 'T1 list) (ys: 'T2 list): ('T1 * 'T2) list = - let root = List.Empty - let mutable node = root - iterate (fun x -> - iterate (fun y -> - node <- node.AppendConsNoTail (x, y) - ) ys) xs - node.SetConsTail List.Empty - root.Tail - -let rec skip count (xs: 'T list) = - if count <= 0 then xs - elif xs.IsEmpty then invalidArg "list" SR.notEnoughElements - else skip (count - 1) xs.Tail - -let rec skipWhile predicate (xs: 'T list) = - if xs.IsEmpty then xs - elif not (predicate xs.Head) then xs - else skipWhile predicate xs.Tail - -let take count (xs: 'T list) = - if count < 0 then invalidArg "count" SR.inputMustBeNonNegative - let rec loop i (acc: 'T list) (xs: 'T list) = - if i <= 0 then acc - elif xs.IsEmpty then invalidArg "list" SR.notEnoughElements - else loop (i - 1) (acc.AppendConsNoTail xs.Head) xs.Tail - let root = List.Empty - let node = loop count root xs - node.SetConsTail List.Empty - root.Tail + Seq.chunkBySize chunkSize xs + |> Seq.map ofArray + |> ofSeq + +let skip count (xs: 'T list) = + Seq.skip count xs |> ofSeq + +let skipWhile predicate (xs: 'T list) = + Seq.skipWhile predicate xs |> ofSeq + +let take count xs = + Seq.take count xs |> ofSeq let takeWhile predicate (xs: 'T list) = - let rec loop (acc: 'T list) (xs: 'T list) = - if xs.IsEmpty then acc - elif not (predicate xs.Head) then acc - else loop (acc.AppendConsNoTail xs.Head) xs.Tail - let root = List.Empty - let node = loop root xs - node.SetConsTail List.Empty - root.Tail - -let truncate count (xs: 'T list) = - let rec loop i (acc: 'T list) (xs: 'T list) = - if i <= 0 then acc - elif xs.IsEmpty then acc - else loop (i - 1) (acc.AppendConsNoTail xs.Head) xs.Tail - let root = List.Empty - let node = loop count root xs - node.SetConsTail List.Empty - root.Tail + Seq.takeWhile predicate xs |> ofSeq + +let truncate count xs = + Seq.truncate count xs |> ofSeq let getSlice (startIndex: int option) (endIndex: int option) (xs: 'T list) = - let len = length xs let startIndex = defaultArg startIndex 0 - let endIndex = defaultArg endIndex (len - 1) - if startIndex < 0 then invalidArg "startIndex" SR.indexOutOfBounds - elif endIndex >= len then invalidArg "endIndex" SR.indexOutOfBounds - elif endIndex < startIndex then List.Empty - else xs |> skip startIndex |> take (endIndex - startIndex + 1) + let endIndex = defaultArg endIndex (xs.Length - 1) + if startIndex > endIndex then + ResizeList.Empty + else + let startIndex = if startIndex < 0 then 0 else startIndex + let endIndex = if endIndex >= xs.Length then xs.Length - 1 else endIndex + // take (endIndex - startIndex + 1) (skip startIndex xs) + let values = allocate (endIndex - startIndex + 1) + let mutable j = 0 + for i = endIndex downto startIndex do + values.[j] <- xs.[i] + j <- j + 1 + values |> newList let splitAt index (xs: 'T list) = if index < 0 then invalidArg "index" SR.inputMustBeNonNegative if index > xs.Length then invalidArg "index" SR.notEnoughElements take index xs, skip index xs -let exactlyOne (xs: 'T list) = - if xs.IsEmpty - then invalidArg "list" SR.inputSequenceEmpty - else - if xs.Tail.IsEmpty then xs.Head - else invalidArg "list" SR.inputSequenceTooLong +let distinctBy (projection: 'T -> 'Key) (xs: 'T list) ([] eq: System.Collections.Generic.IEqualityComparer<'Key>) = + let hashSet = System.Collections.Generic.HashSet<'Key>(eq) + xs |> filter (projection >> hashSet.Add) -let tryExactlyOne (xs: 'T list) = - if not (xs.IsEmpty) && xs.Tail.IsEmpty - then Some (xs.Head) - else None +let distinct (xs: 'T list) ([] eq: System.Collections.Generic.IEqualityComparer<'T>) = + distinctBy id xs eq + +let exactlyOne (xs: 'T list) = + match xs.Length with + | 1 -> head xs + | 0 -> invalidArg "list" SR.inputSequenceEmpty + | _ -> invalidArg "list" SR.inputSequenceTooLong + +let groupBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: System.Collections.Generic.IEqualityComparer<'Key>): ('Key * 'T list) list = + let dict = System.Collections.Generic.Dictionary<'Key, ResizeArray<'T>>(eq) + let keys = ResizeArray<'Key>() + for v in xs do + let key = projection v + match dict.TryGetValue(key) with + | true, prev -> + prev.Add(v) + | false, _ -> + dict.Add(key, ResizeArray [|v|]) + keys.Add(key) + let result = allocate keys.Count + let mutable revIdx = keys.Count + for i = 0 to keys.Count - 1 do + revIdx <- revIdx - 1 + let key = keys.[i] + result.[revIdx] <- (key, ofResizeArrayInPlace dict.[key]) + newList result + +let countBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: System.Collections.Generic.IEqualityComparer<'Key>) = + let dict = System.Collections.Generic.Dictionary<'Key, int>(eq) + let mutable keys = ResizeList.Empty + xs |> iterate (fun v -> + let key = projection v + match dict.TryGetValue(key) with + | true, prev -> + dict.[key] <- prev + 1 + | false, _ -> + dict.[key] <- 1 + keys <- cons key keys ) + let mutable result = ResizeList.Empty + keys |> iterate (fun key -> result <- cons (key, dict.[key]) result) + result let where predicate (xs: 'T list) = filter predicate xs let pairwise (xs: 'T list) = - toArray xs - |> Array.pairwise - |> ofArray + Seq.pairwise xs |> ofSeq let windowed (windowSize: int) (xs: 'T list): 'T list list = - toArray xs - |> Array.windowed windowSize - |> Array.map ofArray - |> ofArray + Seq.windowed windowSize xs + |> Seq.map ofArray + |> ofSeq let splitInto (chunks: int) (xs: 'T list): 'T list list = - toArray xs - |> Array.splitInto chunks - |> Array.map ofArray - |> ofArray + Seq.splitInto chunks xs + |> Seq.map ofArray + |> ofSeq let transpose (lists: seq<'T list>): 'T list list = - lists - |> Array.ofSeq - |> Array.map toArray - |> Array.transpose - |> Array.map ofArray - |> ofArray + Seq.transpose lists + |> Seq.map ofSeq + |> ofSeq +// let rev = reverse // let init = initialize // let iter = iterate // let iter2 = iterate2 // let iteri = iterateIndexed // let iteri2 = iterateIndexed2 -// let forall = forAll -// let forall2 = forAll2 -// let mapi = mapIndexed -// let mapi2 = mapIndexed2 -// let rev = reverse