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

标签 swift sorting

我有一组 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:)采用多个谓词的重载

灵感来自关于 Sorting Collections with map closures and 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 {
  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
  // ...
)

虽然调用站点不像元组变体那样简洁,但您可以更加清楚地了解正在比较的内容以及比较顺序。


符合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)
  }
}

现在只需调用 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)

关于Swift - 使用多个条件对对象数组进行排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37603960/

相关文章:

c - strcmp 未正确比较指向字符的指针数组中的 2 个相邻字符串

algorithm - 如何计算冒泡排序时间复杂度

arrays - 快速数组排序

MySQL UTF-8 单词顺序错误

swift - 从 FinderSync 扩展启动 macOS 应用程序

arrays - 数组排序不正确

swift - Xcode头文件命令点击

php - 按长度排序数组,然后按字母顺序排序

swift - CMPedometer 在 Swift 2.0 中出现错误

ios - 方法 'fontWithName(_:size:)' 与 Objective-C 选择器冲突