2
\$\begingroup\$

I want to group an array of structs only using arrays into an array of new structs. In this example I want to group the Person structs by Job and put them in new Department structs and put them in an array.

enum Job {
    case developer
    case programmer
    case coder
}

struct Person {
    let id: Int
    let job: Job    
}

struct Department {
    let job: Job
    var staff: [Person]

    mutating func addStaff(_ person: Person) {
        staff.append(person)
    }
}

let initial = [
    Person(id: 0, job: .developer),
    Person(id: 1, job: .developer),
    Person(id: 2, job: .programmer),
    Person(id: 3, job: .programmer),
    Person(id: 4, job: .coder)
]

func combinator(accumulator: [Department], current: Person) -> [Department] {
    var accumulator = accumulator

    if let index = accumulator.index(where: { $0.job == current.job }) {

        var department = accumulator[index]
        department.staff.append(current)
        accumulator[index] = department
    } else {
        accumulator.append(Department(job: current.job, staff: [current]))
    }

    return accumulator
}

var departments: [Department] = initial.reduce([], combinator)

I feel like the if statement in the combinator(accumulator:current:) function is a bit clunky, is there a better way to write that part or any other part of this piece of code?

\$\endgroup\$
2
\$\begingroup\$

Swift 4

i would use the new swift 4 init(grouping:by:)

since you create always new Departments then the following code is possible.

if you have an existing [Department] then the code not fit your request.

enum Job {
    case developer
    case programmer
    case coder
}

struct Person {
    let id: Int
    let job: Job    
}

struct Department {
    let job: Job
    var staff: [Person]

    mutating func addStaff(_ person: Person) {
        staff.append(person)
    }
}

let initial = [
    Person(id: 0, job: .developer),
    Person(id: 1, job: .developer),
    Person(id: 2, job: .programmer),
    Person(id: 3, job: .programmer),
    Person(id: 4, job: .coder)
]

let grouped = Dictionary(grouping: initial, by: { $0.job })
let departments = grouped.map{ key, value in Department( job: key, staff: value )}

( NOTE: i don't tested it in playground - just from head to the answer)

Swift 3

in Swift 3 you can use an extension to the array to resolve this grouping

public extension Sequence {
    func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] {
        var categories: [U: [Iterator.Element]] = [:]
        for element in self {
            let key = key(element)
            if case nil = categories[key]?.append(element) {
                categories[key] = [element]
            }
        }
        return categories
    }
}

the extension is from here https://stackoverflow.com/a/31220067/1930509 and when you need a more performat grouping you can find it at the link

\$\endgroup\$
6
  • 1
    \$\begingroup\$ This is specifically tagged swift3... \$\endgroup\$ – Mr. Xcoder Jun 25 '17 at 10:47
  • \$\begingroup\$ There existing some collection extension for grouping by for swift3 if he like my 2 lines solution. \$\endgroup\$ – muescha Jun 25 '17 at 15:48
  • \$\begingroup\$ Yeah sorry, I was looking for a Swift 3 solution \$\endgroup\$ – richy Jun 25 '17 at 19:08
  • \$\begingroup\$ @richy then you can use this extension for swift3 to have a groupby in Swift 3: stackoverflow.com/a/31220067/1930509 \$\endgroup\$ – muescha Jun 26 '17 at 15:53
  • \$\begingroup\$ with this a later upgrade to swift 4 is easy :) \$\endgroup\$ – muescha Jun 26 '17 at 15:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.