Different ways to sort an array of strings in Swift
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.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
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.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
Related Resources
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 ShareHow to specify fractional digits for formatted number string in Swift
Learn how to format a Float and Double string.