Reputation: 123
I have an array of 5 custom objects (all objects are of the same custom type, City
). The below is the output of typing po cities
in my xcode terminal:
▿ 5 elements
▿ 0 : City
▿ name : Name
- rawValue : "Manchester"
▿ description : Optional<String>
- some : "Old Trafford"
▿ 1 : City
▿ name : Name
- rawValue : "London"
▿ description : Optional<String>
- some : "Emirates"
▿ 2 : City
▿ name : Name
- rawValue : "Liverpool"
▿ description : Optional<String>
- some : "Anfield"
▿ 3 : City
▿ name : Name
- rawValue : "Newcastle"
▿ description : Optional<String>
- some : "St James Park"
▿ 4 : City
▿ name : Name
- rawValue : "Norwich"
▿ description : Optional<String>
- some : "Carrow Road"
How can I sort these so the first three items returned from this array are always London
, Liverpool
, Manchester
? The final 2 elements order is irrelevant here.
I've tried forcing the order of the elements by their current array position, i.e. forcing City[0]
to move to City[2]
- however this isn't suitable as the order I receive the elements within City
can change - the position needs to be set based on the output of the specific City
rawValue
TIA
Upvotes: 0
Views: 215
Reputation: 131398
Assuming you want to sort your cities based on raw value:
let sortedCities = cities.sorted { $0.name.rawValue < $1.name.rawValue }
sortedCities.forEach {print($0)}
However, you say you want them sorted in the order London , Liverpool, Manchester, which is not alphabetical.
If you have some arbitrary sort order for your Name values, I suggest using a dictionary of sort indexes:
enum Name: String {
case Manchester
case London
case Liverpool
case Newcastle
case Norwich
}
let sortIndexes: [Name: Int] = [.London:0,
.Liverpool:1,
.Manchester: 2,
.Newcastle: 3,
.Norwich: 4,
]
struct City: CustomStringConvertible {
let name: Name
let info: String?
var description: String {
let info = self.info ?? "nil"
return "City(name: \(self.name.rawValue), info: \(info))"
}
}
let cities = [
City(name: .London, info: "Emirates"),
City(name: .Liverpool, info: "Anfield"),
City(name: .Manchester, info: "Old Trafford"),
City(name: .Newcastle, info: "St James Park"),
City(name: .Norwich, info: "Carrow Road"),
]
let sortedCities = cities.sorted { sortIndexes[$0.name]! < sortIndexes[$1.name]! }
sortedCities.forEach {print($0)}
Alternately, you can make your enums be type Int, and use a description to map to display names, as suggested in Leo's comment.
Here is working example code for that approach:
enum Name: Int, CustomStringConvertible {
case london
case liverpool
case manchester
case newcastle
case norwich
var description: String {
switch self {
case .london:
return "London"
case .liverpool:
return "Liverpool"
case .manchester:
return "Manchester"
case .newcastle:
return "Newcastle"
case .norwich:
return "Norwich"
}
}
}
struct City: CustomStringConvertible {
let name: Name
let info: String?
var description: String {
let info = self.info ?? "nil"
return "City(name: \(self.name.description), info: \(info))"
}
}
let cities = [
City(name: .london, info: "Emirates"),
City(name: .liverpool, info: "Anfield"),
City(name: .manchester, info: "Old Trafford"),
City(name: .newcastle, info: "St James Park"),
City(name: .norwich, info: "Carrow Road"),
]
let sortedCities = cities.sorted { $0.name.rawValue < $1.name.rawValue }
sortedCities.forEach {print($0)}
Upvotes: 1
Reputation: 49590
Since you don't need to sort the entire array, but just ensure the order of specific elements at the top of the array, it might be best to just extract the elements of interest and construct a new array with those elements at the top and with the rest left unchanged.
This way it has an O(n) complexity (assuming the custom sorting of the top is relatively small so can be assumed to be a constant), so for larger arrays it should be faster than doing the full array sorting.
The general approach would be:
let unsorted: [City] = ...
let topOrder = ["London", "Liverpool", "Manchester"]
var top: [String: City] = [:]
var rest: [City] = []
for city in unsorted {
if topCorder.contains(city.name) {
top[city.name] = city
} else {
rest.append(city)
}
}
let sorted = topOrder.compactMap{ top[$0] } + rest
This doesn't handle the case of duplicates (it will drop them), but does handle the case of missing top cities.
Upvotes: 1
Reputation:
Comparable can be automatically synthesized for CaseIterable as of Swift 5.3.
import Foundation
struct City {
enum Name: CaseIterable, Comparable {
case london
case liverpool
case manchester
case newcastle
case norwich
}
let name: Name
}
extension City.Name: RawRepresentable {
init?(rawValue: String) {
guard let name = ( Self.allCases.first { $0.rawValue == rawValue } )
else { return nil }
self = name
}
var rawValue: String { "\(self)".capitalized }
}
AnyIterator { }
.prefix(5)
.map { City.Name.allCases.randomElement()! }
.map(City.init)
.sorted(\.name)
public extension Sequence {
/// Sorted by a common `Comparable` value.
func sorted<Comparable: Swift.Comparable>(
_ comparable: (Element) throws -> Comparable
) rethrows -> [Element] {
try sorted(comparable, <)
}
/// Sorted by a common `Comparable` value, and sorting closure.
func sorted<Comparable: Swift.Comparable>(
_ comparable: (Element) throws -> Comparable,
_ areInIncreasingOrder: (Comparable, Comparable) throws -> Bool
) rethrows -> [Element] {
try sorted {
try areInIncreasingOrder(comparable($0), comparable($1))
}
}
}
Upvotes: 0