Swift – 用多个条件对对象进行sorting
我有一个Contact
对象的数组:
var contacts:[Contact] = [Contact]()
联系人类别:
Class Contact:NSOBject { var firstName:String! var lastName:String! }
我想通过lastName
sorting该数组,然后通过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议的那样,你可以使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()
:
// 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它:
你也可以在这里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
方法,但需要两个额外的定义: compare
和comparisons
。
为了完整起见,下面是我的解决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。
-
使用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)]
-
用姓氏/名字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)]
-
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
,如果它们不等于按lastName
sorting,如果lastName
相等, lastName
第二个字段sorting,在本例中为firstName
。
contacts.sort { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName }