如何按多个标准对Swift对象进行排序

5

我有一个Swift对象列表,我想按多个条件排序。列表中的对象是DateRange类型:

class DateRange {
    var from: NSDate?
    var to: NSDate?
}

该列表包含许多对象,其中一些fromto字段为空。我希望按以下方式对此列表进行排序:
  1. 首先是所有具有日期的对象
  2. 然后是具有至少一个日期(fromto)的对象
  3. 最后是没有任何日期的对象
日期本身并不重要,只要它们存在即可。在Ruby中,我可以这样做(如果日期为nil,则将其设置为非常低的日期):
date_ranges.sort { |a, b|
  [fix_nil(a.from), fix_nil(a.to)] <=> [fix_nil(b.from), fix_nil(b.to)]
}.reverse

def fix_nil(val)
  val.nil? ? Date.new(0) : val
end

怎样用Swift实现最佳方式?提前感谢。

3个回答

2

看起来为你的 DateRange 类型添加一个名为 dateCount 的计算属性可能是个好主意。这是使用模式匹配的好时机:

extension DateRange {
    // returns the number of non-nil NSDate members in 'from' and 'to'
    var dateCount: Int {
        switch (from, to) {
        case (nil, nil): return 0
        case (nil, _): return 1
        case (_, nil): return 1
        default: return 2
        }
    }
}

然后,您可以使用一个简单的闭包对列表进行排序:

var ranges = [DateRange(nil, nil), DateRange(NSDate(), nil), DateRange(nil, NSDate()), DateRange(nil, nil), DateRange(NSDate(), NSDate())]
ranges.sort { $0.dateCount > $1.dateCount }

如果你愿意,你甚至可以再加上几行代码让它成为可比较对象Comparable

extension DateRange : Comparable { }
func ==(lhs: DateRange, rhs: DateRange) -> Bool {
    return lhs.dateCount == rhs.dateCount
}
func <(lhs: DateRange, rhs: DateRange) -> Bool {
    return lhs.dateCount > rhs.dateCount
}

这让您可以使用运算符参数正确地对列表进行排序:
ranges.sort(<)

1
我假设你所说的“list”指的是“array”,因此我的回答是基于这个假设的。
你可以使用数组结构的sort方法,它需要一个具有以下签名的闭包:
(lhs: T, rhs: T) -> Bool 

如果lhs小于rhs,则返回true,否则返回false。 我想出了这个实现:
var x: [DateRange]
// ... initialize the array

x.sort { (lhs: DateRange, rhs: DateRange) -> Bool in
    if lhs.from != nil && lhs.to != nil {
        return true
    }

    if lhs.from == nil && lhs.to == nil {
        return false
    }

    return rhs.from == nil && rhs.to == nil
}
  • 如果lhs的两个属性都不为空,则无论rhs如何,它都排在前面。
  • 如果lhs的两个属性都为空,则无论rhs如何,它都排在后面。
  • 否则,lhs有一个为空,另一个不为空,在这种情况下,只有当rhs的两个属性都为空时,lhs才排在前面。

如果您计划在多个地方重复使用sort,最好将代码移出sort方法 - 最佳位置可能是<运算符的重载:

func < (lhs: DateRange, rhs: DateRange) -> Bool {
    if lhs.from != nil && lhs.to != nil {
        return true
    }

    if lhs.from == nil && lhs.to == nil {
        return false
    }

    return rhs.from == nil && rhs.to == nil
}

在这种情况下,它可以如下使用:

x.sort(<)

如果您不喜欢运算符重载,当然可以给该函数任何其他名称。
请注意,排序是在原地完成的。

我认为你的比较有一个错误——如果两个相等的DateRange实例都是(nil, nil),那么这将返回true。 - Nate Cook
这应该由第二个if语句覆盖:如果lhs两个值都为nil,则返回false。 - Antonio
抱歉——我搞错了。如果两个范围都有日期,由于第一个条件只关注lhs,所以这将返回true。 - Nate Cook
感谢您的回答,特别是提到“原地”排序的提示。我现在使用ExSwiftsortBy函数,它可以很好地处理不可变数据。 - Mattes Dev

1
这是我的解决方法。为了保持简单,为日期范围添加一个评分函数。在您的情况下,有三种可能性:
nil & nil: 0分
nil & date: 1分
date & date: 2分
import Foundation

class DateRange {
    var from: NSDate?
    var to: NSDate?

    init(from: NSDate?, to: NSDate?)
    {
        self.from = from
        self.to = to
    }

    func scoreDateRange() -> Int
    {
        var score = 0
        if from != nil
        {
            score++
        }
        if to != nil
        {
            score++
        }
        return score
    }
}

func sortDateRange( d1 : DateRange, d2 : DateRange)-> Bool
{

    return d1.scoreDateRange() > d2.scoreDateRange()
}

var date_ranges = [DateRange]()
date_ranges.append(DateRange(from:nil, to:nil))
date_ranges.append(DateRange(from:nil, to:nil))
date_ranges.append(DateRange(from:NSDate(), to:NSDate()))
date_ranges.append(DateRange(from:nil, to:NSDate()))
date_ranges.append(DateRange(from:NSDate(), to:nil))
date_ranges.append(DateRange(from:NSDate(), to:NSDate()))

date_ranges.sort(sortDateRange)

评分函数的想法很好,感谢您的快速回答! - Mattes Dev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接