-
Notifications
You must be signed in to change notification settings - Fork 3
Dijkstra's shortest path #26
base: master
Are you sure you want to change the base?
Changes from all commits
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,132 @@ | ||
| open Jest; | ||
| open Expect; | ||
|
|
||
| describe("Breadth First Search", () => { | ||
| open Dijkstra; | ||
|
|
||
| let infinity = Pervasives.max_int; | ||
|
|
||
| test("single node", () => { | ||
|
|
||
| let adj_list = [{id: "A", neighbours: []}]; | ||
|
|
||
| let expected_dist = 0; | ||
| let expected_prev = None; | ||
|
|
||
| let {dist, prev} = search(adj_list, [], "A"); | ||
| let dist = Hashtbl.find(dist, "A"); | ||
| let prev = Hashtbl.find(prev, "A"); | ||
|
|
||
| expect((dist, prev)) |> toEqual((expected_dist, expected_prev)); | ||
| }); | ||
|
|
||
| test("simple unidirectional cycle", () => { | ||
|
|
||
| let adj_list = [ | ||
| {id: "A", neighbours: ["B"]}, | ||
| {id: "B", neighbours: ["C"]}, | ||
| {id: "C", neighbours: ["D"]}, | ||
| {id: "D", neighbours: ["A"]}]; | ||
|
|
||
| let weight_list = [ | ||
| {tail: "A", head: "B", weight: 1}, | ||
| {tail: "B", head: "C", weight: 1}, | ||
| {tail: "C", head: "D", weight: 1}, | ||
| {tail: "D", head: "A", weight: 1}] | ||
|
|
||
| let expected_dists = (0, 1, 2, 3); | ||
| let expected_prevs = (None, Some("A"), Some("B"), Some("C")); | ||
|
|
||
| let {dist, prev} = search(adj_list, weight_list, "A"); | ||
|
|
||
| let dists = ( | ||
| Hashtbl.find(dist, "A"), | ||
| Hashtbl.find(dist, "B"), | ||
| Hashtbl.find(dist, "C"), | ||
| Hashtbl.find(dist, "D")); | ||
|
|
||
| let prevs = ( | ||
| Hashtbl.find(prev, "A"), | ||
| Hashtbl.find(prev, "B"), | ||
| Hashtbl.find(prev, "C"), | ||
| Hashtbl.find(prev, "D")); | ||
|
|
||
| expect((dists, prevs)) |> toEqual((expected_dists, expected_prevs)); | ||
| }); | ||
|
|
||
| test("complete biderectional graph", () => { | ||
|
|
||
| let adj_list = [ | ||
| {id: "A", neighbours: ["B", "C", "D"]}, | ||
| {id: "B", neighbours: ["A", "C", "D"]}, | ||
| {id: "C", neighbours: ["A", "B", "D"]}, | ||
| {id: "D", neighbours: ["A", "B", "C"]}]; | ||
|
|
||
| let weight_list = [ | ||
| {tail: "A", head: "B", weight: 2}, | ||
| {tail: "A", head: "C", weight: 5}, | ||
| {tail: "A", head: "D", weight: 5}, | ||
| {tail: "B", head: "A", weight: 1}, | ||
| {tail: "B", head: "C", weight: 1}, | ||
| {tail: "B", head: "D", weight: 3}, | ||
| {tail: "C", head: "A", weight: 1}, | ||
| {tail: "C", head: "B", weight: 1}, | ||
| {tail: "C", head: "D", weight: 1}, | ||
| {tail: "D", head: "A", weight: 1}, | ||
| {tail: "D", head: "B", weight: 1}, | ||
| {tail: "D", head: "C", weight: 1}] | ||
|
|
||
| let expected_dists = (0, 2, 3, 4); | ||
| let expected_prevs = (None, Some("A"), Some("B"), Some("C")); | ||
|
|
||
| let {dist, prev} = search(adj_list, weight_list, "A"); | ||
|
|
||
| let dists = ( | ||
| Hashtbl.find(dist, "A"), | ||
| Hashtbl.find(dist, "B"), | ||
| Hashtbl.find(dist, "C"), | ||
| Hashtbl.find(dist, "D")); | ||
|
|
||
| let prevs = ( | ||
| Hashtbl.find(prev, "A"), | ||
| Hashtbl.find(prev, "B"), | ||
| Hashtbl.find(prev, "C"), | ||
| Hashtbl.find(prev, "D")); | ||
|
|
||
| expect((dists, prevs)) |> toEqual((expected_dists, expected_prevs)); | ||
| }); | ||
|
|
||
| test("forest", () => { | ||
|
|
||
| let adj_list = [ | ||
| {id: "A", neighbours: ["B"]}, | ||
| {id: "B", neighbours: ["A"]}, | ||
| {id: "C", neighbours: ["D"]}, | ||
| {id: "D", neighbours: ["C"]}]; | ||
|
|
||
| let weight_list = [ | ||
| {tail: "A", head: "B", weight: 1}, | ||
| {tail: "B", head: "A", weight: 1}, | ||
| {tail: "C", head: "D", weight: 1}, | ||
| {tail: "D", head: "C", weight: 1}] | ||
|
|
||
| let expected_dists = (0, 1, infinity, infinity); | ||
| let expected_prevs = (None, Some("A"), None, None); | ||
|
|
||
| let {dist, prev} = search(adj_list, weight_list, "A"); | ||
|
|
||
| let dists = ( | ||
| Hashtbl.find(dist, "A"), | ||
| Hashtbl.find(dist, "B"), | ||
| Hashtbl.find(dist, "C"), | ||
| Hashtbl.find(dist, "D")); | ||
|
|
||
| let prevs = ( | ||
| Hashtbl.find(prev, "A"), | ||
| Hashtbl.find(prev, "B"), | ||
| Hashtbl.find(prev, "C"), | ||
| Hashtbl.find(prev, "D")); | ||
|
|
||
| expect((dists, prevs)) |> toEqual((expected_dists, expected_prevs)); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| type node = { | ||
| id: string, | ||
| neighbours: list(string), | ||
| }; | ||
|
|
||
| type directedGraph = list(node); | ||
|
|
||
| type edge = { | ||
| tail: string, | ||
| head: string, | ||
| weight: int, | ||
| }; | ||
|
|
||
| type resultType = { | ||
| dist: Hashtbl.t(string, int), | ||
| prev: Hashtbl.t(string, option(string)) | ||
| }; | ||
|
|
||
| exception Not_found(string); | ||
|
|
||
| let infinity = Pervasives.max_int; | ||
|
|
||
| let parseAdjList = adj_list => { | ||
| let adj_tbl = Hashtbl.create(List.length(adj_list)); | ||
| let insert = ({id, neighbours}) => { | ||
| Hashtbl.add(adj_tbl, id, neighbours); | ||
| }; | ||
|
|
||
| List.iter(insert, adj_list); | ||
| let validateNeighbours = node => { | ||
| List.iter(neighbour => { | ||
| if (!Hashtbl.mem(adj_tbl, neighbour)) { | ||
| raise(Not_found(neighbour)); | ||
| } | ||
| }, node.neighbours); | ||
| }; | ||
|
|
||
| List.iter(validateNeighbours, adj_list); | ||
| adj_tbl; | ||
| }; | ||
|
|
||
| let parseWeights = (weight_list, adj_list) => { | ||
| let weight_tbl = Hashtbl.create(List.length(adj_list)); | ||
| let initializeWeights = ({id, neighbours}) => { | ||
| let adj_edge_weights = Hashtbl.create(List.length(neighbours)); | ||
| Hashtbl.add(weight_tbl, id, adj_edge_weights); | ||
| }; | ||
|
|
||
| List.iter(initializeWeights, adj_list); | ||
|
Collaborator
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. why creating a nested Hash here? Why not using a tuple as the key? |
||
| let insertWeight = ({tail, head, weight}) => { | ||
| let adj_edge_weights = Hashtbl.find(weight_tbl, tail); | ||
| Hashtbl.add(adj_edge_weights, head, weight); | ||
| }; | ||
|
|
||
| List.iter(insertWeight, weight_list); | ||
| weight_tbl; | ||
| }; | ||
|
|
||
| let initializeDist = (adj_list, source_id) => { | ||
| let dist = Hashtbl.create(List.length(adj_list)); | ||
| let initialize = ({id, _}) => { | ||
| Hashtbl.add(dist, id, infinity); | ||
| }; | ||
|
|
||
| List.iter(initialize, adj_list); | ||
| Hashtbl.replace(dist, source_id, 0); | ||
| dist; | ||
| }; | ||
|
|
||
| let initializePrev = adj_list => { | ||
| let prev = Hashtbl.create(List.length(adj_list)); | ||
| let initialize = ({id, _}) => { | ||
| Hashtbl.add(prev, id, None); | ||
| }; | ||
|
|
||
| List.iter(initialize, adj_list); | ||
| prev; | ||
| }; | ||
|
|
||
| let getEdgeWeight = (weight_tbl, tail, head) => { | ||
| let adj_edge_weights = Hashtbl.find(weight_tbl, tail); | ||
| Hashtbl.find(adj_edge_weights, head); | ||
| }; | ||
|
|
||
| let add = (a, b) => { | ||
| if (a == infinity || b == infinity) { | ||
|
Collaborator
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 don't think we should use infinity.
Member
Author
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 used
Collaborator
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. debatable but i think it's fine. |
||
| infinity; | ||
| } else { | ||
| a + b; | ||
| }; | ||
| }; | ||
|
|
||
| let rec traverse = (~queue, ~adj_tbl, ~weight_tbl, ~dist, ~prev) => { | ||
|
Collaborator
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 think |
||
| switch (Heap.size(queue)) { | ||
| | 0 => (); | ||
| | _ => { | ||
| let current_id = Heap.extract(queue); | ||
| let neighbours = Hashtbl.find(adj_tbl, current_id); | ||
|
|
||
| let updateDist = neighbour_id => { | ||
| let neighbour_dist = Hashtbl.find(dist, neighbour_id); | ||
| let current_dist = Hashtbl.find(dist, current_id); | ||
| let edge_weight = getEdgeWeight(weight_tbl, current_id, neighbour_id); | ||
| let alt_dist = add(current_dist, edge_weight); | ||
|
|
||
| if (alt_dist < neighbour_dist) { | ||
|
Collaborator
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. maybe we should refactor this and call it
Member
Author
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. You mean
Collaborator
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. my comment was for the content of the if statement |
||
| Hashtbl.replace(dist, neighbour_id, alt_dist); | ||
| Hashtbl.replace(prev, neighbour_id, Some(current_id)); | ||
| Heap.remove(queue, (key, _) => key == neighbour_id) |> ignore; | ||
|
Collaborator
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. should we add
Member
Author
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 added an
Collaborator
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 approved your PR |
||
| Heap.add(queue, neighbour_id, neighbour_id); | ||
| }; | ||
| }; | ||
|
|
||
| List.iter(updateDist, neighbours); | ||
| traverse( | ||
| ~queue=queue, | ||
| ~adj_tbl=adj_tbl, | ||
| ~weight_tbl=weight_tbl, | ||
| ~dist=dist, | ||
| ~prev=prev); | ||
| }; | ||
| }; | ||
| }; | ||
|
|
||
| let search = (adj_list, weights, source_id) => { | ||
| let prev = initializePrev(adj_list); | ||
| let dist = initializeDist(adj_list, source_id); | ||
| let weight_tbl = parseWeights(weights, adj_list); | ||
| let adj_tbl = parseAdjList(adj_list); | ||
|
|
||
| let compare = (a, b) => { | ||
| let dist_a = Hashtbl.find(dist, a); | ||
| let dist_b = Hashtbl.find(dist, b); | ||
| dist_a < dist_b; | ||
| }; | ||
|
|
||
| let priorityQueue = Heap.create(compare); | ||
| let enqueue = (key, _) => { | ||
| Heap.add(priorityQueue, key, key); | ||
| }; | ||
|
|
||
| Hashtbl.iter(enqueue, dist); | ||
| traverse( | ||
| ~queue=priorityQueue, | ||
| ~adj_tbl=adj_tbl, | ||
| ~weight_tbl=weight_tbl, | ||
| ~dist=dist, | ||
| ~prev=prev); | ||
|
|
||
| {dist: dist, prev: prev}; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| type node = { | ||
| id: string, | ||
| neighbours: list(string), | ||
|
Collaborator
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. Does a node need a set of neighbors? We can calculate them based on the edges. |
||
| }; | ||
|
|
||
| type edge = { | ||
| tail: string, | ||
| head: string, | ||
| weight: int, | ||
| }; | ||
|
|
||
| type directedGraph = list(node); | ||
|
Collaborator
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. why are you passing in the edges outside of the directedGraph?
Collaborator
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. why don't we only specify the edges and construct the nodes inside the module?
Member
Author
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. What if a node does not have any incident edges? I think we still need a list of nodes but without the |
||
|
|
||
| type resultType = { | ||
| dist: Hashtbl.t(string, int), | ||
| prev: Hashtbl.t(string, option(string)) | ||
| }; | ||
|
|
||
| let search: (directedGraph, list(edge), string) => resultType; | ||
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 think we should construct the adj_table from the edges instead. We don't need to specify the adj_list at all!