[swift] Swift-여러 기준으로 객체 배열 정렬
Contact
개체 배열이 있습니다.
var contacts:[Contact] = [Contact]()
연락 클래스 :
Class Contact:NSOBject {
var firstName:String!
var lastName:String!
}
그리고 일부 연락처가 동일한 경우를 대비 lastName
하여 배열을 정렬하고 싶습니다 .firstName
lastName
해당 기준 중 하나를 기준으로 정렬 할 수 있지만 둘다는 아닙니다.
contacts.sortInPlace({$0.lastName < $1.lastName})
이 배열을 정렬하기 위해 더 많은 기준을 추가하려면 어떻게해야합니까?
답변
“여러 기준으로 정렬”이 무엇을 의미하는지 생각해보십시오. 두 개체가 먼저 하나의 기준으로 비교됨을 의미합니다. 그런 다음 해당 기준이 동일하면 원하는 순서를 얻을 때까지 다음 기준에 따라 동점이 끊어집니다.
let sortedContacts = contacts.sort {
if $0.lastName != $1.lastName { // first, compare by last names
return $0.lastName < $1.lastName
}
/* last names are the same, break ties by foo
else if $0.foo != $1.foo {
return $0.foo < $1.foo
}
... repeat for all other fields in the sorting
*/
else { // All other fields are tied, break ties by last name
return $0.firstName < $1.firstName
}
}
여기서보고있는 것은 요소를 비교하는 방법을 결정하기 위해 제공된 클로저를 참조 하는 Sequence.sorted(by:)
method 입니다.
정렬이 여러 곳에서 사용되는 경우 유형이 Comparable
프로토콜을 준수하도록하는 것이 더 나을 수 있습니다 . 이렇게하면 요소를 비교하는 방법을 결정하기 위해 연산자 구현을 참조하는 Sequence.sorted()
method 를 사용할 수 있습니다 . 이런 식으로, 당신은 정렬 할 수 의 적 분류 코드를 복제하지 않고도들.Comparable.<(_:_:)
Sequence
Contact
답변
튜플을 사용하여 여러 기준 비교
여러 기준에 의해 정렬을 수행하는 정말 간단한 방법 (하나 개의 비교에 의해 즉, 정렬, 다른 비교하여 다음과 경우에 해당)을 사용하는 것입니다 튜플 은 AS, <
그리고 >
운영자가 사전 식 비교를 수행 그들에게 과부하가 있습니다.
/// 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)`).
public func < <A : Comparable, B : Comparable>(lhs: (A, B), rhs: (A, B)) -> Bool
예를 들면 :
struct Contact {
var firstName: String
var lastName: String
}
var contacts = [
Contact(firstName: "Leonard", lastName: "Charleson"),
Contact(firstName: "Michael", lastName: "Webb"),
Contact(firstName: "Charles", lastName: "Alexson"),
Contact(firstName: "Michael", lastName: "Elexson"),
Contact(firstName: "Alex", lastName: "Elexson"),
]
contacts.sort {
($0.lastName, $0.firstName) <
($1.lastName, $1.firstName)
}
print(contacts)
// [
// Contact(firstName: "Charles", lastName: "Alexson"),
// Contact(firstName: "Leonard", lastName: "Charleson"),
// Contact(firstName: "Alex", lastName: "Elexson"),
// Contact(firstName: "Michael", lastName: "Elexson"),
// Contact(firstName: "Michael", lastName: "Webb")
// ]
이것은 lastName
먼저 요소의 속성 을 비교합니다 . 같지 않으면 정렬 순서는 <
비교를 기반으로 합니다. 그들이 경우 이다 동일, 그것은, 튜플의 요소의 다음 쌍에 이동 비교 즉 것이다 firstName
속성을.
표준 라이브러리는 2 ~ 6 개 요소의 튜플을 제공 <
하고 >
오버로드합니다.
다른 속성에 대해 다른 정렬 순서를 원하면 튜플의 요소를 간단히 바꿀 수 있습니다.
contacts.sort {
($1.lastName, $0.firstName) <
($0.lastName, $1.firstName)
}
// [
// Contact(firstName: "Michael", lastName: "Webb")
// Contact(firstName: "Alex", lastName: "Elexson"),
// Contact(firstName: "Michael", lastName: "Elexson"),
// Contact(firstName: "Leonard", lastName: "Charleson"),
// Contact(firstName: "Charles", lastName: "Alexson"),
// ]
이제 lastName
내림차순, firstName
오름차순으로 정렬됩니다 .
sort(by:)
여러 술어를 사용 하는 오버로드 정의
에 대한 논의에서 영감 과 정렬 컬렉션 map
폐쇄 및 SortDescriptors , 또 다른 옵션은 사용자 지정 과부하 정의하는 것입니다 sort(by:)
및 sorted(by:)
각 술어가 요소의 순서를 결정 차례로 간주됩니다 – 여러 술어와 그 거래를.
extension MutableCollection where Self : RandomAccessCollection {
mutating func sort(
by firstPredicate: (Element, Element) -> Bool,
_ secondPredicate: (Element, Element) -> Bool,
_ otherPredicates: ((Element, Element) -> Bool)...
) {
sort(by:) { lhs, rhs in
if firstPredicate(lhs, rhs) { return true }
if firstPredicate(rhs, lhs) { return false }
if secondPredicate(lhs, rhs) { return true }
if secondPredicate(rhs, lhs) { return false }
for predicate in otherPredicates {
if predicate(lhs, rhs) { return true }
if predicate(rhs, lhs) { return false }
}
return false
}
}
}
extension Sequence {
mutating func sorted(
by firstPredicate: (Element, Element) -> Bool,
_ secondPredicate: (Element, Element) -> Bool,
_ otherPredicates: ((Element, Element) -> Bool)...
) -> [Element] {
return sorted(by:) { lhs, rhs in
if firstPredicate(lhs, rhs) { return true }
if firstPredicate(rhs, lhs) { return false }
if secondPredicate(lhs, rhs) { return true }
if secondPredicate(rhs, lhs) { return false }
for predicate in otherPredicates {
if predicate(lhs, rhs) { return true }
if predicate(rhs, lhs) { return false }
}
return false
}
}
}
( secondPredicate:
매개 변수는 유감 스럽지만 기존 sort(by:)
과부하로 인한 모호성을 피하기 위해 필요합니다 )
그러면 다음과 같이 말할 수 있습니다 ( contacts
이전 배열 사용 ).
contacts.sort(by:
{ $0.lastName > $1.lastName }, // first sort by lastName descending
{ $0.firstName < $1.firstName } // ... then firstName ascending
// ...
)
print(contacts)
// [
// Contact(firstName: "Michael", lastName: "Webb")
// Contact(firstName: "Alex", lastName: "Elexson"),
// Contact(firstName: "Michael", lastName: "Elexson"),
// Contact(firstName: "Leonard", lastName: "Charleson"),
// Contact(firstName: "Charles", lastName: "Alexson"),
// ]
// or with sorted(by:)...
let sortedContacts = contacts.sorted(by:
{ $0.lastName > $1.lastName }, // first sort by lastName descending
{ $0.firstName < $1.firstName } // ... then firstName ascending
// ...
)
호출 사이트가 튜플 변형만큼 간결하지는 않지만 비교되는 항목과 순서에 대한 추가 명확성을 얻을 수 있습니다.
준수 Comparable
당신이 같이 정기적으로 다음 비교 이러한 종류의 일을 할 거라면 @AMomchilov 및 @appzYourLife이 제안, 당신은 준수 할 수 있습니다 Contact
합니다 Comparable
:
extension Contact : Comparable {
static func == (lhs: Contact, rhs: Contact) -> Bool {
return (lhs.firstName, lhs.lastName) ==
(rhs.firstName, rhs.lastName)
}
static func < (lhs: Contact, rhs: Contact) -> Bool {
return (lhs.lastName, lhs.firstName) <
(rhs.lastName, rhs.firstName)
}
}
이제 sort()
오름차순을 요청하십시오.
contacts.sort()
또는 sort(by: >)
내림차순 :
contacts.sort(by: >)
중첩 유형에서 사용자 정의 정렬 순서 정의
사용하려는 다른 정렬 순서가있는 경우 중첩 유형으로 정의 할 수 있습니다.
extension Contact {
enum Comparison {
static let firstLastAscending: (Contact, Contact) -> Bool = {
return ($0.firstName, $0.lastName) <
($1.firstName, $1.lastName)
}
}
}
다음으로 간단히 호출하십시오.
contacts.sort(by: Contact.Comparison.firstLastAscending)
답변
두 가지 기준으로 정렬하는 또 다른 간단한 방법은 다음과 같습니다.
첫 번째 필드를 확인합니다.이 경우 lastName
같지 않은 경우 정렬 기준 lastName
, 동일한 경우 lastName
두 번째 필드 기준 정렬 (이 경우) firstName
입니다.
contacts.sort { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName }
답변
사전 식 정렬이 @Hamish에서 설명한대로 수행 할 수없는 한 가지는 다른 정렬 방향을 처리하는 것입니다 (예 : 내림차순, 다음 필드 오름차순 등).
저는 Swift 3에서이 작업을 수행하는 방법에 대한 블로그 게시물을 작성하고 코드를 간단하고 읽기 쉽게 유지했습니다.
여기에서 찾을 수 있습니다.
여기에서 코드가있는 GitHub 저장소를 찾을 수도 있습니다.
https://github.com/jallauca/SortByMultipleFieldsSwift.playground
요점, 예를 들어 위치 목록이 있으면 다음을 수행 할 수 있습니다.
struct Location {
var city: String
var county: String
var state: String
}
var locations: [Location] {
return [
Location(city: "Dania Beach", county: "Broward", state: "Florida"),
Location(city: "Fort Lauderdale", county: "Broward", state: "Florida"),
Location(city: "Hallandale Beach", county: "Broward", state: "Florida"),
Location(city: "Delray Beach", county: "Palm Beach", state: "Florida"),
Location(city: "West Palm Beach", county: "Palm Beach", state: "Florida"),
Location(city: "Savannah", county: "Chatham", state: "Georgia"),
Location(city: "Richmond Hill", county: "Bryan", state: "Georgia"),
Location(city: "St. Marys", county: "Camden", state: "Georgia"),
Location(city: "Kingsland", county: "Camden", state: "Georgia"),
]
}
let sortedLocations =
locations
.sorted(by:
ComparisonResult.flip <<< Location.stateCompare,
Location.countyCompare,
Location.cityCompare
)
답변
이 질문에는 이미 많은 훌륭한 답변이 있지만 기사 -Sort Descriptors in Swift 를 가리키고 싶습니다 . 여러 기준 정렬을 수행하는 방법에는 여러 가지가 있습니다.
-
NSSortDescriptor를 사용하면이 방법에는 몇 가지 제한이 있습니다. 객체는 클래스 여야하며 NSObject에서 상속해야합니다.
class Person: NSObject { var first: String var last: String var yearOfBirth: Int init(first: String, last: String, yearOfBirth: Int) { self.first = first self.last = last self.yearOfBirth = yearOfBirth } override var description: String { get { return "\(self.last) \(self.first) (\(self.yearOfBirth))" } } } let people = [ Person(first: "Jo", last: "Smith", yearOfBirth: 1970), Person(first: "Joe", last: "Smith", yearOfBirth: 1970), Person(first: "Joe", last: "Smyth", yearOfBirth: 1970), Person(first: "Joanne", last: "smith", yearOfBirth: 1985), Person(first: "Joanne", last: "smith", yearOfBirth: 1970), Person(first: "Robert", last: "Jones", yearOfBirth: 1970), ]
예를 들어, 성, 이름, 마지막으로 출생 연도 순으로 정렬하려고합니다. 그리고 우리는 대소 문자를 구분하지 않고 사용자의 로케일을 사용하기를 원합니다.
let lastDescriptor = NSSortDescriptor(key: "last", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:))) let firstDescriptor = NSSortDescriptor(key: "first", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:))) let yearDescriptor = NSSortDescriptor(key: "yearOfBirth", ascending: true) (people as NSArray).sortedArray(using: [lastDescriptor, firstDescriptor, yearDescriptor]) // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1970), Joanne smith (1985), Joe Smith (1970), Joe Smyth (1970)]
-
성 / 이름으로 Swift 정렬 방식 사용. 이 방법은 클래스 / 구조체 모두에서 작동합니다. 그러나 여기서는 yearOfBirth를 기준으로 정렬하지 않습니다.
let sortedPeople = people.sorted { p0, p1 in let left = [p0.last, p0.first] let right = [p1.last, p1.first] return left.lexicographicallyPrecedes(right) { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } } sortedPeople // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1985), Joanne smith (1970), Joe Smith (1970), Joe Smyth (1970)]
-
NSSortDescriptor를 유도하는 신속한 방법. 이것은 ‘함수는 일류 유형’이라는 개념을 사용합니다. SortDescriptor는 함수 유형이며 두 값을 취하고 bool을 반환합니다. sortByFirstName이라고하면 두 개의 매개 변수 ($ 0, $ 1)를 가져 와서 이름을 비교합니다. 결합 함수는 여러 SortDescriptor를 가져 와서 모두 비교하고 주문합니다.
typealias SortDescriptor<Value> = (Value, Value) -> Bool let sortByFirstName: SortDescriptor<Person> = { $0.first.localizedCaseInsensitiveCompare($1.first) == .orderedAscending } let sortByYear: SortDescriptor<Person> = { $0.yearOfBirth < $1.yearOfBirth } let sortByLastName: SortDescriptor<Person> = { $0.last.localizedCaseInsensitiveCompare($1.last) == .orderedAscending } func combine<Value> (sortDescriptors: [SortDescriptor<Value>]) -> SortDescriptor<Value> { return { lhs, rhs in for isOrderedBefore in sortDescriptors { if isOrderedBefore(lhs,rhs) { return true } if isOrderedBefore(rhs,lhs) { return false } } return false } } let combined: SortDescriptor<Person> = combine( sortDescriptors: [sortByLastName,sortByFirstName,sortByYear] ) people.sorted(by: combined) // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1970), Joanne smith (1985), Joe Smith (1970), Joe Smyth (1970)]
struct와 class 모두에서 사용할 수 있기 때문에 좋습니다. 심지어 nil과 비교하기 위해 확장 할 수도 있습니다.
그래도 원본 기사를 읽는 것이 좋습니다. 훨씬 더 자세한 내용과 잘 설명되어 있습니다.
답변
추가 코드가 필요하지 않기 때문에 Hamish의 튜플 솔루션을 사용하는 것이 좋습니다 .
if
명령문 처럼 작동 하지만 분기 논리를 단순화하는 것을 원할 경우이 솔루션을 사용하여 다음을 수행 할 수 있습니다.
animals.sort {
return comparisons(
compare($0.family, $1.family, ascending: false),
compare($0.name, $1.name))
}
이를 수행 할 수있는 기능은 다음과 같습니다.
func compare<C: Comparable>(_ value1Closure: @autoclosure @escaping () -> C, _ value2Closure: @autoclosure @escaping () -> C, ascending: Bool = true) -> () -> ComparisonResult {
return {
let value1 = value1Closure()
let value2 = value2Closure()
if value1 == value2 {
return .orderedSame
} else if ascending {
return value1 < value2 ? .orderedAscending : .orderedDescending
} else {
return value1 > value2 ? .orderedAscending : .orderedDescending
}
}
}
func comparisons(_ comparisons: (() -> ComparisonResult)...) -> Bool {
for comparison in comparisons {
switch comparison() {
case .orderedSame:
continue // go on to the next property
case .orderedAscending:
return true
case .orderedDescending:
return false
}
}
return false // all of them were equal
}
테스트하려면 다음 추가 코드를 사용할 수 있습니다.
enum Family: Int, Comparable {
case bird
case cat
case dog
var short: String {
switch self {
case .bird: return "B"
case .cat: return "C"
case .dog: return "D"
}
}
public static func <(lhs: Family, rhs: Family) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
struct Animal: CustomDebugStringConvertible {
let name: String
let family: Family
public var debugDescription: String {
return "\(name) (\(family.short))"
}
}
let animals = [
Animal(name: "Leopard", family: .cat),
Animal(name: "Wolf", family: .dog),
Animal(name: "Tiger", family: .cat),
Animal(name: "Eagle", family: .bird),
Animal(name: "Cheetah", family: .cat),
Animal(name: "Hawk", family: .bird),
Animal(name: "Puma", family: .cat),
Animal(name: "Dalmatian", family: .dog),
Animal(name: "Lion", family: .cat),
]
Jamie의 솔루션 과의 주요 차이점 은 속성에 대한 액세스가 클래스의 정적 / 인스턴스 메서드가 아닌 인라인으로 정의된다는 것입니다. 예 : $0.family
대신 Animal.familyCompare
. 그리고 오름차순 / 내림차순은 오버로드 된 연산자 대신 매개 변수에 의해 제어됩니다. 제이미의 솔루션은 내 솔루션이 내장 사용하는 반면 배열에 확장자를 추가 sort
/ sorted
방법 만 정의 할 수있는 두 개의 추가 것들을 필요 compare
하고 comparisons
.
완전성을 위해 내 솔루션이 Hamish의 튜플 솔루션 과 비교되는 방법은 다음과 같습니다 . 설명하기 위해 (name, address, profileViews)
Hamish의 솔루션으로 사람을 정렬하려는 야생 예제를 사용 하여 비교가 시작되기 전에 6 개의 속성 값 각각을 정확히 한 번 평가합니다. 이것은 원하지 않을 수도 있고 원하지 않을 수도 있습니다. 예를 들어, profileViews
비용이 많이 드는 네트워크 호출 이라고 가정하면 profileViews
꼭 필요한 경우가 아니면 호출을 피하고 싶을 수 있습니다 . 내 솔루션은 및 profileViews
까지 평가하지 않습니다 . 그러나 평가할 때 한 번 이상 평가할 가능성이 높습니다.$0.name == $1.name
$0.address == $1.address
profileViews
답변
어때 :
contacts.sort() { [$0.last, $0.first].lexicographicalCompare([$1.last, $1.first]) }