Skip to content

Instantly share code, notes, and snippets.

@marciok
Created June 22, 2016 21:15
Show Gist options
  • Save marciok/6585da91a6967ad92f5b6ee2183c9552 to your computer and use it in GitHub Desktop.
Save marciok/6585da91a6967ad92f5b6ee2183c9552 to your computer and use it in GitHub Desktop.
Dijkstra's algorithm in Swift 3
/**
Dijkstra's algorithm in Swift 3
The idea is to create a more protocol oriented implementation.
Note: It could use some optimizations, if you wish to use in production.
*/
import Foundation
protocol Vertex {
var id: String { get }
var edges: [Edge] { get set}
}
protocol Edge {
var weight: Int { get set}
var destination: Vertex { get set}
}
final class Path {
let distance: Int
let destination: Vertex
var previousPath: Path?
init(distance: Int, destination: Vertex, previousPath: Path?) {
self.distance = distance
self.destination = destination
self.previousPath = previousPath
}
}
protocol Graph {
func add<V: Vertex, E: Edge>(edge: E, from source: inout V)
// Returns the shortest Path from an origin to a destination
func shortestPathDijkstra(from origin: Vertex, to destination: Vertex) -> Path?
// Returns the shortest Paths from an orgin to all reachable Vertexes
func shortestPathDijkstra(from origin: Vertex) -> [Path]
}
extension Graph {
func add<V: Vertex, E: Edge>(edge: E, from source: inout V) {
source.edges.append(edge)
}
func shortestPathDijkstra(from origin: Vertex, to destination: Vertex) -> Path? {
return self.shortestPathDijkstra(from: origin).filter { $0.destination.id == destination.id }.first
}
func shortestPathDijkstra(from origin: Vertex) -> [Path] {
var frontier = [Path]()
var finalPath = [Path]()
var bestPath: Path? = nil
// 1. Create a Path for every `edge` on the `origin`
for edge in origin.edges {
let path = Path(
distance: edge.weight,
destination: edge.destination,
previousPath: nil
)
// 2. Add the path to the `frontier`
frontier.append(path)
}
// 3. Look into every Vertexes while there is a `frontier`
while frontier.count > 0 {
var bestPathIndex = 0
for index in 0..<frontier.count {
bestPath = nil
let path = frontier[index]
// 4. Find & store the `bestPath`
if bestPath == nil || path.distance < bestPath!.distance {
bestPath = path
bestPathIndex = index
}
}
// 5. Add to the frontier each `destination.edges` inside `bestPath`
for edge in bestPath!.destination.edges {
let path = Path(
distance: edge.weight + bestPath!.distance,
destination: edge.destination,
previousPath: bestPath
)
frontier.append(path)
}
// 6. Add to the `finalPath` the `bestPath`,
// if it's lower than Path to the same destination on `finalPath`
let pathsToLocation = finalPath.filter { $0.destination.id == bestPath?.destination.id }
if pathsToLocation.count == 0 {
finalPath.append(bestPath!)
} else {
for path in pathsToLocation {
if bestPath?.distance < path.distance {
finalPath.append(bestPath!)
}
}
}
// 8. Remove `bestPath` from `frontier`
frontier.remove(at: bestPathIndex)
}
return finalPath
}
}
struct Neighborhood: Graph { }
final class PointOfInterest: Vertex {
let name: String
var id: String
var edges: [Edge]
init(name: String) {
self.name = name
self.id = name
self.edges = []
}
}
struct Route: Edge {
var weight: Int
var destination: Vertex
init(distance: Int, destination: PointOfInterest) {
self.weight = distance
self.destination = destination
}
}
var bakery = PointOfInterest(name: "Bakery")
var shopB = PointOfInterest(name: "Shop B")
var shopC = PointOfInterest(name: "Shop C")
var shopD = PointOfInterest(name: "Shop D")
var prenzlauerBerg = Neighborhood()
prenzlauerBerg.add(
edge: Route(distance: 10, destination:shopB),
from: &bakery
)
prenzlauerBerg.add(
edge: Route(distance: 18, destination:shopC),
from: &bakery
)
prenzlauerBerg.add(
edge: Route(distance: 12, destination:shopC),
from: &shopB
)
prenzlauerBerg.add(
edge: Route(distance: 5, destination:shopD),
from: &shopC
)
func history(from origin: Vertex, path: Path) -> [Path] {
var history = [Path]()
history.append(path)
var previousPath = path.previousPath
while previousPath != nil {
history.append(previousPath!)
previousPath = previousPath!.previousPath
}
history.append(Path(distance: 0, destination: origin, previousPath: nil))
return history.reversed()
}
if let shortestPath = prenzlauerBerg.shortestPathDijkstra(from: bakery, to: shopD) {
let fullHistory = history(from: bakery, path: shortestPath)
print(fullHistory.map { "\($0.destination.id)(\($0.distance))" }) // ["Bakery(0)", "Shop C(18)", "Shop D(23)"]
}
@giuliocatte
Copy link

I have a problem trying to use this.
you call history using as "path" parameter the result of "shortestPathDijkstra", which is a "[Path]". but path parameter should be a "Path", not a "[Path]".
Is this a bug or there is something I'm not understanding?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment