Revisiting BFS
What is BFS
- BFS is a way of creating a sequence with all elements of a tree or graph
- It consists in returning all elements with depth N, and then the elements with depth N+1, until elements are over
- The initial depth is 0
The common solution
- The typical imperative implementation of BFS relies on a queue
- The current element is pulled out of the queue, and its children are pushed in
Questioning the common solution
- The relation between the queue, the way it works, and the BFS definition is not clear
- Is it possible to rely on pure functional data structures instead of mutation?
First discovery
- The queue in the imperative solution is divided in two parts:
| children with depth N | children with depth N+1|
- Now is clear what we do is returning children in the same level, and meanwhile conveniently recording and postponing children in the next level
- We could have separate lists for children in different levels
- We could annotate each level with its corresponding number, so we can filter later which levels do we need
First restriction
- BFS in a graph requires to mark which nodes we have visited, while in a tree we don't, since in the latter case there's no possibility to create an infinite loop
- the function returning the children can take care independently of marking or not, depending on wether is a graph or a tree. That way the BFS implementation can be delivered from that concern, and thus is cleaner and more general.
Second restriction
- In functional programming instead of representing mutation in a block of code, we create a new instance of it substituting the old value by the new one.
- You can see the difference in comparing the following two blocks of code:
|
let moveFromTo () =
let rec loop from to' =
match from with
| [] -> (from, to')
| x::xs ->
// here we replace to' by x::to' in a new instance of loop
loop xs (x::to')
let from = [1; 2 ;3]
let to' = []
loop from to'
How the implementation works
GetChildren
is our generic way of getting children from whatever nested structure we are dealing with (trees, graphs)- 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.
- Our
List.fold
processes each elementx
inlevel
. In each iteration of it needs an updatedGetChildren
instance, which stores the visited nodes. You can see in the function passed as parameter howxs
is replaced byxs @ ys
andgc
fornewGc
, for the next iteration. - Once the fold finishes we have in
nextLevel
all the nodes of leveln+1
. SincenextLevel
is the replacement forlevel
and ourbfs
function operates on particular level instances, then is time to callbfs
withnextLevel
. In casenextLevel
is empty, we return the empty sequence and stop the recursive calls.
type GetChildren<'a> = { get: 'a -> (GetChildren<'a> * 'a list) }
let rec bfs (gc: GetChildren<'a>, n: int, level: 'a list) =
seq {
yield (n, level)
let nextLevel =
level
|> List.fold
(fun (gc, xs) x ->
let newGc, ys = gc.get x
(newGc, xs @ ys)
)
(gc, []) // initial state
yield!
match nextLevel with
| _, [] -> Seq.empty
| (chl, nl) -> bfs (chl, n+1, nl)
}
Testing the implementation with a graph
type Graph<'a when 'a: comparison> = Map<'a, List<'a>>
let graphChildren (g: Graph<'a>) =
let rec children visited =
{ get =
fun x ->
if Set.contains x visited then
// x is already visited, no need to mark it as visited
(children visited, [])
else
let newChildren = Set.add x visited |> children
match Map.tryFind x g with
| Some xs -> (newChildren, xs)
| None -> (newChildren, []) }
children Set.empty
let g = Map [ 1, [ 2; 3; 4 ]; 2, [ 5; 6 ]; 3, [ 7; 8 ]; 4, [ 9; 10 ] ]
bfs (graphChildren g, 0, [ 1 ]) |> Seq.iter (printfn "%A")
|
Testing the implementation with a tree
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,
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.
Example:
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.
type Tree<'a> = Node of 'a * Tree<'a> list
let rec childrenSeq (Node(_, xs)) =
seq {
match xs with
| [] -> ()
| _ ->
let nodeValues = xs |> List.map (fun (Node(x, _)) -> x)
yield nodeValues
for x in xs do
yield! childrenSeq x
}
let treeChildren (t: Tree<'a>) =
let chs = childrenSeq t |> Seq.toList
let rec loop (chs: ('a list) list) =
{ get =
fun _ ->
match chs with
| [] -> (loop [], [])
| head::tail -> (loop tail, head) }
loop chs
let t =
Node(
1,
[ Node(2, [ Node(5, []); Node(6, []) ])
Node(3, [ Node(7, []); Node(8, []) ])
Node(4, [ Node(9, []); Node(10, []) ]) ]
)
bfs (treeChildren t, 0, [ 1 ]) |> Seq.iter (printfn "%A")
|
type GetChildren<'a> =
{ get: ('a -> GetChildren<'a> * 'a list) }
'a
type 'T list = List<'T>
val bfs: gc: GetChildren<'a> * n: int * level: 'a list -> (int * 'a list) seq
val gc: GetChildren<'a>
val n: int
Multiple items
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
val level: 'a list
Multiple items
val seq: sequence: 'T seq -> 'T seq
--------------------
type 'T seq = System.Collections.Generic.IEnumerable<'T>
val seq: sequence: 'T seq -> 'T seq
--------------------
type 'T seq = System.Collections.Generic.IEnumerable<'T>
val nextLevel: GetChildren<'a> * 'a list
Multiple items
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val fold<'T,'State> : folder: ('State -> 'T -> 'State) -> state: 'State -> list: 'T list -> 'State
val xs: 'a list
val x: 'a
val newGc: GetChildren<'a>
val ys: 'a list
GetChildren.get: 'a -> GetChildren<'a> * 'a list
module Seq
from Microsoft.FSharp.Collections
val empty<'T> : 'T seq
val chl: GetChildren<'a>
val nl: 'a list
type Graph<'a (requires comparison)> = Map<'a,List<'a>>
Multiple items
module Map from Microsoft.FSharp.Collections
--------------------
type Map<'Key,'Value (requires comparison)> = interface IReadOnlyDictionary<'Key,'Value> interface IReadOnlyCollection<KeyValuePair<'Key,'Value>> interface IEnumerable interface IStructuralEquatable interface IComparable interface IEnumerable<KeyValuePair<'Key,'Value>> interface ICollection<KeyValuePair<'Key,'Value>> interface IDictionary<'Key,'Value> new: elements: ('Key * 'Value) seq -> Map<'Key,'Value> member Add: key: 'Key * value: 'Value -> Map<'Key,'Value> ...
--------------------
new: elements: ('Key * 'Value) seq -> Map<'Key,'Value>
module Map from Microsoft.FSharp.Collections
--------------------
type Map<'Key,'Value (requires comparison)> = interface IReadOnlyDictionary<'Key,'Value> interface IReadOnlyCollection<KeyValuePair<'Key,'Value>> interface IEnumerable interface IStructuralEquatable interface IComparable interface IEnumerable<KeyValuePair<'Key,'Value>> interface ICollection<KeyValuePair<'Key,'Value>> interface IDictionary<'Key,'Value> new: elements: ('Key * 'Value) seq -> Map<'Key,'Value> member Add: key: 'Key * value: 'Value -> Map<'Key,'Value> ...
--------------------
new: elements: ('Key * 'Value) seq -> Map<'Key,'Value>
val graphChildren: g: Graph<'a> -> GetChildren<'a> (requires comparison)
val g: Graph<'a> (requires comparison)
val children: visited: Set<'a> -> GetChildren<'a> (requires comparison)
val visited: Set<'a> (requires comparison)
val x: 'a (requires comparison)
Multiple items
module Set from Microsoft.FSharp.Collections
--------------------
type Set<'T (requires comparison)> = interface IReadOnlyCollection<'T> interface IStructuralEquatable interface IComparable interface IEnumerable interface IEnumerable<'T> interface ICollection<'T> new: elements: 'T seq -> Set<'T> member Add: value: 'T -> Set<'T> member Contains: value: 'T -> bool override Equals: obj -> bool ...
--------------------
new: elements: 'T seq -> Set<'T>
module Set from Microsoft.FSharp.Collections
--------------------
type Set<'T (requires comparison)> = interface IReadOnlyCollection<'T> interface IStructuralEquatable interface IComparable interface IEnumerable interface IEnumerable<'T> interface ICollection<'T> new: elements: 'T seq -> Set<'T> member Add: value: 'T -> Set<'T> member Contains: value: 'T -> bool override Equals: obj -> bool ...
--------------------
new: elements: 'T seq -> Set<'T>
val contains: element: 'T -> set: Set<'T> -> bool (requires comparison)
val newChildren: GetChildren<'a> (requires comparison)
val add: value: 'T -> set: Set<'T> -> Set<'T> (requires comparison)
val tryFind: key: 'Key -> table: Map<'Key,'T> -> 'T option (requires comparison)
union case Option.Some: Value: 'T -> Option<'T>
val xs: List<'a> (requires comparison)
union case Option.None: Option<'T>
val empty<'T (requires comparison)> : Set<'T> (requires comparison)
val g: Map<int,int list>
val iter: action: ('T -> unit) -> source: 'T seq -> unit
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
type Tree<'a> = | Node of 'a * Tree<'a> list
val childrenSeq: Tree<'a> -> 'a list seq
union case Tree.Node: 'a * Tree<'a> list -> Tree<'a>
val xs: Tree<'a> list
val nodeValues: 'a list
val map: mapping: ('T -> 'U) -> list: 'T list -> 'U list
val x: Tree<'a>
val treeChildren: t: Tree<'a> -> GetChildren<'a>
val t: Tree<'a>
val chs: 'a list list
val toList: source: 'T seq -> 'T list
val loop: chs: 'a list list -> GetChildren<'a>
val head: 'a list
val tail: 'a list list
val t: Tree<int>