Swift – 用多个条件对对象进行sorting

我有一个Contact对象的数组:

 var contacts:[Contact] = [Contact]() 

联系人类别:

 Class Contact:NSOBject { var firstName:String! var lastName:String! } 

我想通过lastNamesorting该数组,然后通过firstName ,以防一些联系人得到相同的lastName

我可以按照其中一个标准进行sorting,但不能同时进行sorting。

 contacts.sortInPlace({$0.lastName < $1.lastName}) 

我怎么能添加更多的标准来sorting这个数组?

谢谢。

想想“按多个标准sorting”是什么意思。 这意味着两个对象首先被比较一个标准。 那么,如果这些标准是相同的,关系将被下一个标准打破,等等,直到你得到所需的顺序。

 contacts.sortInPlace{ //sort(_:) in Swift 3 if $0.lastName != $1.lastName { 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 } } 

你在这里使用的是sortInPlace(_:)方法 ,它依赖给定的闭包来确定sorting。 如果您的sorting将在许多地方使用,则最好使用sortInPlace()方法 ,该方法只适用于也符合Comparable协议的 MutableCollectionType实例。 这样,您可以sortingContact集合,而无需重复sorting代码。

按照多个标准进行sorting的一种非常简单的方法(即,通过一个比较进行sorting,如果相同,则通过另一次比较)是通过使用元组 ,因为<>运算符具有用于执行词典对比的重载。

 /// 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: "Charlie", lastName: "Webb"), Contact(firstName: "Alex", lastName: "Elexson"), Contact(firstName: "Charles", lastName: "Webb"), Contact(firstName: "Alex", lastName: "Alexson") ] // in Swift 2.x, sortInPlace(_:) contacts.sort { ($0.lastName, $0.firstName) < ($1.lastName, $1.firstName) } print(contacts) // [ // Contact(firstName: "Alex", lastName: "Alexson"), // Contact(firstName: "Alex", lastName: "Elexson"), // Contact(firstName: "Charles", lastName: "Webb"), // Contact(firstName: "Charlie", lastName: "Webb") // ] 

这将首先比较元素的lastName属性。 如果它们不相等,则sorting顺序将基于<与它们进行比较。 如果它们相等,则它将移动到元组中的下一对元素,即比较firstName属性。

标准库为2到6个元素的元组提供<>重载。

如果你想要不同的属性的sorting顺序,你可以简单地交换元组中的元素:

 contacts.sort { ($1.lastName, $0.firstName) < ($0.lastName, $1.firstName) } // [ // Contact(firstName: "Charles", lastName: "Webb"), // Contact(firstName: "Charlie", lastName: "Webb"), // Contact(firstName: "Alex", lastName: "Elexson"), // Contact(firstName: "Alex", lastName: "Alexson") // ] 

现在这将按lastName降序排列,然后按firstName升序排列。

如果你要定期进行这种比较,那么就像@AMomchilov & @appzYourLife所build议的那样,你可以使ContactComparable

 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()

 // ascending contacts.sort() 

或按降序sort(by: >)

 // descending contacts.sort(by: >) 

如果您想要使用其他sorting顺序,则可以使用嵌套types来定义它们:

 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) 

@Hamish描述的词典编纂不能做的一件事就是处理不同的sorting方向,比如说第一个字段降序sorting,第二个字段升序sorting等等。

我在Swift 3中创build了一篇关于如何做到这一点的博文,并保持代码的简单性和可读性。

你可以在这里find它:

http://master-method.com/index.php/2016/11/23/sort-a-sequence-ie-arrays-of-objects-by-multiple-properties-in-swift-3/

你也可以在这里find一个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 ) 

我build议使用Hamish的元组解决scheme,因为它不需要额外的代码。


如果您想要的行为与if语句相似if但简化了分支逻辑,则可以使用此解决scheme,它可以执行以下操作:

 animals.sort { return comparisons( compare($0.family, $1.family, ascending: false), compare($0.name, $1.name)) } 

这里是允许你这样做的function:

 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 } 

如果你想testing它,你可以使用这个额外的代码:

 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的解决scheme的主要区别在于,对属性的访问是内联定义的,而不是类中的静态/实例方法。 例如$0.family而不是Animal.familyCompare 。 而升降由参数控制,而不是重载操作符。 杰米的解决scheme增加了数组的扩展,而我的解决scheme使用内置的sort / sorted方法,但需要两个额外的定义: comparecomparisons

为了完整起见,下面是我的解决scheme与Hamish的元组解决scheme的比较。 为了演示,我将使用一个通用的例子,我们希望通过(name, address, profileViews)对人进行sorting。在开始比较之前,Hamish的解决scheme将对这6个属性值中的每一个值进行一次评估。 这可能不会也可能不被期望。 例如,假设profileViews是一个昂贵的networking调用,我们可能希望避免调用profileViews除非它是绝对必要的。 我的解决scheme将避免评估profileViews直到$0.name == $1.name$0.address == $1.address 。 但是,当它评估profileViews它可能会评估更多的次数比一次。

怎么样:

 contacts.sort() { [$0.last, $0.first].lexicographicalCompare([$1.last, $1.first]) } 

这个问题已经有了很多很好的答案,但我想指出一篇文章 – 在Swift中对Descriptors进行sorting 。 我们有几种方法来执行多个标准sorting。

  1. 使用NSSortDescriptor,这种方式有一些限制,对象应该是一个类,并从NSObjectinheritance。

     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)] 
  2. 用姓氏/名字sorting的Swift方法。 这种方式应该与类/结构。 但是,我们这里不是按年份sorting的。

     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)] 
  3. Swift方式来模拟NSSortDescriptor。 这使用“function是一stream的types”的概念。 SortDescriptor是一个函数types,取两个值,返回一个bool。 说sortByFirstName我们采取两个参数($ 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)] 

    这很好,因为你可以在struct和class中使用它,甚至可以扩展它来与nils进行比较。

不过,强烈build议阅读原文 。 它有更多的细节和很好的解释。

下面显示了使用2个标准进行sorting的另一个简单方法。

检查第一个字段,在这种情况下,它是lastName ,如果它们不等于按lastNamesorting,如果lastName相等, lastName第二个字段sorting,在本例中为firstName

 contacts.sort { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName }