Swift 可选类型全名字符串数组如何按姓氏排序且nil和单名排在最前
可选全名数组排序的最优实现
排序规则拆解
首先把需求中的排序优先级拆分为3个层级,优先级从高到低如下:
- 第一优先级:nil值排在最前
- 第二优先级:非nil的单名(不含空格的字符串)排在nil之后
- 第三优先级:包含空格的全名,先按姓氏排序,姓氏相同再按名字排序
基础实现
直接在sorted(by:)闭包中实现排序逻辑,适合中小规模数组使用:
let unordered:[String?] = [ "Zach Appletree", "Nancy Crabtree", "Bill", nil, "Bonny Appletree", "Zach Johnson", "Paul Brandon" ] let sortedArray = unordered.sorted { a, b in // 定义元素类型权重,数值越小优先级越高 func weight(for str: String?) -> Int { guard let str = str else { return 0 } return str.contains(" ") ? 2 : 1 } let weightA = weight(for: a) let weightB = weight(for: b) // 权重不同直接按权重排序 if weightA != weightB { return weightA < weightB } // 都是nil的情况,顺序不影响 guard let strA = a, let strB = b else { return false } // 都是单名的情况,直接按字符串自然排序 if weightA == 1 { return strA < strB } // 都是全名,拆分姓和名,过滤多余空格避免异常 let componentsA = strA.components(separatedBy: .whitespaces).filter { !$0.isEmpty } let componentsB = strB.components(separatedBy: .whitespaces).filter { !$0.isEmpty } // 异常容错:如果拆分后不足2段,降级为单名排序逻辑 guard componentsA.count >= 2, componentsB.count >= 2 else { return strA < strB } let lastNameA = componentsA.last! let lastNameB = componentsB.last! // 先比较姓氏 if lastNameA != lastNameB { return lastNameA < lastNameB } // 姓氏相同再比较名字 let firstNameA = componentsA.first! let firstNameB = componentsB.first! return firstNameA < firstNameB } // 输出结果:[nil, "Bill", "Bonny Appletree", "Zach Appletree", "Paul Brandon", "Nancy Crabtree", "Zach Johnson"] print(sortedArray)
大数组优化实现
如果待排序数组元素较多,可提前预计算每个元素的排序字段,避免排序过程中重复拆分字符串、计算权重,减少O(n log n)量级的重复运算:
extension String { // 预提取排序用的元组:(权重, 姓氏, 名字) var sortMeta: (weight: Int, lastName: String, firstName: String) { if contains(" ") { let components = components(separatedBy: .whitespaces).filter { !$0.isEmpty } guard components.count >= 2 else { return (1, self, self) } return (2, components.last!, components.first!) } else { return (1, self, self) } } } let sortedOptimized = unordered .map { item -> (weight: Int, lastName: String, firstName: String, origin: String?) in guard let item = item else { return (0, "", "", nil) } let meta = item.sortMeta return (meta.weight, meta.lastName, meta.firstName, item) } .sorted { a, b in if a.weight != b.weight { return a.weight < b.weight } if a.lastName != b.lastName { return a.lastName < b.lastName } return a.firstName < b.firstName } .map(\.origin)
内容的提问来源于stack exchange,提问作者justdan0227
相关产品推荐
相关产品推荐

