2
\$\begingroup\$

I have a list of rescues for ambulance drivers in different states that are displayed on a kanban board. My JSON object is divided by the status of the rescues, and then grouped by driver. The structure is like this:

--Status (unassigned/assigned/arrived/rescued)

----Driver group

-------Rescue list

The rescues have a datetime and lat/long associated with them, so I can use map/reduce to find the rescue that the drivers attended most recently, and show that location on a map.

One issue is that a driver group can exist in one swimlane and none of the other.

I want to: filter the list down to the arrived/rescued states only, (although logically this may not be necessary as it's not possible to have an arrival time without a driver associated, but this is out of scope) and for each driver group return their latest rescue by arrival time or rescue time. This will leave me with a distinct list of driver groups, with only the most recent rescue for that group

Here's the working code:

let groups = cases
                .filter(swimlane => parseInt(swimlane.rescueStatus) >= 3)
                .map(groups => groups.rescuerGroups)
                .map(rescuerGroups => rescuerGroups)
                .reduce((result, current) => {
                  
                  if(result.length === 0){
                    return current;
                  }
                  else{  
                    
                    current.forEach(currentRescueGroup => {
                      
                      let index = result.findIndex(parentRescueGroup => {
                        
                        return parseInt(parentRescueGroup.rescuer1) == parseInt(currentRescueGroup.rescuer1) &&
                            parseInt(parentRescueGroup.rescuer2) == parseInt(currentRescueGroup.rescuer2)
                        
                      })
                      
                      index > -1 ?
                        result[index].rescues = result[index].rescues.concat(currentRescueGroup.rescues)
                        :
                        result.push(currentRescueGroup);                          
                      
                    })
                    
                    return result;
                    
                    
                  }}, [])
                .map(rescueGroup => {
                                    
                  let maxRescue = rescueGroup.rescues.reduce((current, previous) => {
                  
                  let currentTime = new Date(current.ambulanceArrivalTime) > (new Date(current.rescueTime) || new Date(1901, 01, 01)) ? current.ambulanceArrivalTime : current.rescueTime;
                  let previousTime = new Date(previous.ambulanceArrivalTime) > (new Date(previous.rescueTime) || new Date(1901, 01, 01)) ? previous.ambulanceArrivalTime : previous.rescueTime;
           
              return previousTime > currentTime ? current : previous;
              
            })
              
             return {
                       rescuer1Abbreviation: rescueGroup.rescuer1Abbreviation,
                       rescuer2Abbreviation: rescueGroup.rescuer2Abbreviation,
                       latestLocation: maxRescue.latLngLiteral,
                       rescues: rescueGroup.rescues
                     }
           })

And here's a JSBin including the working code:

https://jsbin.com/hocasob/edit?js,console

Any help to make the reduce sections simpler would be much appreciated

\$\endgroup\$
2
  • 2
    \$\begingroup\$ One often given suggestion is to use a linter. One thing it will tell you, as an example, is that the parseInt requires a radix (it does not default to 10). Another option would be to use the Number constructor instead. Also you could extract the accumulators outside as a variable and use argument destructuring. \$\endgroup\$
    – morbusg
    Aug 28, 2020 at 18:56
  • \$\begingroup\$ Sorry I meant reducer instead of accumulator. \$\endgroup\$
    – morbusg
    Aug 29, 2020 at 22:32

1 Answer 1

2
\$\begingroup\$

Turning a comment into a review (I provide an alternative implementation, but the basic ideas can be applied to the OP implementation).

First, some general thoughts. As I mention in the comments regarding the parseInt function, a linter can help spot any problems. A popular one is ESLint. But perhaps more to the point, the data seems to already be a number, so there shouldn't be any need to parse it.

It can help to clean up the code quite a bit by extracting the filters, reducers, mappers, sorters, etc. outside as a variable and give them a descriptive name, for example:

// filter
const arrivedOrRescued = ({rescueStatus}) => rescueStatus >= 3

// sorter
const byArrivalOrRescueTime = (a, b) =>
  Date.parse(a.ambulanceArrivalTime || a.rescueTime)
    - Date.parse(b.ambulanceArrivalTime || b.rescueTime)

// reducer
const byRescuers = (acc, {rescuerGroups}) => {
  rescuerGroups.forEach(({
    rescues
    , rescuer1Abbreviation: r1
    , rescuer2Abbreviation: r2
  }) => {
    let group = `${r1}-${r2}`
    acc.has(group) ? acc.get(group).push(...rescues) : acc.set(group, rescues)
  })

  return acc
}

so getting a Map (just an example) by rescuers out of the swimlanes could then become

let rescuers = cases.filter(arrivedOrRescued).reduce(byRescuer, new Map())

and sorting their rescues could become

rescuers.forEach((rescues, abbrevs) => rescues.sort(byArrivalOrRescueTime))

after which the latest rescue is the last element of the array now that they have all been collected into a single array and sorted.

One thing I would like to promote here when there can be somewhat complex nested objects in the data, is to use JSDoc comments to document the data shape. For example:

/*
 * @typedef {Object} Swimlane
 * @property {Array.<RescuerGroup>} rescuerGroups
 * @property {Number} rescueStatus
 * @property {String} rescueStatusName
 *
 * @typedef {Object} RescuerGroup
 * @property {Array.<Rescue>} rescues
 * @property {?Number} rescuer1
 * @property {?Number} rescuer2
 * @property {?String} rescuer1Abbreviation
 * @property {?String} rescuer2Abbreviation
 *
 * @typedef {Object} Rescue
 * @property {?String} ambulanceArrivalTime
 * @property {?String} callDateTime
 * @property {?String} callerName
 * @property {String} callerNumber
 * @property {?Number} callOutcomeId
 * @property {Number} emergencyCaseId
 * @property {Number} emergencyCodeId
 * @property {Number} emergencyNumber
 * @property {Number} isLargeAnimal
 * @property {Number} latitude
 * @property {Number} longitude
 * @property {{lat: Number, lng: Number}} latLngLiteral
 * @property {String} location
 * @property {Array.<String>} patients
 * @property {Number} rescuer1
 * @property {Number} rescuer2
 * @property {?String} rescueTime
 * @property {Number} RescueStatus
 */

This makes it easier for others especially in data transformations.

Just a note that I haven't tested any of this, so take the above code with a grain of salt. Also, many will probably tell me to use more braces and semicolons.

\$\endgroup\$

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.