Как отсортировать массив строк
В Swift есть два способа сортировки: тот, который изменяет исходный массив, и тот, который этого не делает. У них обоих было одно и то же требование: элемент в коллекции должен соответствовать протоколу Comparable.
Типы, соответствующие протоколу Comparable, можно сравнивать с помощью операторов отношения <, <=, >= и >. Многие типы в стандартной библиотеке, включая String, соответствуют протоколу Comparable.
Изменяемая сортировка с sort и sort(by:)
sort() и sort(by:) — это функции сортировки, которые изменяют исходный массив. Чтобы использовать его, вызовите sort() для изменяемого значения массива.
var students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"] // <1>
students.sort()
print(students)
// Prints "["Abena", "Akosua", "Kofi", "Kweku", "Peter"]"
<1> Так как sort() является изменяемой функцией, вы должны объявить массив как var. В противном случае вы получите следующую ошибку.
Cannot use mutating member on immutable value: 'students' is a 'let' constant
Change 'let' to 'var' to make it mutable
sort() будет использовать оператор «меньше» (<) при сравнении элементов, что приведет к сортировке по возрастанию. Чтобы отсортировать в порядке убывания, вам нужно использовать метод sort(by:), который позволяет указать замыкание сравнения, которое возвращает true, если его первый аргумент должен быть упорядочен перед вторым аргументом.
var students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
students.sort { (lhs: String, rhs: String) -> Bool in
return lhs > rhs
}
// Prints "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]"
Неизменяемая сортировка с sort и sort(by:)
sorted() и sorted(by:) имеют ту же функциональность, что и sort() и sort(by:). Единственное отличие состоит в том, что они возвращают новые отсортированные элементы последовательности вместо модификации исходного массива.
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> Здесь можно использовать let, так как sorted() и sorted(by:) не изменяют исходный массив.
<2> Нам нужно объявить новую переменную для чтения отсортированного результата.
<3> Исходный массив не изменяется.
<4> sorted() возвращает новый отсортированный массив.
Как и sort(), sorted() использует оператор «меньше» (<) при сравнении элементов, что приводит к сортировке по возрастанию. Вам нужно использовать sort(by:), если вы хотите изменить порядок элементов.
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"]"
Функция сортировки проста. Вы выбираете метод сортировки в зависимости от того, хотите ли вы изменить массив или нет. Давайте рассмотрим необходимый порядок сортировки, который вы можете встретить в повседневной работе.
Как отсортировать массив по возрастанию
Вы можете использовать sort() или sorted() для сортировки строк в порядке возрастания.
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"]"
Как отсортировать массив по убыванию
Вы можете сортировать в порядке убывания, указав замыкание сортировки либо для sort(by:), либо для 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"]"
Как изменить порядок сортировки в Swift
Вы можете инвертировать элементы массива, используя reverse(), который изменяет исходный массив или неизменяемый вариант, 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"]"
Как отсортировать массив по алфавиту
Порядок возрастания может быть не тем порядком, который вы ищете при сортировке массива строк.
Вот пример массива имен, которые содержат как прописные, так и строчные буквы.
let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua", "abena", "bee"]
let sortedStudents = students.sorted()
print(sortedStudents)
// Prints "["Abena", "Akosua", "Kofi", "Kweku", "Peter", "abena", "bee"]"
Как видите, отсортированный результат чувствителен к регистру, где «Abena» и «abena» разделены. В этом случае вам нужно больше контроля над методами сравнения. К счастью, в Swift есть много встроенных методов сравнения.
caseInsensitiveCompare(_:) сравнивает две строки без учета регистра. Это краткая форма вызова compare(_:options:) с .caseInsensitive в качестве единственной опции.
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"]"
Вы также можете использовать compare(_:options:) с .caseInsensitive как единственную опцию, которая дает тот же результат.
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(_:) дает результат, который нам нужен для этого небольшого выборочного массива, но может быть неправильным методом для того, что нам нужно. На самом деле сортировка по имени или строке намного сложнее. В некоторых странах может использоваться диакритический знак, усложняющий сортировку.
Давайте посмотрим, что произойдет после того, как мы добавим «abenā» в массив наших учеников. Я использую «abenā» в качестве примера, чтобы показать случай с диакритическим знаком. Возможно, это неверное имя.
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"]"
Как видите, «abenā» отделяется от «abena» и «abena». Вы можете указать параметр .diacriticInsensitive, чтобы игнорировать диакритический знак, но вы можете видеть, что сложность увеличивается и увеличивается по мере того, как мы пытаемся обработать больше случаев.
let sortedStudents = files.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.compare(rhs, options: [.diacriticInsensitive, .caseInsensitive]) == .orderedAscending
}
Нужно ли нам знать все особенности языка, чтобы уметь их сортировать?
К счастью, нет. Что нам действительно нужно здесь, так это метод сравнения с учетом локали. Apple предоставила вам три метода сравнения с учетом региональных настроек. localizedCompare, localizedCaseInsensitiveCompare и 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
}
результат
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 |
Как видите, все три метода сравнения с учетом локали, localizedCompare, localizedCaseInsensitiveCompare и localizedStandardCompare, дают приемлемый результат сортировки.
Я могу привести вам пример того, насколько сложной может быть сортировка в других языках. В следующем примере представлен набор тайских имен.
sorted | compare | caseInsensitiveCompare | localizedCompare | localizedCaseInsensitiveCompare | localizedStandardCompare |
---|---|---|---|---|---|
มา | มา | มา | มา | มา | มา |
มี | มี | มี | มี | มี | มี |
มี่ | มี่ | มี่ | มี่ | มี่ | มี่ |
มี้ | มี้ | มี้ | มี้ | มี้ | มี้ |
มี๊ | มี๊ | มี๊ | มี๊ | มี๊ | มี๊ |
มี๋ | มี๋ | มี๋ | มี๋ | มี๋ | มี๋ |
หมก | หมก | หมก | เม | เม | เม |
หมี | หมี | หมี | แม | แม | แม |
หมี่ | หมี่ | หมี่ | หมก | หมก | หมก |
หมึก | หมึก | หมึก | หมี | หมี | หมี |
เม | เม | เม | หมี่ | หมี่ | หมี่ |
แม | แม | แม | หมึก | หมึก | หมึก |
Вы можете увидеть их как тарабарщину, но вы можете видеть в строках 7, 8 и 9, что существует разница в порядке между сравнением с учетом локали и без нее.
Существует три метода с учетом локали. Какой из них я должен использовать?
localizedStandardCompare — это то, что вы должны использовать. Причина в следующем разделе.
Как отсортировать имя файла или строку с номерами
Если ваша строка содержит числа, такие как Name2.txt, Name7.txt и Name25.txt, вы хотите, чтобы они сортировались следующим образом:
Name2.txt
Name7.txt
Name25.txt
а нет так:
Name2.txt
Name25.txt
Name7.txt
Здесь на помощь приходит localizedStandardCompare. localizedStandardCompare сравнивает строки так же, как это делает Finder. Который уже обрабатывает такого рода числовую сортировку.
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
}
Вы можете добиться аналогичного эффекта с помощью параметра compare(_:options:range:locale:) .numeric.
let custom = files.sorted { (lhs: String, rhs: String) -> Bool in
return lhs.compare(rhs, options: [.numeric], locale: .current) == .orderedAscending
}
Существует небольшая разница между localizedStandardCompare и compare(_:options:range:locale:) с опцией .numeric, как вы можете видеть в следующем результате (untitled01.txt и untitled1.txt).
Результат:
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 |
Вывод
Итак, вот правило большого пальца. Когда вам нужно отсортировать или работать с текстом, представленным пользователю, используйте localizedStandardCompare(_:). Он сравнивает ваши строки таким образом, который имеет смысл и является ожидаемым пользователями с учетом их региональных настроек. localizedStandardCompare(_:) даст вам тот же результат, что и System Finder. Так что его использование даст результат, к которому уже привыкли пользователи Apple.
Если у вас есть конкретная потребность, которая не соответствует тому, что предоставляет localizedStandardCompare(_:), вы можете использовать compare(_:options:range:locale:). Но, пожалуйста, убедитесь, что вы передаете языковой стандарт пользователя в качестве аргумента, чтобы получить функцию с учетом языкового стандарта.
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
}