Swift - 여러 기준을 가진 객체 배열 정렬
나는 여러 가지를 가지고 있습니다.Contact
체:
var contacts:[Contact] = [Contact]()
연락처 클래스:
Class Contact:NSOBject {
var firstName:String!
var lastName:String!
}
을 별로 .lastName
그리고 firstName
연락처들이 연락처를 얻었을 lastName
.
저는 그 기준들 중 하나로 분류할 수는 있지만 둘 다는 아닙니다.
contacts.sortInPlace({$0.lastName < $1.lastName})
이 배열을 정렬하려면 어떻게 더 많은 기준을 추가할 수 있습니까?
튜플을 사용하여 여러 기준 비교 수행
여러 기준으로 정렬을 수행하는 정말 간단한 방법은 다음과 같이 튜플을 사용하는 것입니다.<
그리고.>
연산자는 사전 비교를 수행하는 작업자에게 과부하가 걸립니다.
/// 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:)
개의
폐쇄 및 SortDescriptor가 포함된 Sorting Collections에 대한 논의에서 영감을 얻어 사용자 정의 오버로드를 정의하는 것도 방법입니다.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 {
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:
parameter합니다(으)로되지 않도록 필요합니다.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
@AMMchilov & @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)
"여러 기준에 따라 정렬하는 것"이 무엇을 의미하는지 생각해 보세요.이것은 두 개의 물체가 하나의 기준에 의해 먼저 비교된다는 것을 의미합니다.그런 다음, 그 기준이 같다면, 다음 기준에 의해 타이가 깨지고, 원하는 주문을 받을 때까지 계속됩니다.
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
Contact
를 가 없습니다.
두 가지 기준으로 정렬하는 또 다른 간단한 방법은 다음과 같습니다.
번째 이 는 합니다. 입니다.lastName
.lastName
,한다면lastName
값은 그런 두 의, 됩니다)로합니다.firstName
.
contacts.sort { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName }
이 질문은 이미 많은 훌륭한 답을 가지고 있지만 스위프트의 기술자 정렬 기사를 소개하고자 합니다.다중 기준 정렬을 수행하는 몇 가지 방법이 있습니다.
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)]
성/이름으로 정렬하는 스위프트 방식을 사용합니다.이 방법은 클래스/구조 둘 다와 함께 작동해야 합니다.하지만 여기서는 출생연도별로 분류하지 않습니다.
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)]
NS SortDescriptor를 인티베이션하는 빠른 방법.이것은 '함수는 일급형'이라는 개념을 사용합니다.SortDescriptor는 함수 유형으로 두 개의 값을 사용하고 bool을 반환합니다.sortByFirstName이라고 하면 두 개의 매개 변수($0,$1)를 사용하여 그들의 이름을 비교합니다.콤바인 함수는 여러 개의 소트 디스크립터를 필요로 하며, 이들을 모두 비교한 후 명령을 내립니다.
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)]
구조와 클래스를 함께 사용할 수 있고, nils와 비교할 수 있도록 확장할 수도 있기 때문에 좋습니다.
그럼에도 불구하고, 원문을 읽는 것이 강하게 권고됩니다.그것은 훨씬 더 상세하고 잘 설명되어 있습니다.
@Hamish에 의해 설명된 것처럼 사전 정렬이 할 수 없는 한 가지 일은 다른 정렬 방향을 처리하는 것입니다. 첫 번째 필드 하행, 다음 필드 상승 등에 의한 정렬입니다.
저는 스위프트 3에서 어떻게 하면 코드를 간단하고 읽을 수 있게 유지할 수 있는지에 대한 블로그 게시물을 만들었습니다.
여기에서 확인할 수 있습니다.
http://master-method.com/index.php/2016/11/23/sort-a-sequence-i-e-arrays-of-objects-by-multiple-properties-in-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
)
추가 코드가 필요 없기 때문에 해미쉬의 튜플 솔루션을 사용하는 것을 추천합니다.
문처럼 동작하지만 분기 논리를 단순화하는 것을 원하는 경우 다음을 수행할 수 있는 이 솔루션을 사용할 수 있습니다.
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
그리고 상승/하강은 오버로드된 연산자 대신 매개변수에 의해 제어됩니다. 기능을 , 제 합니다 Jamie면 Array은에 을 사용합니다.sort
/sorted
method다라는 두 .compare
그리고.comparisons
.
완성도를 위해, 제 솔루션이 해미쉬의 튜플 솔루션과 비교되는 방법은 다음과 같습니다.시연을 위해 사람들을 분류하고 싶은 거친 예시를 사용하겠습니다.(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]) }
스위프트 3에서 나의 배열 [String]을 위해 작동했고 스위프트 4에서는 괜찮은 것 같습니다.
array = array.sorted{$0.compare($1, options: .numeric) == .orderedAscending}
언급URL : https://stackoverflow.com/questions/37603960/swift-sort-array-of-objects-with-multiple-criteria
'programing' 카테고리의 다른 글
PHP 엔티티 클래스 생성기 (0) | 2023.10.01 |
---|---|
자바스크립트로 User Agent 가져오기 (0) | 2023.10.01 |
jQuery를 사용하여 상위 요소를 삭제하는 방법 (0) | 2023.10.01 |
WP REST API 2.0 플러그인을 사용하여 기본 인증으로 인증할 수 없음 (0) | 2023.10.01 |
곱슬곱슬한 괄호는 PowerShell 변수 선언에서 의미하는 바가 있습니까? (0) | 2023.10.01 |