[英]Swift: Map Array of Objects Alphabetically by Name(String) into Separate Letter Collections within a new Array
我创建了一个名为 Contact 的结构,它代表一个人类联系人,其中当前有几个在一个数组中。 它们已经按字母顺序排序,但是我想按 name 属性按字母顺序对它们进行排序,该属性是一个字符串,但我不只是想将它们按顺序排列在单个数组中,我想将对象拆分为不同的集合对应于他们名字的第一个字母。 例如。 “A”包含 2 个对象,其中联系人名称以 A 开头,“B”表示 Bobby、Brad 等名称,依此类推。
let contactData:[Contact] = [
Contact(id: 1, available: true, name: "Adam"),
Contact(id: 2, available: true, name: "Adrian"),
Contact(id: 3, available: true, name: "Balthazar"),
Contact(id: 4, available: true, name: "Bobby")
]
我想创建类似的东西
let sectionTitles = ["A", "B"]
let sortedContactData = [
[
Contact(name: "Adam"),
Contact(name: "Adrian")
],
[
Contact(name:"Balthazar")
Contact(name:"Bobby")
]
]
或者类似的东西...
最终结果是,我想将它们显示到 UITableView 中,其中 Sections 中的字母和 Objects 显示到 indexPath.rows 中,就像 iPhone 原生的 Contacts 应用程序那样。 我实际上不确定这是否是实现这一结果的最理想方式,所以我欢迎对这个问题提出任何挑战!
let sortedContacts = contactData.sorted(by: { $0.name < $1.name }) // sort the Array first.
print(sortedContacts)
let groupedContacts = sortedContacts.reduce([[Contact]]()) {
guard var last = $0.last else { return [[$1]] }
var collection = $0
if last.first!.name.characters.first == $1.name.characters.first {
last += [$1]
collection[collection.count - 1] = last
} else {
collection += [[$1]]
}
return collection
}
print(groupedContacts)
reduce
迭代列表中的每个联系人,然后将其添加到新组或最后一个。 O(n),其中 n 是 Array(sortedContacts) 中的项目数。 如果需要更好的打印信息,最好让 Contact 符合协议CustomStringConvertible
我们可以让自己受到 Github 用户oisdk:s chunk(n:)
集合方法的启发,并修改它以根据提供的(Element, Element) -> Bool
谓词将Collection
实例分块,用于判断给定的元素应包含在与前一个相同的块中。
extension Collection {
func chunk(by predicate: @escaping (Iterator.Element, Iterator.Element) -> Bool) -> [SubSequence] {
var res: [SubSequence] = []
var i = startIndex
var k: Index
while i != endIndex {
k = endIndex
var j = index(after: i)
while j != endIndex {
if !predicate(self[i], self[j]) {
k = j
break
}
formIndex(after: &j)
}
res.append(self[i..<k])
i = k
}
return res
}
}
示例设置(正如您所说,我们假设contactData
数组已经排序)。
struct Contact {
let id: Int
var available: Bool
let name: String
}
let contactData: [Contact] = [
Contact(id: 1, available: true, name: "Adam"),
Contact(id: 2, available: true, name: "Adrian"),
Contact(id: 3, available: true, name: "Balthazar"),
Contact(id: 4, available: true, name: "Bobby")
]
使用上面的chunk(by:)
方法根据名称的首字母将contactData
数组拆分为多个Contact
实例:
let groupedContactData = contactData.chunk {
$0.name.characters.first.map { String($0) } ?? "" ==
$1.name.characters.first.map { String($0) } ?? ""
}
for group in groupedContactData {
print(group.map { $0.name })
} /* ["Adam", "Adrian"]
["Balthazar", "Bobby"] */
chunk(by:)
方法在上面的chunk(by:)
初始(非编译)版本中,我想使用Slice
实例可用的index(where:)
方法:
// does not compile!
extension Collection {
func chunk(by predicate: @escaping (Iterator.Element, Iterator.Element) -> Bool) -> [SubSequence] {
var res: [SubSequence] = []
var i = startIndex
var j = index(after: i)
while i != endIndex {
j = self[j..<endIndex]
.index(where: { !predicate(self[i], $0) } ) ?? endIndex
/* ^^^^^ error: incorrect argument label in call
(have 'where:', expected 'after:') */
res.append(self[i..<j])
i = j
}
return res
}
}
但似乎它无法正确解析此方法,可能是由于扩展中缺少约束( Collection where ...
)。 也许有人可以阐明如何允许上面的 stdlib 简化扩展?
但是,如果我们将其应用于Array
,我们可能会实现这个稍微简短的扩展,在这种情况下index(where:)
可以在ArraySlice
实例( self[...]
)上成功调用index(where:)
:
// ok
extension Array {
func chunk(by predicate: @escaping (Iterator.Element, Iterator.Element) -> Bool) -> [SubSequence] {
var res: [SubSequence] = []
var i = startIndex
var j = index(after: i)
while i != endIndex {
j = self[j..<endIndex]
.index(where: { !predicate(self[i], $0) } ) ?? endIndex
res.append(self[i..<j])
i = j
}
return res
}
}
恕我直言,没有单地图方法可以做到这一点,所以算法是:
var sectionedData: [String: [Contact]] = [:]
contactData.forEach {
guard let firstLetter = $0.name.characters.first else {
sectionedData["#"] = (sectionedData["#"] ?? []) + [$0]
return
}
let firstLetterStr = String(firstLetter)
sectionedData[firstLetterStr] = (sectionedData[firstLetterStr] ?? []) + [$0]
}
let sortedContactData = sectionedData.sorted(by: { $0.0.key < $0.1.key })
你可能想这样做:
let contactData:[Contact] = [
Contact(id: 1, available: true, name: "Adam"),
Contact(id: 2, available: true, name: "Adrian"),
Contact(id: 3, available: true, name: "Balthazar"),
Contact(id: 4, available: true, name: "Bobby")
]
let mapped = stride(from: 0, to: contactData.count, by: 2).map {
[contactData[$0], contactData[$0+1]]
}
print(mapped)
// [[Contact(id: 1, available: true, name: "Adam"), Contact(id: 2, available: true, name: "Adrian")], [Contact(id: 3, available: true, name: "Balthazar"), Contact(id: 4, available: true, name: "Bobby")]]
mapped将表示Contact
数组的数组,每个数组应包含一对对象。
将集合过滤和拆分为与给定谓词一样多的较小集合的解决方案。
例如给定的整数数组 [1, 2, 3, 4] 应用谓词: odd
、 even
和>3
,
结果将是[ [1, 3], [2, 4], [4] ]
注意:根据给定的谓词,子集可以是重复的。 (我很好奇这是否可以改进reduce
复杂度:O(n))
extension Collection {
func filterParts(_ predicates: ((Element) -> Bool)...) -> [ [Element] ] {
let empty = predicates.map { _ -> [Element] in return [] }
let enumeratedPredicates = predicates.enumerated()
return reduce(empty) { (result, element) in
var result = result
enumeratedPredicates.forEach { offset, predicate in
if predicate(element) {
result[offset].append(element)
}
}
return result
}
}
}
像这样使用字典的 init(grouping:by:) :
lazy var sectionDictionary: Dictionary<String, [Contact]> = {
return Dictionary(grouping: contactData, by: {
// assumes title is a non-empty string
let name = $0.name
let normalizedName = name.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current)
let firstCharAsString = String(normalizedName.first!).uppercased()
return firstCharAsString
})
}()
我详细说明了不同的转换步骤,但如果您愿意,您可以将它们组合成一行。
这将生成一个字典,其中部分名称作为键,对象数组作为值。
从那里您可以轻松提取数组的部分数组,并免费获得您的部分名称数组:
lazy var sectionTitles: [String] = {
return self.sectionDictionary.keys.sorted()
}()
lazy var sections: Array<[String]> = {
return self.sectionTitles.map { sectionDictionary[$0]! }
}()
请注意,我使用了一些强制解包,您应该在生产代码中加以guard
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.