My first Code Review post.
I'm up to a generic implementation of the A* search algorithm in Swift (for now, it's a single goal implementation).
Here's what's been coded so far:
// State : a protocole for states in the search space
protocol State : Equatable {
// successors() : returns an array of successors states in the search space
func successors() -> [Successor<Self>]
// heuristic(goal) : returns the heuristic value for a given states in relation to a given goal state
func heuristic(goal:Self) -> Double
// id : a string identifying a state
var id : String { get }
}
// States are compared by their id
func ==<T:State>(lhs:T, rhs:T) -> Bool {
return lhs.id==rhs.id
}
// Successor : represents a successor state and its cost
struct Successor<T:State> {
var state: T
var cost: Double
}
// Plan : a plan of states
struct Plan<T:State> {
// states : an array of states that make a plan
var states: [T]
// cost : the total cost of the plan
var cost: Double
// isNot(another) : checks if another plan is different from the current one
func isNot(another: Plan) -> Bool {
return !(states == another.states && cost == another.cost)
}
}
// AStar<TState> : finds the A* solution (nil if no solution found) given a start state and goal state
func AStar<TState:State>(start: TState, goal: TState) -> Plan<TState>? {
var fringe : [Plan<TState>] = [Plan(states: [start], cost: 0)]
while fringe.count>0 {
let bestPlan = fringe.minElement({
a,b
in
a.cost + a.states.last!.heuristic(goal) < b.cost + b.states.last!.heuristic(goal)
})!
fringe = fringe.filter({
plan in plan.isNot(bestPlan)
})
if bestPlan.states.last! == goal {
return bestPlan
}else{
let successors = bestPlan.states.last!.successors()
for successor in successors.filter({ s in !bestPlan.states.contains(s.state) }) {
let newPlan = Plan(states: bestPlan.states+[successor.state], cost: bestPlan.cost+successor.cost)
fringe.append(newPlan)
}
}
}
return nil
}
I'm here for some suggestions to make this Swiftier
EDIT:
I tested this implementation using a graph of vertices representing cities with costs representing road distances. I used the straight line distance as a heuristic value :
let adjacencyList : [String:[String:Double]] = [
"oued" : ["biskra":150.0],
"biskra" : ["batna":120.0, "oued":150.0],
"batna" : ["biskra":120.0, "barika":40.0, "setif":100.0, "constantine":110.0],
"barika" : ["batna":40.0, "setif":50.0],
"setif" : ["batna":100.0, "barika":50.0, "constantine":50.0, "bejaia":80.0],
"constantine": ["batna":110.0, "setif":50.0, "annaba":80.0],
"bajaia" : ["setif":80.0, "annaba":30.0],
"annaba" : ["constantine":80.0, "bejaia":30.0]
]
let straightLineDistances = [
"biskra" : ["annaba":220.0],
"batna" : ["annaba":140.0],
"barika" : ["annaba":200.0],
"setif" : ["annaba":100.0],
"constantine": ["annaba":80.0],
"bejaia" : ["annaba":30.0],
"oued" : ["annaba":320.0],
"annaba" : ["annaba":0.0]
]
final class Vertex : State, CustomDebugStringConvertible {
let label : String
init(label:String) {
self.label = label
}
func successors() -> [Successor<Vertex>] {
return adjacencyList[label]!.map { x in Successor<Vertex>(state:Vertex(label: x.0),cost: x.1) }
}
func heuristic(goal:Vertex) -> Double {
return straightLineDistances[label]![goal.label]!
}
var id : String {
return label
}
var debugDescription : String {
return id
}
}
let solution = AStar(Vertex(label: "biskra"), goal: Vertex(label: "annaba"))
print(solution)
And the output solutions was the expected A* solution. But I'm more concerned about the elegance of this implementation
fringe
is also called theopen set
. \$\endgroup\$ – Redjimi Adel May 31 '16 at 1:17