I have set of elements that each have relative predicates which I want to use to properly sort the group. What is the best algorithm/approach to make the following group get sorted correctly to
[a, c, b, e, f, d]
or [c, a, b, e, f, d]
?
[
a,
b, // >a and <e
c, // =a
d, // >f
e, // <f
f, // >a
]
I set up my predicates in Swift by doing the following:
import Foundation
class Item {
let id: String
let relations: [String: ComparisonResult]
var description: String {
return id
}
init(id: String, relations: [String: ComparisonResult] = [:]) {
self.id = id
self.relations = relations
}
}
let items = [
Item(id: "a"),
Item(id: "b", relations: ["a": .orderedDescending, "e": .orderedAscending]),
Item(id: "c", relations: ["a": .orderedSame]),
Item(id: "d", relations: ["f": .orderedDescending]),
Item(id: "e", relations: ["f": .orderedAscending]),
Item(id: "f", relations: ["a": .orderedDescending]),
]
print(items) // [a, b, c, d, e, f, g]
print(items.sorted) // [a, c, b, e, f, d]
Using Alexander Reinstate Monica's suggestion in the comments I created a graph theory solution that works as desired below!
import Foundation
extension ComparisonResult {
var inverse: ComparisonResult {
switch self {
case .orderedAscending: return .orderedDescending
case .orderedDescending: return .orderedAscending
default: return .orderedSame
}
}
}
extension ComparisonResult: CustomStringConvertible {
public var description: String {
switch self {
case .orderedAscending: return "<"
case .orderedDescending: return ">"
default: return "="
}
}
}
class Graph {
var nodes = [String]()
var arcs = [String: [String: ComparisonResult]]()
init(nodes: [String] = []) {
self.nodes = nodes
}
func connect(_ a: String, _ b: String, _ relation: ComparisonResult) {
var arcs = self.arcs[a] ?? [:]
arcs[b] = relation
self.arcs[a] = arcs
arcs = self.arcs[b] ?? [:]
arcs[a] = relation.inverse
self.arcs[b] = arcs
}
func connect(_ a: String, _ b: String, _ relation: String) {
switch relation {
case "<": connect(a, b, .orderedAscending)
case ">": connect(a, b, .orderedDescending)
default: connect(a, b, .orderedSame)
}
}
func compare(_ a: String, _ b: String, _ excluding: [String] = []) -> ComparisonResult {
if let arcs = self.arcs[a] {
if let value = arcs[b] { return value }
for (id, relation) in arcs {
guard !excluding.contains(id), relation != .orderedDescending else { continue }
return compare(id, b, excluding + [a, b, id])
}
}
return .orderedSame
}
func sorted(reversed: Bool = false) -> [String] {
return nodes.sorted {
if reversed { return self.compare($0, $1) != .orderedAscending }
return self.compare($0, $1) == .orderedAscending
}
}
}
extension Graph: CustomStringConvertible {
var description: String {
var strings = [String]()
for (id, map) in arcs {
strings.append("\(id) -> \(map)")
}
return strings.joined(separator: "\n")
}
}
let graph = Graph(nodes: ["a","b","c","d","e","f"])
graph.connect("b", "a", ">")
graph.connect("b", "e", "<")
graph.connect("c", "a", "=")
graph.connect("e", "f", "<")
graph.connect("f", "a", ">")
graph.connect("d", "f", ">")
print(graph)
print(graph.nodes) // [a, b, c, d, e, f]
print(graph.sorted()) // [a, c, b, e, f, d]
print(graph.sorted(reversed: true)) // [d, f, e, b, c, a]