1

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?

2
  • 3
    That is clear, readable code. – You'll always need a nested loop (explicit or hidden in a filter) unless you change the data structures. Commented Jul 25, 2017 at 9:23
  • It's not bad, I was just wondering if it could be achieved with the new functions in Swift. Commented Jul 25, 2017 at 9:24

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

If you are discarding the result anyway, why make the function return anything? Could it not be done like so: _ = jobs.map({ ..., and have the function without a return value?
@damjandd can't argue with that, but is this approach actually better in any way?
Not really, I was just curious how it would be done using the new techniques and if it would make the code more readable. Thanks for your input.
1

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

Comments

0

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

4 Comments

It looks as if you are using reduce as a "map replacement", which is extremely inefficient because lots of intermediate arrays are created. Compare airspeedvelocity.net/2015/08/03/…
yeah, right, I could use flatMap here too, I prefer reduce for readability at times, plus afaik there's been another article recently where the exponential part of reduce has been removed, and it was taking linearly 1.5 times of a map
Do you have a link to that article? – Also this github.com/apple/swift-evolution/blob/master/proposals/… should improve can help in Swift 4, when implemented.
Your second code does not compile. Created new JobBookPayload needs to keep jobId and other properties unchanged. You code returns a new Array where some elements are removed, which is completely wrong. The original code never deletes any elements from the Array of JobBookPayload.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.