Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/FSharpx.Collections/DList.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
76 changes: 47 additions & 29 deletions src/FSharpx.Collections/DList.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -12,94 +12,112 @@ 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

[<RequireQualifiedAccess>]
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.
[<GeneralizableValue>]
val empty<'T> : DList<'T>

///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[]
115 changes: 113 additions & 2 deletions tests/FSharpx.Collections.Tests/DListTest.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>
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<int>()
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<int>) }

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<int>) }

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<int>) } ]

[<Tests>]
let propertyTestDList =
Expand Down Expand Up @@ -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) ]
Loading