Swift sort an array with strings and numbers [duplicate]

Xcode 11.3 • Swift 5.2 or later

You can use String method localizedStandardCompare (diacritics and case insensitive):

let array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
let sorted = array.sorted {$0.localizedStandardCompare($1) == .orderedAscending}

print(sorted) // ["1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA"]

or using the method sort(by:) on a MutableCollection:

var array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
array.sort {$0.localizedStandardCompare($1) == .orderedAscending}

print(array) // ["1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA"]

You can also implement your own localized standard sort method extending Collection:

public extension Sequence where Element: StringProtocol {
     func localizedStandardSorted(ascending: Bool = true) -> [Element] {
        let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
        return sorted { $0.localizedStandardCompare($1) == result }
    }
}

let array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
let sorted = array.localizedStandardSorted()

print(sorted) // ["1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA"]

The mutating method as well extending MutableCollection:

public extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
    mutating func localizedStandardSort(ascending: Bool = true) {
        let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
        return sort { $0.localizedStandardCompare($1) == result }
    }
}

var array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
array.localizedStandardSort()

print(array) // ["1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA"]

If you need to sort your array numerically you can use String compare method setting the options parameter to .numeric:

public extension Sequence where Element: StringProtocol {
    func sortedNumerically(ascending: Bool = true) -> [Element] {
        let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
        return sorted { $0.compare($1, options: .numeric) == result }
    }
}

public extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
    mutating func sortNumerically(ascending: Bool = true) {
        let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
        return sort { $0.compare($1, options: .numeric) == result }
    }
}

var numbers = ["1.5","0.5","1"]
let sortedNumbers = numbers.sortedNumerically()
print(sortedNumbers)  // ["0.5", "1", "1.5"]
print(numbers) // ["1.5","0.5","1"]
// mutating the original collection
numbers.sortNumerically(ascending: false)
print(numbers)  // "["1.5", "1", "0.5"]\n"


To sort a custom class/structure by one of its properties:


extension MutableCollection where Self: RandomAccessCollection {
    public mutating func localizedStandardSort<T: StringProtocol>(_ predicate: (Element) -> T, ascending: Bool = true) {
        sort {
            predicate($0).localizedStandardCompare(predicate($1)) ==
                (ascending ? .orderedAscending : .orderedDescending)
        }
    }
}

public extension Sequence {
    func localizedStandardSorted<T: StringProtocol>(_ predicate: (Element) -> T, ascending: Bool = true) -> [Element] {
        sorted {
            predicate($0).localizedStandardCompare(predicate($1)) ==
                (ascending ? .orderedAscending : .orderedDescending)
        }
    }
}

public extension Sequence {
    func sortedNumerically<T: StringProtocol>(_ predicate: (Element) -> T, ascending: Bool = true) -> [Element] {
        let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
        return sorted { predicate($0).compare(predicate($1), options: .numeric) == result }
    }
}

public extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
    mutating func sortNumerically<T: StringProtocol>(_ predicate: (Element) -> T, ascending: Bool = true) {
        let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
        return sort { predicate($0).compare(predicate($1), options: .numeric) == result }
    }
}


Playground testing

struct Person {
    let name: String
    let age : Int
}

extension Person : CustomStringConvertible {
    var description: String { "name: \(name), age: \(age)" }
}

var people: [Person] = [.init(name: "Éd Sheeran", age: 26),
                        .init(name: "phil Collins", age: 66),
                        .init(name: "Shakira", age: 40),
                        .init(name: "rihanna", age: 25),
                        .init(name: "Bono", age: 57)]

let sorted = people.localizedStandardSorted(\.name)

print(sorted) // [name: Bono, age: 57, name: Éd Sheeran, age: 26, name: phil Collins, age: 66, name: rihanna, age: 25, name: Shakira, age: 40]

edit/update: Xcode 12 • Swift 5.5 or later

You can use KeyPathComparator and pass localizedStandard as the Comparator:

people.sort(using: KeyPathComparator(\.name, comparator: .localizedStandard))
print(people) // [name: Bono, age: 57, name: Éd Sheeran, age: 26, name: phil Collins, age: 66, name: rihanna, age: 25, name: Shakira, age: 40]
people.sort(using: KeyPathComparator(\.name, comparator: .localizedStandard, order: .reverse))
print(people) // "[name: Shakira, age: 40, name: rihanna, age: 25, name: phil Collins, age: 66, name: Éd Sheeran, age: 26, name: Bono, age: 57]"

For sorting just an array of strings you can also use KeyPathComparator and pass self for the KeyPath:

var array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
array.sort(using: KeyPathComparator(\.self, comparator: .localizedStandard))
array  // ["1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA"]
array.sort(using: KeyPathComparator(\.self, comparator: .localizedStandard, order: .reverse))
array  // ["NA", "NA", "210", "200", "101", "100", "20", "10", "7", "1"]

Leave a Comment