Skip to content

Commit

Permalink
BFS draft
Browse files Browse the repository at this point in the history
  • Loading branch information
lamg committed Apr 1, 2024
0 parents commit adc2df9
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .editorconfig
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tests.fsx
274 changes: 274 additions & 0 deletions bfs.ipynb
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
}

0 comments on commit adc2df9

Please sign in to comment.