-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add AA Tree and tests #203
base: master
Are you sure you want to change the base?
Changes from 1 commit
434122d
7990983
e7e1732
50d31b6
9053d20
8ccd82f
dda2bd0
cb72994
56bac48
8d1a93c
171dd41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
namespace rec FSharpx.Collections.Experimental | ||
|
||
open System.Collections | ||
open FSharpx.Collections | ||
open System.Collections.Generic | ||
|
||
(* Implementation guided by following paper: https://arxiv.org/pdf/1412.4882.pdf *) | ||
|
||
/// A balanced binary tree similar to a red-black tree which may have less predictable performance. | ||
type AaTree<'T when 'T: comparison> = | ||
| E | ||
| T of int * AaTree<'T> * 'T * AaTree<'T> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using names for the T case data as in the Shape example here - https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/discriminated-unions . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea. Would be helpful when editing the tree itself to specify the different data types mean. (Added.) |
||
|
||
member x.ToList() = | ||
AaTree.toList x | ||
|
||
interface IEnumerable<'T> with | ||
member x.GetEnumerator() = | ||
(x.ToList() :> _ seq).GetEnumerator() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
interface System.Collections.IEnumerable with | ||
member x.GetEnumerator() = | ||
(x :> _ seq).GetEnumerator() | ||
|
||
[<RequireQualifiedAccess>] | ||
module AaTree = | ||
/// O(1): Returns a boolean if tree is empty. | ||
let isEmpty = function | ||
| E -> true | ||
| _ -> false | ||
|
||
let private sngl = function | ||
| E -> false | ||
| T(_, _, _, E) -> true | ||
| T(lvx, _, _, T(lvy, _, _, _)) -> lvx > lvy | ||
|
||
/// O(1): Returns an empty AaTree. | ||
let empty = E | ||
|
||
let private lvl = function | ||
| E -> 0 | ||
| T(lvt, _, _, _) -> lvt | ||
|
||
let private nlvl = function | ||
| T(lvt, _, _, _) as t -> | ||
if sngl t | ||
then (lvt - 1) | ||
else lvt | ||
| _ -> failwith "unexpected nlvl case" | ||
|
||
let private skew = function | ||
| T(lvx, T(lvy, a, ky, b), kx, c) when lvx = lvy | ||
-> T(lvx, a, ky, T(lvx, b, kx, c)) | ||
| t -> t | ||
|
||
let private split = function | ||
| T(lvx, a, kx, T(lvy, b, ky, T(lvz, c, kz, d))) | ||
when lvx = lvy && lvy = lvz | ||
-> T(lvx + 1, T(lvx, a, kx, b), ky, T(lvx, c, kz, d)) | ||
| t -> t | ||
|
||
/// O(log n): Returns a new AaTree with the parameter inserted. | ||
let rec insert item = function | ||
| E -> T(1, E, item, E) | ||
| T(h, l, v, r) as node -> | ||
if item < v | ||
then split <| (skew <| T(h, insert item l, v, r)) | ||
elif item > v | ||
then split <| (skew <| T(h, l, v, insert item r)) | ||
else node | ||
|
||
let private adjust = function | ||
| T(lvt, lt, kt, rt) as t when lvl lt >= lvt - 1 && lvl rt >= (lvt - 1) | ||
-> t | ||
| T(lvt, lt, kt, rt) when lvl rt < lvt - 1 && sngl lt-> | ||
skew <| T(lvt - 1, lt, kt, rt) | ||
| T(lvt, T(lv1, a, kl, T(lvb, lb, kb, rb)), kt, rt) when lvl rt < lvt - 1 | ||
-> T(lvb + 1, T(lv1, a, kl, lb), kb, T(lvt - 1, rb, kt, rt)) | ||
| T(lvt, lt, kt, rt) when lvl rt < lvt | ||
-> split <| T(lvt - 1, lt, kt, rt) | ||
| T(lvt, lt, kt, T(lvr, T(lva, c, ka, d), kr, b)) -> | ||
let a = T(lva, c, ka, d) | ||
T(lva + 1, T(lvt - 1, lt, kt, c), ka, (split (T(nlvl a, d, kr, b)))) | ||
| _ -> failwith "unexpected adjust case" | ||
|
||
let rec private dellrg = function | ||
| T(_, l, v, E) -> (l, v) | ||
| T(h, l, v, r) -> | ||
let (newLeft, newVal) = dellrg l | ||
T(h, newLeft, v, r), newVal | ||
| _ -> failwith "unexpected dellrg case" | ||
|
||
/// O(log n): Returns an AaTree with the parameter removed. | ||
let rec delete item = function | ||
| E -> E | ||
| T(_, E, v, rt) when item = v -> rt | ||
| T(_, lt, v, E) when item = v -> lt | ||
| T(h, l, v, r) as node -> | ||
if item < v | ||
then adjust <| T(h, delete item l, v, r) | ||
elif item > v | ||
then T(h, l, v, delete item r) | ||
else | ||
let (newLeft, newVal) = dellrg l | ||
T(h, newLeft, newVal, r) | ||
|
||
/// O(log n): Returns true if the given item exists in the tree. | ||
let rec exists item = function | ||
| E -> false | ||
| T(_, l, v, r) -> | ||
if v = item then true | ||
elif item < v then exists item l | ||
else exists item r | ||
|
||
/// O(log n): Returns true if the given item does not exist in the tree. | ||
let rec notExists item tree = | ||
not <| exists item tree | ||
|
||
/// O(log n): Returns Some item if it was found in the tree; else, returns None. | ||
let rec tryFind item = function | ||
| E -> None | ||
| T(_, l, v, r) -> | ||
if v = item then Some v | ||
elif item < v then tryFind item l | ||
else tryFind item r | ||
|
||
/// O(log n): Returns an item if it was found in the tree; else, throws error. | ||
let rec find item tree = | ||
match tryFind item tree with | ||
| None -> failwith <| sprintf "Item %A was not found in the tree." item | ||
| Some x -> x | ||
|
||
let rec private foldOpt (f: OptimizedClosures.FSharpFunc<_,_,_>) x t = | ||
match t with | ||
| E -> x | ||
| T(_, l, v, r) -> | ||
let x = foldOpt f x l | ||
let x = f.Invoke(x,v) | ||
foldOpt f x r | ||
|
||
/// Executes a function on each element in order (for example: 1, 2, 3 or a, b, c). | ||
let fold f x t = foldOpt (OptimizedClosures.FSharpFunc<_,_,_>.Adapt(f)) x t | ||
|
||
let rec private foldBackOpt (f: OptimizedClosures.FSharpFunc<_,_,_>) x t = | ||
match t with | ||
| E -> x | ||
| T(_, l, v, r) -> | ||
let x = foldBackOpt f x r | ||
let x = f.Invoke(x,v) | ||
foldBackOpt f x l | ||
|
||
/// Executes a function on each element in reverse order (for example: 3, 2, 1 or c, b, a). | ||
let foldBack f x t = foldBackOpt (OptimizedClosures.FSharpFunc<_,_,_>.Adapt(f)) x t | ||
|
||
/// O(n): Returns a list containing the elements in the tree. | ||
let toList (tree: AaTree<'T>) = | ||
foldBack (fun a e -> e::a) [] tree | ||
|
||
let toSeq (tree: AaTree<'T>) = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we provide a lazy implementation? IMO it is unexpected and surprising for the user that just getting the seq, without even using it is O(n). Maybe something along the lines of: let rec toSeq (tree: AaTree<'T>) =
seq{
match tree with
| E -> ()
| T(_, l, v, r) ->
yield! toSeq l
yield v
yield! toSeq r
} ? |
||
tree |> toList |> List.toSeq | ||
|
||
let toArray (tree: AaTree<'T>) = | ||
tree |> toList |> List.toArray | ||
|
||
/// O(n log n): Builds an AaTree from the elements in the given list. | ||
let ofList collection = | ||
List.fold (fun acc item -> insert item acc) empty collection | ||
|
||
let ofSeq collection = | ||
Seq.fold (fun acc item -> insert item acc) empty collection | ||
|
||
let ofArray collection = | ||
Array.fold (fun acc item -> insert item acc) empty collection | ||
|
||
type AaTree<'T when 'T: comparison> with | ||
member x.Insert(y) = insert y x | ||
member x.Delete(y) = delete y x | ||
member x.ToSeq() = toSeq x | ||
member x.ToArray() = toArray x | ||
member x.Fold(folder, initialState) = fold folder initialState x | ||
member x.FoldBack(folder, initialState) = foldBack folder initialState x | ||
member x.Find(y) = find y x | ||
member x.TryFind(y) = tryFind y x | ||
member x.IsEmpty() = isEmpty x |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
namespace FSharpx.Collections.Experimental.Tests | ||
|
||
open FSharpx.Collections | ||
open FSharpx.Collections.Experimental | ||
open Expecto | ||
open Expecto.Flip | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe that you are not using the flip style and that the code will compile again if you remove it. |
||
|
||
module AaTreeTest = | ||
[<Tests>] | ||
let testAaTree = | ||
testList "AaTree" [ | ||
|
||
(* Existence tests. *) | ||
test "test isEmpty" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we divide the tests to have test per assertion/aspect and explain in the test name which aspect of |
||
Expect.isTrue <| AaTree.isEmpty AaTree.empty <| "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not use empty assertion messages |
||
Expect.isFalse <| AaTree.isEmpty (AaTree.ofList [9]) <| "" | ||
} | ||
|
||
test "test exists" { | ||
let tree = AaTree.ofList [9] | ||
Expect.isTrue <| AaTree.exists 9 tree <| "" | ||
Expect.isFalse <| AaTree.exists 10 tree <| "" | ||
} | ||
|
||
test "test notExists" { | ||
let tree = AaTree.ofList [9] | ||
Expect.isFalse <| AaTree.notExists 9 tree <| "" | ||
Expect.isTrue <| AaTree.notExists 10 tree <| "" | ||
} | ||
|
||
test "test tryFind" { | ||
let tree = AaTree.ofList ["hello"; "bye"] | ||
Expect.equal (Some("hello")) <| AaTree.tryFind "hello" tree <| "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using AAA(arrange, act, assert) structure for the test. let tree = AaTree.ofList ["hello"; "bye"]
let result = AaTree.tryFind "hello" tree
Expect.equal (Some "hello" ) result "tryFind should find hello in aatree created from [hello; bye]" |
||
Expect.isNone <| AaTree.tryFind "goodbye" tree <| "" | ||
} | ||
|
||
test "test find" { | ||
let tree = AaTree.ofList ["hello"; "bye"] | ||
Expect.equal "hello" <| AaTree.find "hello" tree <| "" | ||
Expect.throws (fun () -> AaTree.find "goodbye" tree |> ignore) "" | ||
} | ||
|
||
(* Conversion from tests. *) | ||
test "test ofList" { | ||
let list = ['a'; 'b'; 'c'; 'd'; 'e'] | ||
let tree = AaTree.ofList list | ||
for i in list do | ||
Expect.isTrue <| AaTree.exists i tree <| "" | ||
} | ||
|
||
test "test ofArray" { | ||
let array = [|1; 2; 3; 4; 5|] | ||
let tree = AaTree.ofArray array | ||
for i in array do | ||
Expect.isTrue <| AaTree.exists i tree <| "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
test "test ofSeq" { | ||
let seq = Seq.ofList ["hello"; "yellow"; "bye"; "try"] | ||
let tree = AaTree.ofSeq seq | ||
for i in seq do | ||
Expect.isTrue <| AaTree.exists i tree <| "" | ||
} | ||
|
||
(* Conversion to tests. *) | ||
test "test toList" { | ||
let inputList = [0;1;2;3] | ||
let tree = AaTree.ofList inputList | ||
let outputList = AaTree.toList tree | ||
Expect.equal outputList inputList "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it guaranteed that |
||
} | ||
|
||
test "test toArray" { | ||
let inputArray = [|0;1;2;3|] | ||
let tree = AaTree.ofArray inputArray | ||
let outputArray = AaTree.toArray tree | ||
Expect.equal outputArray inputArray "" | ||
} | ||
|
||
test "test toSeq" { | ||
let inputSeq = Seq.ofList ["hi";"why";"try"] | ||
let tree = AaTree.ofSeq inputSeq | ||
let outputSeq = AaTree.toSeq tree | ||
Expect.containsAll outputSeq inputSeq "" | ||
} | ||
|
||
(* Fold and foldback tests. | ||
* We will try building two lists using fold/foldback, | ||
* because that is an operation where order matters. *) | ||
test "test fold" { | ||
let tree = AaTree.ofList [1;2;3] | ||
let foldBackResult = AaTree.fold (fun a e -> e::a) [] tree | ||
Expect.equal foldBackResult [3;2;1] "" | ||
} | ||
|
||
test "test foldBack" { | ||
let tree = AaTree.ofList [1;2;3] | ||
let foldResult = AaTree.foldBack (fun a e -> e::a) [] tree | ||
Expect.equal foldResult [1;2;3] "" | ||
} | ||
|
||
(* Insert and delete tests. *) | ||
test "test insert" { | ||
let numsToInsert = [1;2;3;4;5] | ||
// Insert items into tree from list via AaTree.Insert in lambda. | ||
let tree = List.fold (fun tree el -> AaTree.insert el tree) AaTree.empty numsToInsert | ||
|
||
// Test that each item in the list is in the tree. | ||
for i in numsToInsert do | ||
Expect.isTrue <| AaTree.exists i tree <| "" | ||
} | ||
|
||
test "test delete" { | ||
// We have to insert items into a tree before we can delete them. | ||
let numsToInsert = [1;2;3;4;5] | ||
let tree = List.fold (fun tree el -> AaTree.insert el tree) AaTree.empty numsToInsert | ||
|
||
// Define numbers to delete and use List.fold to perform AaTree.delete on all | ||
let numsToDelete = [1;2;4;5] | ||
let tree = List.fold (fun tree el -> AaTree.delete el tree) tree numsToDelete | ||
|
||
// Test that none of the deleted items exist | ||
for i in numsToDelete do | ||
Expect.isFalse <| AaTree.exists i tree <| "" | ||
|
||
// Test that the one element we did not delete still exists in the tree. | ||
Expect.isTrue <| AaTree.exists 3 tree <| "" | ||
} | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will be able to familiarize myself with it on Tuesday.