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

时间:2017-03-23 15:58:39

标签: ios arrays swift

我创建了一个名为Contact的结构,它代表一个人类联系人,其中当前有一些数组。它们已按字母顺序排序,但是我想按名称属性按字母顺序对它们进行排序,这是一个字符串,但我不想将它们按顺序放在一个数组中,我想将这些对象拆分成不同的集合。与他们名字的第一个字母相对应。例如。 “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中的字母和将对象转换为indexPath.rows,就像iPhone的原生联系人应用程序所做的那样。我实际上不确定这是否是实现这一结果的最理想方式,所以我欢迎对这个问题提出任何挑战!

6 个答案:

答案 0 :(得分:5)

reduce
  1. 对列表进行排序。 O(nlogn),其中n是数组中的项目数(contactData)。
  2. 使用CustomStringConvertible来迭代每个联系人 在列表中,然后将其添加到新组或最后一个组。 O(n),其中n是数组中的项目数(sortedContacts)。
  3. 如果您需要更好的打印信息,最好使联系人符合协议{{1}}

答案 1 :(得分:1)

根据谓词

对集合进行分块

我们可以让自己受到Github用户oisdk:s chunk(n:)收集方法的启发,并根据提供的Collection谓词对其进行修改以构建(Element, Element) -> Bool实例,用于判断是否存在给定元素应包含在与前一个元素相同的块中。

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:)的初始(非编译)版本中,我想使用index(where:)方法available to Slice instances

// 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[...] ):

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

答案 2 :(得分:0)

恕我直言,没有单一地图的方法,所以算法是:

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

答案 3 :(得分:0)

你可能想这样做:

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的数组数组,每个数组应包含一对对象。

答案 4 :(得分:0)

一种用于过滤集合并将其拆分为与给定谓词一样多的较小集合的解决方案。

例如给定整数数组[1、2、3、4],应用谓词:oddeven>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
        }
    }
}

答案 5 :(得分:0)

像这样使用Dictionary's 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
        })
    }()

我详细说明了不同的转换步骤,但是如果您愿意,可以将它们组合成一个直线。

这将产生一个字典,其中的节名称为键,而对象数组为值。
从那里,您可以轻松提取数组的section数组,并免费获得您的section names数组:

    lazy var sectionTitles: [String] = {

        return self.sectionDictionary.keys.sorted()
    }()

    lazy var sections: Array<[String]> = {

        return self.sectionTitles.map { sectionDictionary[$0]! }
    }()

请注意,我在生产代码中使用了一些guard中应该使用的强制展开功能。