-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit adc2df9
Showing
3 changed files
with
282 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[*.fs] | ||
indent_size = 2 | ||
end_of_line = lf | ||
|
||
[*.fsx] | ||
indent_size = 2 | ||
end_of_line = lf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
tests.fsx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"# Revisiting BFS" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"##### What is BFS\n", | ||
"- BFS is a way of creating a sequence with all elements of a tree or graph\n", | ||
"- It consists in returning all elements with depth N, and then the elements with depth N+1, until elements are over\n", | ||
"- The initial depth is 0\n", | ||
"\n", | ||
"##### The common solution\n", | ||
"- The typical imperative implementation of BFS relies on a queue\n", | ||
"- The current element is pulled out of the queue, and its children are pushed in\n", | ||
"\n", | ||
"##### Questioning the common solution\n", | ||
"- The relation between the queue, the way it works, and the BFS definition is not clear\n", | ||
"- Is it possible to rely on pure functional data structures instead of mutation?\n", | ||
"\n", | ||
"##### First discovery\n", | ||
"- The queue in the imperative solution is divided in two parts: `| children with depth N | children with depth N+1|`\n", | ||
"- Now is clear what we do is returning children in the same level, and meanwhile conveniently recording and postponing children in the next level\n", | ||
"- We could have separate lists for children in different levels\n", | ||
"- We could annotate each level with its corresponding number, so we can filter later which levels do we need\n", | ||
"\n", | ||
"##### First restriction\n", | ||
"- BFS in a graph requires to mark which nodes we have visited, while in a tree we don't, \n", | ||
"since in the latter case there's no possibility to create an infinite loop\n", | ||
"- the function returning the children can take care independently of marking or not, depending on wether is a graph or a tree.\n", | ||
"That way the BFS implementation can be delivered from that concern, and thus is cleaner and more general.\n", | ||
"\n", | ||
"##### Second restriction\n", | ||
"- In functional programming instead of representing mutation in a block of code, we create a new instance of it\n", | ||
"substituting the old value by the new one.\n", | ||
"- Example:\n", | ||
"\n", | ||
"```csharp\n", | ||
"using System;\n", | ||
"\n", | ||
"int[] MoveFromTo() {\n", | ||
" var from = new List<int>(new int[] { 1, 2, 3 });\n", | ||
" var to = new List<int>();\n", | ||
" while (from.Count > 0) {\n", | ||
" to.Add(from[0]);\n", | ||
" from.RemoveAt(0);\n", | ||
" }\n", | ||
" return to.ToArray();\n", | ||
"}\n", | ||
"```\n", | ||
"\n", | ||
"```fsharp\n", | ||
"let moveFromTo () =\n", | ||
" let rec loop from to' =\n", | ||
" match from with\n", | ||
" | [] -> (from, to')\n", | ||
" | x::xs -> \n", | ||
" // here we replace to' by x::to' in a new instance of loop\n", | ||
" loop xs (x::to')\n", | ||
" \n", | ||
" let from = [1; 2 ;3]\n", | ||
" let to' = []\n", | ||
" loop from to'\n", | ||
"```" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"### How the implementation works" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"- `GetChildren` is our generic way of getting children from whatever nested structure we are dealing with (trees, graphs)\n", | ||
"- Along with the list of children it returns and instance of itself. This is needed because is the functional way of updating the collection where we store visited nodes.\n", | ||
"- Our `List.fold` processes each element `x` in `level`. In each iteration of it needs an updated `GetChildren` instance, which stores the visited nodes. You can see in the function passed as parameter how `xs` is replaced by `xs @ ys` and `gc` for `newGc`, for the next iteration.\n", | ||
"- Once the fold finishes we have in `nextLevel` all the nodes of level `n+1`. Since `nextLevel` is the replacement for `level` and our `bfs` function operates on particular level instances, then is time to call `bfs` with `nextLevel`. In case `nextLevel` is empty, we return the empty sequence and stop the recursive calls." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 1, | ||
"metadata": { | ||
"dotnet_interactive": { | ||
"language": "fsharp" | ||
}, | ||
"polyglot_notebook": { | ||
"kernelName": "fsharp" | ||
}, | ||
"vscode": { | ||
"languageId": "polyglot-notebook" | ||
} | ||
}, | ||
"outputs": [], | ||
"source": [ | ||
"type GetChildren<'a> = { get: 'a -> (GetChildren<'a> * 'a list) }\n", | ||
"\n", | ||
"let rec bfs (gc: GetChildren<'a>, n: int, level: 'a list) =\n", | ||
" seq {\n", | ||
" yield (n, level)\n", | ||
"\n", | ||
" let nextLevel =\n", | ||
" level\n", | ||
" |> List.fold\n", | ||
" (fun (gc, xs) x ->\n", | ||
" let newGc, ys = gc.get x\n", | ||
" (newGc, xs @ ys)\n", | ||
" )\n", | ||
"\n", | ||
" (gc, []) // initial state\n", | ||
" \n", | ||
" yield! \n", | ||
" match nextLevel with\n", | ||
" | _, [] -> Seq.empty\n", | ||
" | (chl, nl) -> bfs (chl, n+1, nl)\n", | ||
" }\n" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"##### Testing the implementation with a graph" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 4, | ||
"metadata": { | ||
"dotnet_interactive": { | ||
"language": "fsharp" | ||
}, | ||
"polyglot_notebook": { | ||
"kernelName": "fsharp" | ||
}, | ||
"vscode": { | ||
"languageId": "polyglot-notebook" | ||
} | ||
}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"(0, [1])\n", | ||
"(1, [2; 3; 4])\n", | ||
"(2, [5; 6; 7; 8; 9; 10])\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"type Graph<'a when 'a: comparison> = Map<'a, List<'a>>\n", | ||
"\n", | ||
"let graphChildren (g: Graph<'a>) =\n", | ||
" let rec children visited =\n", | ||
" { get =\n", | ||
" fun x ->\n", | ||
" if Set.contains x visited then\n", | ||
" // x is already visited, no need to mark it as visited\n", | ||
" (children visited, [])\n", | ||
" else\n", | ||
" let newChildren = Set.add x visited |> children\n", | ||
"\n", | ||
" match Map.tryFind x g with\n", | ||
" | Some xs -> (newChildren, xs)\n", | ||
" | None -> (newChildren, []) }\n", | ||
"\n", | ||
" children Set.empty\n", | ||
"\n", | ||
"let g = Map [ 1, [ 2; 3; 4 ]; 2, [ 5; 6 ]; 3, [ 7; 8 ]; 4, [ 9; 10 ] ]\n", | ||
"\n", | ||
"bfs (graphChildren g, 0, [ 1 ]) |> Seq.iter (printfn \"%A\")" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"##### Testing the implementation with a tree" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"In a tree by visiting a node there's no way of going back to it, since there are no cycles. Keeping that in mind, \n", | ||
"we can rely on a sequence of children to be consumed by `bfs`. As long as we put children in the order `bfs` expects them, the implementation will be correct.\n", | ||
"\n", | ||
"Example:\n", | ||
"We know if `bfs` gets the children of node `X`, then after that it will ask for children of node `X+1`, where `X` and `X+1` are consecutive nodes in the same level." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 6, | ||
"metadata": { | ||
"dotnet_interactive": { | ||
"language": "fsharp" | ||
}, | ||
"polyglot_notebook": { | ||
"kernelName": "fsharp" | ||
}, | ||
"vscode": { | ||
"languageId": "polyglot-notebook" | ||
} | ||
}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"(0, [1])\n", | ||
"(1, [2; 3; 4])\n", | ||
"(2, [5; 6; 7; 8; 9; 10])\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"type Tree<'a> = Node of 'a * Tree<'a> list\n", | ||
"\n", | ||
"let rec childrenSeq (Node(_, xs)) =\n", | ||
" seq {\n", | ||
" match xs with\n", | ||
" | [] -> ()\n", | ||
" | _ ->\n", | ||
" let nodeValues = xs |> List.map (fun (Node(x, _)) -> x)\n", | ||
" yield nodeValues\n", | ||
"\n", | ||
" for x in xs do\n", | ||
" yield! childrenSeq x\n", | ||
" }\n", | ||
"\n", | ||
"let treeChildren (t: Tree<'a>) =\n", | ||
" let chs = childrenSeq t |> Seq.toList\n", | ||
"\n", | ||
" let rec loop (chs: ('a list) list) =\n", | ||
" { get =\n", | ||
" fun _ ->\n", | ||
" match chs with\n", | ||
" | [] -> (loop [], [])\n", | ||
" | head::tail -> (loop tail, head) }\n", | ||
"\n", | ||
" loop chs\n", | ||
"\n", | ||
"let t =\n", | ||
" Node(\n", | ||
" 1,\n", | ||
" [ Node(2, [ Node(5, []); Node(6, []) ])\n", | ||
" Node(3, [ Node(7, []); Node(8, []) ])\n", | ||
" Node(4, [ Node(9, []); Node(10, []) ]) ]\n", | ||
" )\n", | ||
"\n", | ||
"bfs (treeChildren t, 0, [ 1 ]) |> Seq.iter (printfn \"%A\")" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"language_info": { | ||
"name": "python" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 2 | ||
} |