Swift - 使用多个条件对对象数组进行排序

sbk*_*bkl 73 sorting swift

我有一个Contact对象数组:

var contacts:[Contact] = [Contact]()
Run Code Online (Sandbox Code Playgroud)

联系班级:

Class Contact:NSOBject {
    var firstName:String!
    var lastName:String!
}
Run Code Online (Sandbox Code Playgroud)

我希望按顺序对该数组进行排序,lastName然后firstName在某些联系人得到相同的情况下lastName.

我可以按照其中一个标准排序,但不能同时排序.

contacts.sortInPlace({$0.lastName < $1.lastName})
Run Code Online (Sandbox Code Playgroud)

如何添加更多条件来对此数组进行排序?

Ale*_*ica 91

想想"按多个标准排序"的含义.这意味着首先通过一个标准比较两个对象.然后,如果这些标准相同,则将通过下一个标准打破关系,依此类推,直到获得所需的排序.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

你在这里看到的是Sequence.sorted(by:)方法,它参考提供的闭包来确定元素的比较方式.

如果您的排序将在许多地方使用,那么最好使您的类型符合Comparable 协议.这样,您可以使用Sequence.sorted()方法,该方法参考您的Comparable.<(_:_:)运算符实现来确定元素的比较方式.这样,您可以对任何s进行排序Sequence,Contact而无需复制排序代码.

  • `else`主体必须在`{...}`之间,否则代码不能编译. (2认同)
  • @AthanasiusOfAlex 使用 `==` 不是一个好主意。它仅适用于 2 个属性。除此之外,你会开始用很多复合布尔表达式来重复自己 (2认同)

Ham*_*ish 83

使用元组进行多个条件的比较

由多个条件执行排序的一个非常简单的方式(由一个比较即排序,并且如果等效,然后通过另一比较)是通过使用元组,作为<>运营商执行字典式比较他们的重载.

/// 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
Run Code Online (Sandbox Code Playgroud)

例如:

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")
// ]
Run Code Online (Sandbox Code Playgroud)

这将首先比较元素的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"),
// ]
Run Code Online (Sandbox Code Playgroud)

现在将按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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

(该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
  // ...
)
Run Code Online (Sandbox Code Playgroud)

虽然呼叫站点不像元组变体那样简洁,但您可以更清楚地了解所比较的内容和顺序.


符合 Comparable

如果你打算做这些类型的比较有规律的话,作为@AMomchilov&@appzYourLife建议,你可以遵循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)
  }
}
Run Code Online (Sandbox Code Playgroud)

现在只需要sort()升序:

contacts.sort()
Run Code Online (Sandbox Code Playgroud)

或者sort(by: >)降序排列:

contacts.sort(by: >)
Run Code Online (Sandbox Code Playgroud)

以嵌套类型定义自定义排序顺序

如果您要使用其他排序顺序,可以使用嵌套类型定义它们:

extension Contact {
  enum Comparison {
    static let firstLastAscending: (Contact, Contact) -> Bool = {
      return ($0.firstName, $0.lastName) <
               ($1.firstName, $1.lastName)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

然后简单地称为:

contacts.sort(by: Contact.Comparison.firstLastAscending)
Run Code Online (Sandbox Code Playgroud)

  • 这是最好的答案. (7认同)
  • 这应该是公认的答案; 这是一个可读且灵活的解决方案. (3认同)
  • 这应该是公认的答案。完美运作。 (2认同)

oya*_*lhi 11

使用2个标准进行分类的另一种简单方法如下所示.

检查第一个字段,在这种情况下lastName,如果它们不相等排序lastName,如果lastName相等,则按第二个字段排序,在这种情况下firstName.

contacts.sort { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName  }
Run Code Online (Sandbox Code Playgroud)


小智 5

@Hamish描述的字典排序不能做的一件事就是处理不同的排序方向,比如按第一个字段降序排序,下一个字段升序排序等.

我在Swift 3中创建了一篇关于如何使用它的博客文章,并保持代码简单易读.

你可以在这里找到它:

http://master-method.com/index.php/2016/11/23/sort-a-sequence-ie-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
        )
Run Code Online (Sandbox Code Playgroud)


Xue*_*eYu 5

这个问题已有很多很好的答案,但我想指出一篇文章 - Swift中的Sort Descriptors.我们有多种方法可以进行多重标准排序.

  1. 使用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),
    ]
    
    Run Code Online (Sandbox Code Playgroud)

    例如,在这里,我们希望按姓氏排序,然后按名字排序,最后按出生年份排序.我们希望不区分大小写并使用用户的语言环境.

    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)]
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用Swift方式对姓氏/名字进行排序.这种方式应该适用于class/struct.但是,我们不会在这里按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)]
    
    Run Code Online (Sandbox Code Playgroud)
  3. 快速的方式来传播NSSortDescriptor.这使用了"函数是一流类型"的概念.SortDescriptor是一个函数类型,取两个值,返回一个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)]
    
    Run Code Online (Sandbox Code Playgroud)

    这很好,因为你可以将它与struct和class一起使用,你甚至可以扩展它以与nils进行比较.

仍然,强烈建议阅读原始文章.它有更多的细节和很好的解释.