damjandd
damjandd

Reputation: 707

Updating array values using higher order functions in Swift

I’m struggling with using the new map, flatMap, filter, reduce and zip functions. Consider the following: You have two arrays, A and B, containing different objects. For each object in A, you need to find the corresponding object in B (by their id property), and update some of the properties of the object from B. This could be done the old way, using two for cycles, like so:

private func update(statuses: [JobStatus], forJobs jobs: [JobBookPayload]) {
    for jobStatus in statuses {
        for job in jobs {
            if jobStatus.jobId == job.jobId {
                job.status = jobStatus.status!
                job.option = jobStatus.option!
            }
        }
    }
}

Can this be done using the new functions, to make the code more "Swifty" and improve readability?

Upvotes: 1

Views: 3135

Answers (3)

dfrib
dfrib

Reputation: 73186

Replacing for .. in .. { if conditional { ... } } with for .. in .. where conditional { ... }

You could make a minor re-factoring by introducing a where clause in the inner for loop to replace the single if statement of its body:

private func update(statuses: [JobStatus], forJobs jobs: [JobBookPayload]) {
    for jobStatus in statuses {
        for job in jobs where jobStatus.jobId == job.jobId {
            job.status = jobStatus.status!
            job.option = jobStatus.option!
        }
    }
}

Upvotes: 1

user3581248
user3581248

Reputation: 1014

I suppose that since you're using classes, you don't actually want to create a new object from the old one. I believe this could be achieved using map and first methods:

@discardableResult
func update(statuses: [JobStatus], forJobs jobs: [JobBookPayload]) -> [JobBookPayload] {
    return jobs.map({ (payload) -> JobBookPayload in
        if let status = statuses.first(where: { payload.jobId == $0.jobId }) {
            payload.status = status.status
            payload.option = status.option
        }
        return payload
    })
}

Using @discardableResult will allow you to use this function and ignore the resulting array - the objects inside the initial array were mutated anyway.

Upvotes: 2

S2dent
S2dent

Reputation: 949

The idea in the "new" (functional) way, is to create a new object from the previous one:

private func update(statuses: [JobStatus], for jobs: [JobBookPayload]) -> [JobBookPayload] {
    return jobs.reduce([]) { newJobs, job in
        return newJobs + statuses.flatMap { status in
            return status.jobId == job.jobId ?
                //Construct a new object
                JobBookPayload(status: status.status, option: status.option) : nil
        }
    }
}

Assuming there is one status object corresponding to a job:

private func update(statuses: [JobStatus], for jobs: [JobBookPayload]) -> [JobBookPayload] {
    return jobs.reduce([]) { newJobs, job in
        return newJobs + statuses.lazy.filter { $0.jobId == job.jobId }.first.flatMap { 
            JobBookPayload(status: $0.status, option: $0.option) // Construct the object
        }
    }
}

Avoiding reduce:

private func update(statuses: [JobStatus], for jobs: [JobBookPayload]) -> [JobBookPayload] {
    return jobs.flatMap { job in
        return statuses.flatMap { status in
            return status.jobId == job.jobId ?
                //Construct a new object
                JobBookPayload(status: status.status, option: status.option) : nil
        }.last
    }
}

Upvotes: 0

Related Questions