Swift:为名称列表创建节索引

时间:2016-02-11 15:52:50

标签: ios swift

我编写了一个算法来为tableview创建一个段索引。 不幸的是,当列表只包含一个结果为空的项时,我有一个错误。

你有一个优雅的解决方案吗?

var sections : [(index: Int, length :Int, title: String)] = Array()

    func createSectionIndices(participants: List<Participant>){

        sections.removeAll()

        var index = 0;

        let array = participants.sort({$0.lastName < $1.lastName})

        for i in 0.stride(to: array.count, by: 1){

            let commonPrefix = array[i].lastName.commonPrefixWithString(array[index].lastName, options: .CaseInsensitiveSearch)

            if (commonPrefix.isEmpty) {

                let string = array[index].lastName.uppercaseString;
                let firstCharacter = string[string.startIndex]
                let title = "\(firstCharacter)"
                let newSection = (index: index, length: i - index, title: title)
                sections.append(newSection)
                index = i;
            }
        }
        print("sectionCount: \(sections.count)")
    } 

1 个答案:

答案 0 :(得分:0)

这是构建部分列表的一行解决方案:

 var participants:[(firstName:String, lastName:String)] = 
     [
        ("John", "Smith"),
        ("Paul", "smith"),
        ("Jane", "Doe"),
        ("John", "denver"),
        ("Leo",  "Twain"),
        ("Jude", "law")
     ]   

 // case insensitive sort (will keep "denver" and "Doe" together)
 participants = participants.sort({$0.lastName.uppercaseString < $1.lastName.uppercaseString})

 // The process:
 // - get first letter of each name (in uppercase)
 // - combine with indices (enumerate)
 // - only keep first occurrence of each letter (with corresponding indice)
 // - build section tuples using index, letter and number of participants with name begining with letter
 let sections = participants
                .map({String($0.lastName.uppercaseString.characters.first!)})
                .enumerate()
                .filter({ $0 == 0 || !participants[$0 - 1].lastName.uppercaseString.hasPrefix($1) })
                .map({ (start,letter) in return 
                       ( 
                         index:  start, 
                         length: participants.filter({$0.lastName.uppercaseString.hasPrefix(letter)}).count,
                         title:  letter
                       )
                    })

 // sections will contain:
 // (index:0, length:2, title:"D")
 // (index:2, length:1, title:"L")
 // (index:3, length:2, title:"S")
 // (index:5, length:1, title:"T")

您可能已经有很多现有代码基于存储在元组数组中的部分,但如果没有,我建议您稍微改变一下,并使用字母和参与者数据构建您的sections数组。

 let sections = participants
                .map({ String($0.lastName.uppercaseString.characters.first!) })
                .reduce( Array<String>(), combine: { $0.contains($1) ? $0 : $0 + [$1] })      
                .map({ (letter) in return 
                       ( 
                         title: letter, 
                         participants: participants.filter({$0.lastName.uppercaseString.hasPrefix(letter)})
                       ) 
                    })

这将允许您使用sections.count响应部分的数量,但也可以更容易地操作每个部分中的索引路径和数据:

  • 某个部分的参与者人数:部分[index] .participants.count
  • 索引路径的参与者:sections [indexPath.section] .participants [indexPath.row]

这只是语法糖果,但如果您对参与者列表有很多引用,它将使代码更具可读性。
此外,如果您的参与者是对象而不是元组或结构,您甚至可以更新主参与者列表中的数据,而无需重建部分(除非更改了姓氏)。

[EDIT]修复了最后一个元组语法中的错误

[EDIT2] Swift 4 ......

Swift 4中的词典提供了一种更简单的方法来管理这类事情。

用于原始参考结构:

 let sections = [Character:[Int]](grouping:participants.indices)
                {participants[$0].lastName.uppercased().first!}
                .map{(index:$1.reduce(participants.count,min), length:$1.count, title:String($0))}
                .sorted{$0.title<$1.title} 

以及包含自己的参与者子列表的部分结构(我的建议):

let sectionData = [Character:[Participant]](grouping:participants)
                  {$0.lastName.uppercased().first!}
                  .map{(title:$0, participants:$1)}
                  .sorted{$0.title<$1.title}