Swift:按照名称(string)按字母顺序将对象数组映射到新数组中的单独的字母集合中

我已经创build了一个名为Contact的结构体,它代表了一个在Array中当前有几个人的联系人。 他们已经按照字母顺序sorting,但是我想按字母顺序排列它们,这是一个string的名称属性,但是我不只是想要在一个数组中排列它们,我想将这些对象分成不同的集合与他们的名字的第一个字母相对应。 例如。 “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") ] 

我想创build类似的东西

 let sectionTitles = ["A", "B"] let sortedContactData = [ [ Contact(name: "Adam"), Contact(name: "Adrian") ], [ Contact(name:"Balthazar") Contact(name:"Bobby") ] ] 

或者类似的…

最终的结果是,我想显示它们到一个UITableView与章节和对象中的字母到indexPath.rows,就像iPhone本地的通讯录应用程序一样。 我其实不确定这是否是达到这个结果的最理想的方法,所以我欢迎任何挑战这个问题!

 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) 
  1. 对列表进行sorting。 O(nlogn),其中n是数组(contactData)中的项目数。
  2. 使用reduce来迭代列表中的每个联系人,然后将其添加到新组或最后一个。 O(n),其中n是数组中的项目数(sortedContacts)。

如果您需要获得更好的打印信息,最好使联系人符合协议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数组已经sorting)。

 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:)上面,我想利用index(where:)方法可用于Slice实例 :

 // 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 } } 

但似乎无法正确解决此方法,可能是由于扩展中缺less一个约束( 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 } } 

你可能想这样做:

 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")]] 

映射将表示一个Contact数组的数组,每个都应该包含对象对。

恕我直言,没有单一的地图方式来做到这一点,所以algorithm是:

 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 })