diff --git a/src/FSharpx.Collections/DList.fs b/src/FSharpx.Collections/DList.fs index 42f73e57..7db24add 100644 --- a/src/FSharpx.Collections/DList.fs +++ b/src/FSharpx.Collections/DList.fs @@ -261,3 +261,21 @@ module DList = match l.Length with | 0 -> DList(0, Nil) | _ -> DList(l.Length - 1, dlistData) + + let map (f: 'T -> 'U) (l: DList<'T>) : DList<'U> = + foldBack (fun x acc -> cons (f x) acc) l empty + + let filter (predicate: 'T -> bool) (l: DList<'T>) : DList<'T> = + foldBack (fun x acc -> if predicate x then cons x acc else acc) l empty + + let inline iter (action: 'T -> unit) (l: DList<'T>) : unit = + Seq.iter action l + + let inline exists (predicate: 'T -> bool) (l: DList<'T>) : bool = + Seq.exists predicate l + + let inline forall (predicate: 'T -> bool) (l: DList<'T>) : bool = + Seq.forall predicate l + + let inline toArray(l: DList<'T>) : 'T[] = + Seq.toArray l diff --git a/src/FSharpx.Collections/DList.fsi b/src/FSharpx.Collections/DList.fsi index 40160d61..894f06c1 100644 --- a/src/FSharpx.Collections/DList.fsi +++ b/src/FSharpx.Collections/DList.fsi @@ -12,45 +12,45 @@ type DList<'T> = interface System.Collections.Generic.IReadOnlyCollection<'T> ///O(1). Returns the count of elememts. - member Length : int + member Length: int ///O(1). Returns a new DList with the element added to the front. - member Cons : 'T -> DList<'T> + member Cons: 'T -> DList<'T> ///O(log n). Returns the first element. - member Head : 'T + member Head: 'T ///O(log n). Returns option first element - member TryHead : 'T option + member TryHead: 'T option ///O(1). Returns true if the DList has no elements. - member IsEmpty : bool + member IsEmpty: bool ///O(1). Returns a new DList with the element added to the end. - member Conj : 'T -> DList<'T> + member Conj: 'T -> DList<'T> ///O(log n). Returns a new DList of the elements trailing the first element. - member Tail : DList<'T> + member Tail: DList<'T> ///O(log n). Returns option DList of the elements trailing the first element. - member TryTail : DList<'T> option + member TryTail: DList<'T> option ///O(log n). Returns the first element and tail. - member Uncons : 'T * DList<'T> + member Uncons: 'T * DList<'T> ///O(log n). Returns option first element and tail. - member TryUncons : ('T * DList<'T>) option + member TryUncons: ('T * DList<'T>) option [] module DList = //pattern discriminators (active pattern) - val (|Cons|Nil|) : DList<'T> -> Choice<('T * DList<'T>),unit> + val (|Cons|Nil|): DList<'T> -> Choice<('T * DList<'T>), unit> ///O(1). Returns a new DList of two lists. - val append : DList<'T> -> DList<'T> -> DList<'T> + val append: DList<'T> -> DList<'T> -> DList<'T> ///O(1). Returns a new DList with the element added to the beginning. - val cons : 'T -> DList<'T> -> DList<'T> + val cons: 'T -> DList<'T> -> DList<'T> ///O(1). Returns DList of no elements. [] @@ -58,48 +58,66 @@ module DList = ///O(n). Fold walks the DList using constant stack space. Implementation is from Norman Ramsey. /// See http://stackoverflow.com/questions/5324623/functional-o1-append-and-on-iteration-from-first-element-list-data-structure/5334068#5334068 - val foldBack : ('T -> 'State -> 'State) -> DList<'T> -> 'State -> 'State + val foldBack: ('T -> 'State -> 'State) -> DList<'T> -> 'State -> 'State - val fold : ('State -> 'T -> 'State) -> 'State -> DList<'T> -> 'State + val fold: ('State -> 'T -> 'State) -> 'State -> DList<'T> -> 'State ///O(log n). Returns the first element. - val inline head : DList<'T> -> 'T + val inline head: DList<'T> -> 'T ///O(log n). Returns option first element. - val inline tryHead : DList<'T> -> 'T option + val inline tryHead: DList<'T> -> 'T option ///O(1). Returns true if the DList has no elements. - val inline isEmpty : DList<'T> -> bool + val inline isEmpty: DList<'T> -> bool ///O(1). Returns the count of elememts. - val inline length : DList<'T> -> int + val inline length: DList<'T> -> int ///O(1). Returns DList of one elements. - val singleton : 'T -> DList<'T> + val singleton: 'T -> DList<'T> ///O(1). Returns a new DList with the element added to the end. - val inline conj : 'T -> DList<'T> -> DList<'T> + val inline conj: 'T -> DList<'T> -> DList<'T> ///O(log n). Returns a new DList of the elements trailing the first element. - val inline tail : DList<'T> -> DList<'T> + val inline tail: DList<'T> -> DList<'T> ///O(log n). Returns option DList of the elements trailing the first element. - val inline tryTail : DList<'T> -> DList<'T> option + val inline tryTail: DList<'T> -> DList<'T> option ///O(log n). Returns the first element and tail. - val inline uncons : DList<'T> -> 'T * DList<'T> + val inline uncons: DList<'T> -> 'T * DList<'T> ///O(log n). Returns option first element and tail. - val inline tryUncons : DList<'T> -> ('T * DList<'T>) option + val inline tryUncons: DList<'T> -> ('T * DList<'T>) option ///O(n). Returns a DList of the seq. - val ofSeq : seq<'T> -> DList<'T> + val ofSeq: seq<'T> -> DList<'T> ///O(n). Returns a list of the DList elements. - val inline toList : DList<'T> -> list<'T> + val inline toList: DList<'T> -> list<'T> ///O(n). Returns a seq of the DList elements. - val inline toSeq : DList<'T> -> seq<'T> + val inline toSeq: DList<'T> -> seq<'T> ///O(n). Returns a pairwise DList of elements. - val pairwise : DList<'T> -> DList<'T*'T> + val pairwise: DList<'T> -> DList<'T * 'T> + + ///O(n). Returns a new DList whose elements are the results of applying the given function to each element. + val map: ('T -> 'U) -> DList<'T> -> DList<'U> + + ///O(n). Returns a new DList containing only the elements for which the given predicate returns true. + val filter: ('T -> bool) -> DList<'T> -> DList<'T> + + ///O(n). Applies the given function to each element of the DList. + val inline iter: ('T -> unit) -> DList<'T> -> unit + + ///O(n). Returns true if the given predicate returns true for at least one element. + val inline exists: ('T -> bool) -> DList<'T> -> bool + + ///O(n). Returns true if the given predicate returns true for all elements. + val inline forall: ('T -> bool) -> DList<'T> -> bool + + ///O(n). Returns an array of the DList elements. + val inline toArray: DList<'T> -> 'T[] diff --git a/tests/FSharpx.Collections.Tests/DListTest.fs b/tests/FSharpx.Collections.Tests/DListTest.fs index 047e00fa..cfa6fdc0 100644 --- a/tests/FSharpx.Collections.Tests/DListTest.fs +++ b/tests/FSharpx.Collections.Tests/DListTest.fs @@ -164,7 +164,76 @@ module DListTests = let testDList = DList.ofSeq testList let paired = DList.pairwise testDList Expect.sequenceEqual "pairwise does not match List.pairwise" expectedPairs paired - } ] + } + + test "DList.map transforms elements" { + let q = DList.ofSeq [ 1; 2; 3; 4; 5 ] + let mapped = DList.map (fun x -> x * 2) q + Expect.equal "map values" [ 2; 4; 6; 8; 10 ] (DList.toList mapped) + Expect.equal "map length" 5 (DList.length mapped) + } + + test "DList.map on empty returns empty" { + let mapped = DList.map (fun x -> x * 2) DList.empty + Expect.isTrue "map empty" (DList.isEmpty mapped) + } + + test "DList.filter keeps matching elements" { + let q = DList.ofSeq [ 1; 2; 3; 4; 5; 6 ] + let evens = DList.filter (fun x -> x % 2 = 0) q + Expect.equal "filter values" [ 2; 4; 6 ] (DList.toList evens) + Expect.equal "filter length" 3 (DList.length evens) + } + + test "DList.filter with all matching returns same elements" { + let q = DList.ofSeq [ 1; 2; 3 ] + let result = DList.filter (fun _ -> true) q + Expect.equal "filter all" [ 1; 2; 3 ] (DList.toList result) + } + + test "DList.filter with none matching returns empty" { + let q = DList.ofSeq [ 1; 2; 3 ] + let result = DList.filter (fun _ -> false) q + Expect.isTrue "filter none" (DList.isEmpty result) + } + + test "DList.iter visits all elements in order" { + let q = DList.ofSeq [ 1; 2; 3 ] + let visited = System.Collections.Generic.List() + DList.iter visited.Add q + Expect.equal "iter order" [ 1; 2; 3 ] (List.ofSeq visited) + } + + test "DList.exists returns true when element matches" { + let q = DList.ofSeq [ 1; 2; 3 ] + Expect.isTrue "exists found" (DList.exists (fun x -> x = 2) q) + } + + test "DList.exists returns false when no element matches" { + let q = DList.ofSeq [ 1; 2; 3 ] + Expect.isFalse "exists not found" (DList.exists (fun x -> x = 99) q) + } + + test "DList.exists on empty returns false" { Expect.isFalse "exists empty" (DList.exists (fun _ -> true) DList.empty) } + + test "DList.forall returns true when all elements match" { + let q = DList.ofSeq [ 2; 4; 6 ] + Expect.isTrue "forall true" (DList.forall (fun x -> x % 2 = 0) q) + } + + test "DList.forall returns false when any element does not match" { + let q = DList.ofSeq [ 2; 3; 6 ] + Expect.isFalse "forall false" (DList.forall (fun x -> x % 2 = 0) q) + } + + test "DList.forall on empty returns true" { Expect.isTrue "forall empty" (DList.forall (fun _ -> false) DList.empty) } + + test "DList.toArray returns elements in order" { + let q = DList.ofSeq [ 1; 2; 3; 4; 5 ] + Expect.equal "toArray" [| 1; 2; 3; 4; 5 |] (DList.toArray q) + } + + test "DList.toArray on empty returns empty array" { Expect.equal "toArray empty" [||] (DList.toArray DList.empty) } ] [] let propertyTestDList = @@ -383,4 +452,46 @@ module DListTests = config10k "string DList builds and serializes" (Prop.forAll(Arb.fromGen DListStringGen) - <| fun (q, l) -> q |> Seq.toList = l) ] + <| fun (q, l) -> q |> Seq.toList = l) + + testPropertyWithConfig + config10k + "DList.map matches List.map 0" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> DList.map (fun x -> x * 2) q |> DList.toList = List.map (fun x -> x * 2) l) + + testPropertyWithConfig + config10k + "DList.map matches List.map 1" + (Prop.forAll(Arb.fromGen intGensStart1.[1]) + <| fun (q, l) -> DList.map (fun x -> x * 2) q |> DList.toList = List.map (fun x -> x * 2) l) + + testPropertyWithConfig + config10k + "DList.filter matches List.filter 0" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> DList.filter (fun x -> x % 2 = 0) q |> DList.toList = List.filter (fun x -> x % 2 = 0) l) + + testPropertyWithConfig + config10k + "DList.filter matches List.filter 1" + (Prop.forAll(Arb.fromGen intGensStart1.[1]) + <| fun (q, l) -> DList.filter (fun x -> x % 2 = 0) q |> DList.toList = List.filter (fun x -> x % 2 = 0) l) + + testPropertyWithConfig + config10k + "DList.exists matches List.exists 0" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> DList.exists (fun x -> x % 3 = 0) q = List.exists (fun x -> x % 3 = 0) l) + + testPropertyWithConfig + config10k + "DList.forall matches List.forall 0" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> DList.forall (fun x -> x >= 0) q = List.forall (fun x -> x >= 0) l) + + testPropertyWithConfig + config10k + "DList.toArray matches Seq.toArray 0" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> DList.toArray q = Array.ofList l) ]