Different ways to sort an array of strings in Swift

⋅ 12 min read ⋅ Swift String Sort Array

Table of Contents

How to sort an array of strings

There are two ways to sort in Swift, The one that mutates the original array and the one that don't. Both of them shared the same requirement: the element in the collection must conform to Comparable protocol.

Types that conform to Comparable protocol can be compared using the relational operators <, <=, >=, and >. Many types in the standard library, including String, are conform to the Comparable protocol.

Mutating sort with sort and sort(by:)

sort() and sort(by:) are sort functions that mutate the original array. To use it, call sort() over a mutable array value.

var students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"] // <1>
students.sort()
print(students)
// Prints "["Abena", "Akosua", "Kofi", "Kweku", "Peter"]"

<1> Since sort() is mutating function, you have to declare array as var. Otherwise, you will get the following error.

Cannot use mutating member on immutable value: 'students' is a 'let' constant

Change 'let' to 'var' to make it mutable

sort() will use the less-than operator (<) when making a comparison among elements, which result in ascending sort order. To sort in descending order, you need to use sort(by:) method, which allows you to specify a comparison closure that returns true if its first argument should be ordered before its second argument.

var students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
students.sort { (lhs: String, rhs: String) -> Bool in
return lhs > rhs
}
// Prints "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]"

Immutable sort with sorted and sorted(by:)

sorted() and sorted(by:) has the same functionality as sort() and sort(by:). The only difference is that they return the new sorted elements of the sequence instead of modifying the original array.

let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"] // <1>
let sortedStudents = students.sorted() // <2>
print(students)
// Prints "["Kofi", "Abena", "Peter", "Kweku", "Akosua"]" <3>

print(sortedStudents)
// Prints "["Abena", "Akosua", "Kofi", "Kweku", "Peter"]" <4>

<1> We can use let here since sorted() and sorted(by:) don't mutate the original array.
<2> We need to declare a new variable to read a sorted result.
<3> The original array doesn't change.
<4> sorted() return a new sorted array.

Like sort(), sorted() use the less-than operator (<) when making a comparison among elements, which result in ascending sort order. You need sorted(by:) if you want to order it differently.

let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
let sortedStudents = students.sorted { (lhs: String, rhs: String) -> Bool in
return lhs > rhs
}
print(sortedStudents)
// Prints "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]"

The sort function is straightforward. You pick the sort method depending on whether you want to mutate the array or not. Let's go through some necessary sort ordering that you might find in day to day job.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

How to sort array in ascending order

You can use either sort() or sorted() to sort strings in ascending order.

var students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
let sortedStudents = students.sorted()
students.sort()
print(students)
// Prints "["Abena", "Akosua", "Kofi", "Kweku", "Peter"]"

print(sortedStudents)
// Prints "["Abena", "Akosua", "Kofi", "Kweku", "Peter"]"

How to sort array in descending order

You can sort in descending order by specify sorting closure to either sort(by:) or sorted(by:).

var students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
let sortedStudents = students.sorted { (lhs: String, rhs: String) -> Bool in
return lhs > rhs
}
students.sort { (lhs: String, rhs: String) -> Bool in
return lhs > rhs
}
print(students)
// Prints "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]"

print(sortedStudents)
// Prints "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]"

How to reverse sort order in Swift

You can reverse the array elements using reverse() that mutate the original array or immutable variant, reversed().

let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
let ascStudents = students.sorted()
let dscStudents = Array(ascStudents.reversed())
print(ascStudents)
// Prints "["Abena", "Akosua", "Kofi", "Kweku", "Peter"]"

print(dscStudents)
// Prints "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]"

How to sort array alphabetically

Ascending order might not be the order you seek when sorting an array of strings.

Here's an example array of name which contain in both capital case and lower case.

let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua", "abena", "bee"]
let sortedStudents = students.sorted()

print(sortedStudents)
// Prints "["Abena", "Akosua", "Kofi", "Kweku", "Peter", "abena", "bee"]"

As you can see, the sorted result is case sensitive where "Abena" and "abena" are separate apart. In this case, you need more control over comparing methods. Luckily Swift has many build-in comparison methods for you.

caseInsensitiveCompare(_:) will compare two strings ignoring case. This is a shortform of calling compare(_:options:) with .caseInsensitive as the only option.

let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua", "abena", "bee"]
let sortedStudents = students.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.caseInsensitiveCompare(rhs) == .orderedAscending
}
print(sortedStudents)
// Prints "["Abena", "abena", "Akosua", "bee", "Kofi", "Kweku", "Peter"]"

You can also use compare(_:options:) with .caseInsensitive as the only option which yields the same result.

let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua", "abena", "bee"]
let sortedStudents = students.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.compare(rhs, options: .caseInsensitive) == .orderedAscending
}
print(sortedStudents)
// Prints "["Abena", "abena", "Akosua", "bee", "Kofi", "Kweku", "Peter"]"

caseInsensitiveCompare(_:) gives the result we want for this small sample array, but might not be the right method for what we need. In reality, name or string sort is far more complicated than this. In some countries, there might be a use of a diacritic mark, which makes sorting harder.

Let's see what will happen after we add "ábenā" to our students' array. I use "ábenā" as an example to show a case with a diacritic. It might not be a valid name.

let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua", "abena", "bee", "ábenā"]
let sortedStudents = students.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.caseInsensitiveCompare(rhs) == .orderedAscending
}
print(sortedStudents)
// Prints "["Abena", "abena", "Akosua", "ábenā", "bee", "Kofi", "Kweku", "Peter"]"

As you can see, "ábenā" gets separated from "Abena" and "abena". You can specify .diacriticInsensitive option to ignore the diacritic mark, but you can see that the complexity increases and increases as we try to handle more cases.

let sortedStudents = files.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.compare(rhs, options: [.diacriticInsensitive, .caseInsensitive]) == .orderedAscending
}

Do we have to know all language features to be able to sort them?

Luckily, the answer is no. What we really need here is a locale-aware comparison method. Apple provided three locale-aware comparison methods for you. localizedCompare, localizedCaseInsensitiveCompare, and localizedStandardCompare.

let files = ["Kofi", "Abena", "Peter", "Kweku", "Akosua", "abena", "bee", "ábenā"]
let sorted = files.sorted()
let compare = files.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.compare(rhs) == .orderedAscending
}
let caseInsensitiveCompare = files.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.caseInsensitiveCompare(rhs) == .orderedAscending
}

let localizedCompare = files.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.localizedCompare(rhs) == .orderedAscending
}
let localizedCaseInsensitiveCompare = files.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.localizedCaseInsensitiveCompare(rhs) == .orderedAscending
}
let localizedStandardCompare = files.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.localizedStandardCompare(rhs) == .orderedAscending
}

Result:

sorted compare caseInsensitiveCompare localizedCompare localizedCaseInsensitiveCompare localizedStandardCompare
Abena Abena Abena abena Abena abena
Akosua Akosua abena Abena abena Abena
Kofi Kofi Akosua ábenā ábenā ábenā
Kweku Kweku ábenā Akosua Akosua Akosua
Peter Peter bee bee bee bee
abena abena Kofi Kofi Kofi Kofi
bee ábenā Kweku Kweku Kweku Kweku
ábenā bee Peter Peter Peter Peter

As you can see, all three locale-aware comparison methods, localizedCompare, localizedCaseInsensitiveCompare, and localizedStandardCompare giving a reasonable sort result.

There are three locale-aware methods. Which one should I use?

localizedStandardCompare is the one that you should use. The reason is in the next section.

How to sort file name or string with numbers

If your string contains a number such as Name2.txt, Name7.txt, and Name25.txt, you want it to sort like this:

Name2.txt
Name7.txt
Name25.txt

Not this:

Name2.txt
Name25.txt
Name7.txt

This is where localizedStandardCompare comes to the rescue. localizedStandardCompare compares strings the same way that Finder does. Which already handles this kind of numeric sorting.

let files = ["Design final.psd", "untitled01.txt", "untitled1.txt", "design final.psd", "design final(2).psd", "design final final.psd", "design final2.psd", "design final12.psd", "untitled2.txt", "untitled21.txt", "untitled11.txt", "untitled012.txt", "untitled.txt", "Design Final2.psd", "DESIGN final final.psd", "untitled12.psd"]

let sortedFiles = files.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.localizedStandardCompare(rhs) == .orderedAscending
}

You can achieve similar effect with compare(_:options:range:locale:) passing .numeric option.

let custom = files.sorted { (lhs: String, rhs: String) -> Bool in    
return lhs.compare(rhs, options: [.numeric], locale: .current) == .orderedAscending
}

There is minor different between localizedStandardCompare and compare(_:options:range:locale:) with .numeric option as you can see in the following result (untitled01.txt and untitled1.txt).

Result:

caseInsensitiveCompare localizedCompare localizedCaseInsensitiveCompare localizedStandardCompare custom
design final final.psd design final final.psd design final final.psd design final final.psd design final final.psd
DESIGN final final.psd DESIGN final final.psd DESIGN final final.psd DESIGN final final.psd DESIGN final final.psd
design final(2).psd design final.psd Design final.psd design final.psd design final.psd
Design final.psd Design final.psd design final.psd Design final.psd Design final.psd
design final.psd design final(2).psd design final(2).psd design final(2).psd design final(2).psd
design final12.psd design final12.psd design final12.psd design final2.psd design final2.psd
design final2.psd design final2.psd design final2.psd Design Final2.psd Design Final2.psd
Design Final2.psd Design Final2.psd Design Final2.psd design final12.psd design final12.psd
untitled.txt untitled.txt untitled.txt untitled.txt untitled.txt
untitled01.txt untitled01.txt untitled01.txt untitled1.txt untitled01.txt
untitled012.txt untitled012.txt untitled012.txt untitled01.txt untitled1.txt
untitled1.txt untitled1.txt untitled1.txt untitled2.txt untitled2.txt
untitled11.txt untitled11.txt untitled11.txt untitled11.txt untitled11.txt
untitled12.psd untitled12.psd untitled12.psd untitled12.psd untitled12.psd
untitled2.txt untitled2.txt untitled2.txt untitled012.txt untitled012.txt
untitled21.txt untitled21.txt untitled21.txt untitled21.txt untitled21.txt

Conclusion

So here is the rule of thumbs. When you need to sort or work with text presented to the user, use the localizedStandardCompare(_:). It compares your strings in a way that makes sense and is expected by users respecting their locale. localizedStandardCompare(_:) will give you the same result as System Finder does. So, using it will give a result that Apple users are already accustomed to.

If you have a particular need that doesn't match what localizedStandardCompare(_:) provided, you can use compare(_:options:range:locale:). But please make sure you pass the user’s locale as an argument to get a locale-aware feature.

let custom = files.sorted { (lhs: String, rhs: String) -> Bool in
let options: String.CompareOptions = [] // customize options
return lhs.compare(rhs, options: options, locale: Locale.current) == .orderedAscending
}

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Apple Documentation


Read more article about Swift, String, Sort, Array, or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Buy me a coffee Tweet Share
Previous
How to specify fractional digits for formatted number string in Swift

Learn how to format a Float and Double string.

Next
2020 Review

A look back at 2020.

← Home