Contact
オブジェクトの配列があります。
var contacts:[Contact] = [Contact]()
連絡先クラス:
Class Contact:NSOBject {
var firstName:String!
var lastName:String!
}
そして、いくつかの連絡先が同じlastName
を取得した場合に備えて、その配列をfirstName
で並べ替えてからlastName
で並べ替えたいと思います。
これらの基準のいずれかでソートできますが、両方ではありません。
contacts.sortInPlace({$0.lastName < $1.lastName})
この配列をソートするための基準を追加するにはどうすればよいですか?
「複数の基準によるソート」の意味を考えてください。これは、2つのオブジェクトが最初に1つの基準で比較されることを意味します。次に、それらの基準が同じ場合、次の基準によって関係が解除され、希望する順序になるまで続きます。
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
protocol に準拠させる方がよい場合があります。このように、 Sequence.sorted()
method を使用して、 Comparable.<(_:_:)
operator の実装を調べて、要素の比較方法を決定できます。これにより、Sequence
of Contact
sを並べ替えて、並べ替えコードを複製する必要がなくなります。
複数の基準による並べ替えを実行する非常に簡単な方法(つまり、1つの比較による並べ替え、および同等の場合は別の比較による並べ替え)は、tuplesを使用することです。 <
および>
演算子には、辞書式の比較を実行するオーバーロードがあるためです。
/// 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
プロパティが比較されます。それらが等しくない場合、ソート順はそれらとの<
比較に基づきます。それらがare等しい場合、タプル内の次の要素のペアに移動します。つまり、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
// ...
)
呼び出しサイトはTupleバリアントほど簡潔ではありませんが、比較対象と順序を明確にすることができます。
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)
2つの基準でソートする別の簡単なアプローチを以下に示します。
最初のフィールドをチェックします。この場合はlastName
です。等しくない場合はlastName
で並べ替え、lastName
が等しい場合は2番目のフィールド、この場合はfirstName
で並べ替えます。
contacts.sort { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName }
この質問にはすでに多くの素晴らしい答えがありますが、記事を指し示したいと思います- 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は関数型であり、2つの値を取り、ブール値を返します。 sortByFirstNameの場合、2つのパラメーター($ 0、$ 1)を取り、それらの名を比較します。結合関数は、多数のSortDescriptorsを受け取り、それらすべてを比較して順序を与えます。
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のTupleソリューション を使用することをお勧めします。追加のコードは不要です。
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
。また、昇順/降順は、オーバーロードされた演算子ではなくパラメーターによって制御されます。 JamieのソリューションはArrayに拡張機能を追加しますが、私のソリューションは組み込みのsort
/sorted
メソッドを使用しますが、定義する追加の2つのメソッドが必要です:compare
およびcomparisons
。
完全を期すために、ここで私のソリューションを HamishのTupleソリューション と比較します。デモのために、(name, address, profileViews)
で人を並べ替えるというワイルドな例を使用します。Hamishのソリューションは、比較が始まる前に6つのプロパティ値をそれぞれ1回だけ評価します。これは望ましくない場合と望ましくない場合があります。たとえば、profileViews
が高価なネットワークコールであると仮定すると、絶対に必要でない限り、profileViews
を呼び出さないようにすることができます。私のソリューションは、$0.name == $1.name
および$0.address == $1.address
までprofileViews
を評価することを避けます。ただし、profileViews
を評価するときは、おそらく1回よりも多くの回数評価します。
どうですか:
contacts.sort() { [$0.last, $0.first].lexicographicalCompare([$1.last, $1.first]) }
Swift 3の配列[String]で機能し、Swift 4で問題ないようです
array = array.sorted{$0.compare($1, options: .numeric) == .orderedAscending}