You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 5数组sorted排序异常问题及自定义排序实现方法

解决字符串数组的自然排序问题

默认的字符串排序是字典序,即逐字符比较ASCII值。比如"Chapter 40"和"Chapter 5"比较时,前7个字符完全一致,第8个字符"4"的ASCII值(52)小于"5"的ASCII值(53),所以"Chapter 40"会排在"Chapter 5"前面,这就是你遇到的异常排序问题。

要修复这个问题,需要实现自然排序逻辑:先按前缀(Chapter/Unit)分组,再按数字的数值大小排序。以下是两种可行的实现方式:

方法1:自定义排序闭包(直接在sorted中使用)

var chaptersAndUnits: [String] = ["Chapter 2", "Chapter 3", "Chapter 5", "Unit 12", "Chapter 40"]

let sortedArray = chaptersAndUnits.sorted { a, b in
    // 匹配"前缀+空格+数字"的正则表达式
    let pattern = #"^(\w+)\s+(\d+)$"#
    let regex = try! NSRegularExpression(pattern: pattern)
    
    // 提取字符串a的前缀和数字
    let aMatches = regex.firstMatch(in: a, range: NSRange(a.startIndex..., in: a))
    guard let aPrefix = aMatches?.range(at: 1).toString(in: a),
          let aNumberStr = aMatches?.range(at: 2).toString(in: a),
          let aNumber = Int(aNumberStr) else {
        return a < b // 格式异常时 fallback 到默认排序
    }
    
    // 提取字符串b的前缀和数字
    let bMatches = regex.firstMatch(in: b, range: NSRange(b.startIndex..., in: b))
    guard let bPrefix = bMatches?.range(at: 1).toString(in: b),
          let bNumberStr = bMatches?.range(at: 2).toString(in: b),
          let bNumber = Int(bNumberStr) else {
        return a < b
    }
    
    // 先比较前缀,前缀相同则按数字大小排序
    if aPrefix != bPrefix {
        return aPrefix < bPrefix
    } else {
        return aNumber < bNumber
    }
}

// 辅助方法:将NSRange转换为Swift字符串
extension NSRange {
    func toString(in string: String) -> String? {
        guard let range = Range(self, in: string) else { return nil }
        return String(string[range])
    }
}

执行后数组会变为:["Chapter 2", "Chapter 3", "Chapter 5", "Chapter 40", "Unit 12"],符合预期排序。

方法2:封装可复用的自定义Comparator

如果需要在多个场景使用该排序逻辑,可以封装成Comparator:

var chaptersAndUnits: [String] = ["Chapter 2", "Chapter 3", "Chapter 5", "Unit 12", "Chapter 40"]

// 封装自然排序比较器
let chapterUnitNaturalComparator: Comparator = { obj1, obj2 in
    guard let a = obj1 as? String, let b = obj2 as? String else {
        return .orderedSame
    }
    
    let pattern = #"^(\w+)\s+(\d+)$"#
    let regex = try! NSRegularExpression(pattern: pattern)
    
    // 提取a的前缀和数字
    let aMatches = regex.firstMatch(in: a, range: NSRange(a.startIndex..., in: a))
    guard let aPrefix = aMatches?.range(at: 1).toString(in: a),
          let aNumberStr = aMatches?.range(at: 2).toString(in: a),
          let aNumber = Int(aNumberStr) else {
        return a.compare(b)
    }
    
    // 提取b的前缀和数字
    let bMatches = regex.firstMatch(in: b, range: NSRange(b.startIndex..., in: b))
    guard let bPrefix = bMatches?.range(at: 1).toString(in: b),
          let bNumberStr = bMatches?.range(at: 2).toString(in: b),
          let bNumber = Int(bNumberStr) else {
        return a.compare(b)
    }
    
    if aPrefix != bPrefix {
        return aPrefix.compare(bPrefix)
    } else {
        return aNumber < bNumber ? .orderedAscending : .orderedDescending
    }
}

// 使用比较器排序
let sortedArray = (chaptersAndUnits as NSArray).sortedArray(using: chapterUnitNaturalComparator) as! [String]

额外优化:自定义前缀优先级

如果需要强制指定前缀的排序顺序(比如让Chapter始终排在Unit前面,而非按字母顺序),可以修改前缀比较逻辑:

// 自定义前缀排序优先级
let prefixPriority: [String: Int] = ["Chapter": 0, "Unit": 1]
let aPriority = prefixPriority[aPrefix] ?? Int.max
let bPriority = prefixPriority[bPrefix] ?? Int.max

if aPriority != bPriority {
    return aPriority < bPriority
}

内容的提问来源于stack exchange,提问作者Ars_Codicis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 09:15:41