В моей последней статье «Как сортировать по нескольким свойствам в Swift» мы узнаем, как сортировать массив объектов по нескольким критериям.
Окончательную реализацию можно резюмировать следующим образом.
Сортировка по нескольким критериям означает сортировку, при которой мы сравниваем первые критерии, и только если первый критерий равен, мы переходим к следующему. Мы делаем это до тех пор, пока не найдем неравный критерий.
Что можно перевести в псевдокод следующим образом:
let sortedObjects = objects.sorted { (lhs, rhs) in
for (lhsCriteria, rhsCriteria) in [(lhsCrtria1, rhsCriteria1), (lhsCrtria2, rhsCriteria2), (lhsCrtria3, rhsCriteria3), ... , (lhsCrtriaN, rhsCriteriaN)] {
if lhsCriteria == rhsCriteria {
continue
}
return lhsCriteria < rhsCriteria
}
}
Это включает в себя объявление массива предикатов и циклов, но мы можем удалить большую часть этой логики с помощью кортежа.
Сравнение кортежей
Swift имеет операторы сравнения, такие как <, >, <=, >= для нас, со следующей реализацией.
/// Returns a Boolean value indicating whether the first tuple is ordered
/// before the second in a lexicographical ordering.
///
/// Given two tuples `(a1, a2, ..., aN)` and `(b1, b2, ..., bN)`, the first
/// tuple is before the second tuple if and only if
/// `a1 < b1` or (`a1 == b1` and
/// `(a2, ..., aN) < (b2, ..., bN)`).
///
/// - Parameters:
/// - lhs: A tuple of `Comparable` elements.
/// - rhs: Another tuple of elements of the same type as `lhs`.
@inlinable public func < <A, B>(lhs: (A, B), rhs: (A, B)) -> Bool where A : Comparable, B : Comparable
Я хочу, чтобы вы сосредоточились на следующем предложении:
/// Given two tuples `(a1, a2, ..., aN)` and `(b1, b2, ..., bN)`, the first
/// tuple is before the second tuple if and only if
/// `a1 < b1` or (`a1 == b1` and
/// `(a2, ..., aN) < (b2, ..., bN)`).
Именно так мы реализуем нашу сортировку. Давайте сравним это с нашим псевдокодом. Вы можете видеть, что сравнение кортежей — это то, как мы сравниваем наши критерии сортировки.
let sortedObjects = objects.sorted { (lhs, rhs) in
for (lhsCriteria, rhsCriteria) in [(lhsCrtria1, rhsCriteria1), (lhsCrtria2, rhsCriteria2), (lhsCrtria3, rhsCriteria3), ... , (lhsCrtriaN, rhsCriteriaN)] {
if lhsCriteria == rhsCriteria {
continue
}
return lhsCriteria < rhsCriteria
}
}
Реализуем сортировку критериев по кортежу.
Пример
Мы будем использовать тот же объект, что и в нашем последнем примере.
struct BlogPost {
let title: String
let pageView: Int
let sessionDuration: Double
}
По возрастанию
Чтобы отсортировать по возрастанию, мы используем оператор меньше чем (<) над кортежами.
let posts: [BlogPost] = [
BlogPost(title: "Zoo", pageView: 5, sessionDuration: 1),
BlogPost(title: "Alice", pageView: 1, sessionDuration: 3),
BlogPost(title: "Alice", pageView: 1, sessionDuration: 2),
BlogPost(title: "Alice", pageView: 2, sessionDuration: 1),
BlogPost(title: "Zoo", pageView: 4, sessionDuration: 2),
BlogPost(title: "Abena", pageView: 4, sessionDuration: 10),
BlogPost(title: "Angero", pageView: 1, sessionDuration: 2)
]
let sorted = posts.sorted { (lhs, rhs) -> Bool in
return (lhs.title, lhs.pageView, lhs.sessionDuration) < (rhs.title, rhs.pageView, rhs.sessionDuration)
}
print(sorted)
Вот результат:
[BlogPost(title: "Abena", pageView: 4, sessionDuration: 10.0),
BlogPost(title: "Alice", pageView: 1, sessionDuration: 2.0),
BlogPost(title: "Alice", pageView: 1, sessionDuration: 3.0),
BlogPost(title: "Alice", pageView: 2, sessionDuration: 1.0),
BlogPost(title: "Angero", pageView: 1, sessionDuration: 2.0),
BlogPost(title: "Zoo", pageView: 4, sessionDuration: 2.0),
BlogPost(title: "Zoo", pageView: 5, sessionDuration: 1.0)]
Это сравнит первый элемент lhs.title < rhs.title и если lhs.title == rhs.title, мы сравним (lhs.pageView, lhs.sessionDuration) < (rhs.pageView, rhs.sessionDuration). Это идентично нашей предыдущей реализации.
В порядке убывания
Чтобы отсортировать по убыванию, мы используем оператор больше, чем (>) над кортежами.
let posts: [BlogPost] = [
BlogPost(title: "Zoo", pageView: 5, sessionDuration: 1),
BlogPost(title: "Alice", pageView: 1, sessionDuration: 3),
BlogPost(title: "Alice", pageView: 1, sessionDuration: 2),
BlogPost(title: "Alice", pageView: 2, sessionDuration: 1),
BlogPost(title: "Zoo", pageView: 4, sessionDuration: 2),
BlogPost(title: "Abena", pageView: 4, sessionDuration: 10),
BlogPost(title: "Angero", pageView: 1, sessionDuration: 2)
]
let sorted = posts.sorted { (lhs, rhs) -> Bool in
return (lhs.title, lhs.pageView, lhs.sessionDuration) > (rhs.title, rhs.pageView, rhs.sessionDuration)
}
print(sorted)
Вот результат:
[BlogPost(title: "Zoo", pageView: 5, sessionDuration: 1.0),
BlogPost(title: "Zoo", pageView: 4, sessionDuration: 2.0),
BlogPost(title: "Angero", pageView: 1, sessionDuration: 2.0),
BlogPost(title: "Alice", pageView: 2, sessionDuration: 1.0),
BlogPost(title: "Alice", pageView: 1, sessionDuration: 3.0),
BlogPost(title: "Alice", pageView: 1, sessionDuration: 2.0),
BlogPost(title: "Abena", pageView: 4, sessionDuration: 10.0)]
Разный порядок сортировки для каждого элемента
Если вам нужны разные порядки сортировки для каждого свойства, вы можете сделать это, поменяв местами элементы в кортежах.
Мы не можем изменить оператор для каждого элемента в кортежах, но изменение его позиции приведет к тому же эффекту. Рассмотрим следующие примеры.
lhs.property1 < rhs.proerty1 будет сортироваться в порядке возрастания.
lhs.property2 > rhs.proerty2 будет сортироваться в порядке убывания, но все элементы должны сравниваться с использованием одного и того же оператора. Итак, меняем местами.
rhs.property2 < lhs.property2 равно lhs.property2 > rhs.property2, который сортируется по убыванию.
Итак, если мы хотим отсортировать title по возрастанию, а pageView и sessionDuration по убыванию, мы можем использовать следующий код.
let posts: [BlogPost] = [
BlogPost(title: "Zoo", pageView: 5, sessionDuration: 1),
BlogPost(title: "Alice", pageView: 1, sessionDuration: 3),
BlogPost(title: "Alice", pageView: 1, sessionDuration: 2),
BlogPost(title: "Alice", pageView: 2, sessionDuration: 1),
BlogPost(title: "Zoo", pageView: 4, sessionDuration: 2),
BlogPost(title: "Abena", pageView: 4, sessionDuration: 10),
BlogPost(title: "Angero", pageView: 1, sessionDuration: 2)
]
let sorted = posts.sorted { (lhs, rhs) -> Bool in
return (lhs.title, rhs.pageView, rhs.sessionDuration) < (rhs.title, lhs.pageView, lhs.sessionDuration)
}
print(sorted)
Предостережения
Мы можем сравнивать только до шести свойств, используя кортежи. Swift только перегружает сравнение кортежей до шести элементов.
/// Returns a Boolean value indicating whether the first tuple is ordered
/// after or the same as the second in a lexicographical ordering.
///
/// Given two tuples `(a1, a2, ..., aN)` and `(b1, b2, ..., bN)`, the first
/// tuple is after or the same as the second tuple if and only if
/// `a1 > b1` or (`a1 == b1` and
/// `(a2, ..., aN) >= (b2, ..., bN)`).
///
/// - Parameters:
/// - lhs: A tuple of `Comparable` elements.
/// - rhs: Another tuple of elements of the same type as `lhs`.
@inlinable public func >= <A, B, C, D, E, F>(lhs: (A, B, C, D, E, F), rhs: (A, B, C, D, E, F)) -> Bool where A : Comparable, B : Comparable, C : Comparable, D : Comparable, E : Comparable, F : Comparable
Я бы сказал, что шести свойств должно быть достаточно для большинства случаев, но если это не так, вы можете вернуться к моей последней реализации.