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
相关产品推荐
相关产品推荐

