Tom G
Tom G

Reputation: 133

How to get sorted struct into another struct/dictionary using swift3?

I'm trying to create alphabetic sections in my TableView, so far I'v managed to get my database into struct and get the fist letter of every name from my database and sort it for the section header. My problem is that I don't get the sorted variable into the another struct and show it in the table.

My structs:

struct CrimesInfo {

    let name: String
    let detail: String
    let time: String


    init(name:String, detail: String, time: String) {
        self.name = name
        self.detail = detail
        self.time = time
    }

    init(fromResultSet: FMResultSet) {
        self.init(
            name: fromResultSet.string(forColumn: "Name"),
            detail: fromResultSet.string(forColumn: "Detail"),
            time: fromResultSet.string(forColumn: "Time")
        )

    }
}

struct CrimeNameSection {

    var firstLetter: Character
    var crimes: [CrimesInfo]

    init(title: Character, objects: [CrimesInfo]) {
        firstLetter = title
        crimes = objects
    }
}

My database store in struct 'CrimesInfo', after the sorting I want to insert it into the struct 'CrimeNameSection' (title: the first letter of 'name', objects: the rest of the data accordingly).

My code:

class SectionData {

    var crimeInfo : [CrimesInfo] = []

    func getCrimesData() {
        crimeInfo = ModelManager.getInstance().getAllCrimeInfo() // get the database into the struct
    }

    func getSectionFromData() -> [CrimeNameSection] { // get the fisrt letter of 'name', sort it and get in the another struct
        var crimeIndex = [Character: [CrimesInfo]]()
        var CrimeSections = [CrimeNameSection]()
        for crime in crimeInfo {
            if let firstCharacter = crime.name.characters.first {
                if crimeIndex[firstCharacter] == nil {
                    crimeIndex[firstCharacter] = [crime]
                } else {
                    crimeIndex[firstCharacter]?.append(crime)
            }
        }
    }

    let sortedIndex = crimeIndex.sorted { $0.0 < $1.0 } // type: [(key: Character, value:[CrimesInfo])]

    for key in sortedIndex { // get the sorted data into struct 'CrimeNameSection'
        let sortedSections = CrimeNameSection(title: sortedIndex(key), objects: sortedIndex(value)) // error: 'Use of unresolved identifier 'value'
    }

    CrimeSections.append(sortedSections)

    return CrimeSections
    }
}

Upvotes: 0

Views: 87

Answers (2)

Alexander
Alexander

Reputation: 63271

I suggest against CrimeNameSection. Just use a dictionary from Character to [CrimeInfo]. It'll be much easier to work with.

extension Array {
    func groupBy<Key>(_ keyGenerator: (Element) -> Key)
        -> [Key: [Element]] {
        var result = [Key: [Element]]()

        for element in self {
            let key = keyGenerator(element)
            var array = result[key] ?? []
            array.append(element)
            result[key] = array
        }

        return result
    }
}

let crimes = [
    CrimesInfo(name: "ba", detail: "", time: ""),
    CrimesInfo(name: "aa", detail: "", time: ""),
    CrimesInfo(name: "ab", detail: "", time: ""),
    CrimesInfo(name: "ca", detail: "", time: ""),
    CrimesInfo(name: "ac", detail: "", time: ""),
    CrimesInfo(name: "bb", detail: "", time: ""),
]

let crimesByInitial = crimes.groupBy{ $0.name.characters.first! }

result:

[
    "b": [
        CrimesInfo(name: "ba", detail: "", time: ""),
        CrimesInfo(name: "bb", detail: "", time: "")
    ],
    "a": [
        CrimesInfo(name: "aa", detail: "", time: ""),
        CrimesInfo(name: "ab", detail: "", time: ""),
        CrimesInfo(name: "ac", detail: "", time: "")
    ],
    "c": [
         CrimesInfo(name: "ca", detail: "", time: "")
    ]
]

Upvotes: 0

Luca Angeletti
Luca Angeletti

Reputation: 59506

Given a list of CrimeInfo

let crimes = [
    CrimesInfo(name: "ba", detail: "", time: ""),
    CrimesInfo(name: "aa", detail: "", time: ""),
    CrimesInfo(name: "ab", detail: "", time: ""),
    CrimesInfo(name: "ca", detail: "", time: ""),
    CrimesInfo(name: "ac", detail: "", time: ""),
    CrimesInfo(name: "bb", detail: "", time: ""),
]

you can write

let sections: [CrimeNameSection] = crimes
    .sorted { $0.name < $1.name }
    .reduce([CrimeNameSection]()) { result, crime -> [CrimeNameSection] in
        let crimeFirstLetter = crime.name.characters.first ?? " "

        guard var index = result.index(where: { $0.firstLetter == crimeFirstLetter }) else {
            let newSection = CrimeNameSection(title: crimeFirstLetter, objects: [crime])
            return result + [newSection]
        }

        var result = result
        var section = result[index]
        section.crimes.append(crime)
        result[index] = section
        return result
    }

and you get this output

print(sections[0])
// CrimeNameSection(firstLetter: "a", crimes: [CrimesInfo(name: "aa", detail: "", time: ""), CrimesInfo(name: "ab", detail: "", time: ""), CrimesInfo(name: "ac", detail: "", time: "")])

print(sections[1])
// CrimeNameSection(firstLetter: "b", crimes: [CrimesInfo(name: "ba", detail: "", time: ""), CrimesInfo(name: "bb", detail: "", time: "")])

print(sections[2])
// CrimeNameSection(firstLetter: "c", crimes: [CrimesInfo(name: "ca", detail: "", time: "")])

Just a note

Let's look at your CrimeNameSection.

struct CrimeNameSection {

    var firstLetter: Character
    var crimes: [CrimesInfo]

    init(title: Character, objects: [CrimesInfo]) {
        firstLetter = title
        crimes = objects
    }
}

you named the second param of the initializer objects. This is semantically wrong since CrimeInfo is not an object, is a value.

Why don't you simply remove the initializer and the the struct to expose the memberwise initializer? Look

struct CrimeNameSection {
    var firstLetter: Character
    var crimes: [CrimesInfo]
}

now you can write

CrimeNameSection(firstLetter: "a", crimes: crimes)

Update

This is how you populate your table view

class Table: UITableViewController {

    var sections: [CrimeNameSection] = ...

    override func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sections[section].crimes.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let crime: CrimesInfo = sections[indexPath.section].crimes[indexPath.row]

        // TODO: use crime to populate your cell
    }

}

Upvotes: 2

Related Questions