0

I understand that reduce is a very powerful array method in Javascript and have seen a lot of examples but could not use it to accomplish the task below.

To group object of persons' statistics by their ages where age difference must not be greater than 5 and each group must have only maximum numbers of 3.

I have been able to achieve it with the code below

const immutable = require('../fixtures/inputs/input')
const edge = require('../fixtures/inputs/edge-input')
/**
 * This is the entry point to the program
 *
 * @param {any} input Array of student objects
 */


function classifier(input) {
    // console.log(input)
    // let returnedInput = []
    let newInput = JSON.parse(JSON.stringify(input));

    let exampleOutput = {

    }


    if (!Array.isArray(newInput)) {

        throw new Error("invalid")
    }
    if (newInput.length < 1) {
        exampleOutput = { noOfGroups: 0 }
    }
    function compare(a, b) {
        const { age: ageA, regNo: regNoA } = a
        const { age: ageB, regNo: regNoB } = b
        // const ageA = a.age
        // const ageB = b.age
        let comparison = 0;
        if (ageA > ageB) {
            comparison = 1;
        } else if (ageA < ageB) {
            comparison = -1;
        }
        return comparison
    }

    const ages = newInput.map(function (each) {
        let datDob = new Date(each.dob).getFullYear()
        return each.age = new Date().getFullYear() - datDob
    })

    sortedInput = newInput.sort(compare)
    // console.log(sortedInput)
    const getMember = (arg) => {
        let memArray = []
        // console.log(arg)
        if (arg.length == 1) {
            return arg
        }
        let i = 0;
        let j = 1;
        // console.log(arg)
        // console.log(arg.length)
        while (i <= arg.length) {

            while (j < 3) {
                //  console.log(arg[j])
                if (arg[j]) {
                    if ((arg[j].age - arg[i].age) <= 5) {

                        memArray.push(arg[j])
                    }
                }

                j++
            }

            memArray.push(arg[i])
            i++

            return memArray
        }

    }

    let i = 0;
    // console.log(sortedInput)

    while (sortedInput.length >= 1) {
        // console.log(sortedInput)
        let memberss = getMember(sortedInput)
        memberss = memberss.sort(compare)
        // let memRegSort = memberss.sort((a, b) => (a.regNo > b.regNo) ? 1 : -1) 
        memberss = memberss.sort((a, b) => (a.age > b.age) ? 1 : (a.age === b.age) ? ((a.regNo > b.regNo) ? 1 : -1) : -1)
        // return memberss
        const oldest = memberss.map(item => item.age).reduce((a, b) => Math.max(a, b))
        const sumAge = memberss.map(item => item.age).reduce((total, curVal) => total + curVal)
        const regNo = memberss.map(item => parseInt(item.regNo))
        exampleOutput[`noOfGroups`] = i + 1
        exampleOutput[`group${i + 1}`] = {}
        exampleOutput[`group${i + 1}`]['members'] = memberss
        exampleOutput[`group${i + 1}`].oldest = oldest
        exampleOutput[`group${i + 1}`].sum = sumAge

        exampleOutput[`group${i + 1}`].regNos = regNo.sort((a, b) => a > b ? 1 : -1)
        sortedInput = sortedInput.slice(memberss.length, sortedInput.length + 1)
        // console.log(sortedInput)
        // sortedInput.splice(0, memberss.length)
        // console.log(exampleOutput[`group${i + 1}`]['members'])


        i++
    }

    // console.log(exampleOutput)
    return exampleOutput
    // console.log (getMember(sortedInput))

}
const input = [
    {
        name: 'Hendrick',
        dob: '1853-07-18T00:00:00.000Z',
        regNo: '041',
    }

]
Object.freeze(edge)
const out = classifier(edge)
console.log(out)

module.exports = classifier;

input

const input = [
  {
    name: 'Hendrick',
    dob: '1853-07-18T00:00:00.000Z',
    regNo: '041',
  },
  {
    name: 'Albert',
    dob: '1910-03-14T00:00:00.000Z',
    regNo: '033',
  },
  {
    name: 'Marie',
    dob: '1953-11-07T00:00:00.000Z',
    regNo: '024',
  },
  {
    name: 'Neils',
    dob: '1853-10-07T00:00:00.000Z',
    regNo: '02',
  },
  {
    name: 'Max',
    dob: '1853-04-23T00:00:00.000Z',
    regNo: '014',
  },
  {
    name: 'Erwin',
    dob: '1854-08-12T00:00:00.000Z',
    regNo: '09',
  },
  {
    name: 'Auguste',
    dob: '1854-01-28T00:00:00.000Z',
    regNo: '08',
  },
  {
    name: 'Karl',
    dob: '1852-12-05T00:00:00.000Z',
    regNo: '120',
  },
  {
    name: 'Louis', //
    dob: '1852-08-15T00:00:00.000Z',
    regNo: '022',
  },
  {
    name: 'Arthur',
    dob: '1892-09-10T00:00:00.000Z',
    regNo: '321',
  },
  {
    name: 'Paul',
    dob: '1902-08-08T00:00:00.000Z',
    regNo: '055',
  },
  {
    name: 'William',
    dob: '1890-03-31T00:00:00.000Z',
    regNo: '013',
  },
  {
    name: 'Owen',
    dob: '1853-04-26T00:00:00.000Z',
    regNo: '052',
  },
  {
    name: 'Martin',
    dob: '1854-02-15T00:00:00.000Z',
    regNo: '063',
  },
  {
    name: 'Guye',
    dob: '1854-10-15T00:00:00.000Z',
    regNo: '084',
  },
  {
    name: 'Charles',
    dob: '1954-02-14T00:00:00.000Z',
    regNo: '091',
  },
];

module.exports = input;

output

{ noOfGroups: 8,
  group1:
   { members:
      '[{"name":"Charles","dob":"1954-02-14T00:00:00.000Z","regNo":"091","age":65},{"name":"Marie","dob":"1953-11-07T00:00:00.000Z","regNo":"024","age":66}]',
     oldest: 66,
     sum: 131,
     regNos: [ 24, 91 ] },
  group2:
   { members:
      '[{"name":"Albert","dob":"1910-03-14T00:00:00.000Z","regNo":"033","age":109}]',     oldest: 109,
     sum: 109,
     regNos: [ 33 ] },
  group3:
   { members:
      '[{"name":"Paul","dob":"1902-08-08T00:00:00.000Z","regNo":"055","age":117}]',  
     oldest: 117,
     sum: 117,
     regNos: [ 55 ] },
  group4:
   { members:
      '[{"name":"Arthur","dob":"1892-09-10T00:00:00.000Z","regNo":"321","age":127},{"name":"William","dob":"1890-03-31T00:00:00.000Z","regNo":"013","age":129}]',
     oldest: 129,
     sum: 256,
     regNos: [ 13, 321 ] },
  group5:
   { members:
      '[{"name":"Auguste","dob":"1854-01-28T00:00:00.000Z","regNo":"08","age":165},{"name":"Guye","dob":"1854-10-15T00:00:00.000Z","regNo":"084","age":165},{"name":"Erwin","dob":"1854-08-12T00:00:00.000Z","regNo":"09","age":165}]',
     oldest: 165,
     sum: 495,
     regNos: [ 8, 9, 84 ] },
  group6:
   { members:
      '[{"name":"Martin","dob":"1854-02-15T00:00:00.000Z","regNo":"063","age":165},{"name":"Max","dob":"1853-04-23T00:00:00.000Z","regNo":"014","age":166},{"name":"Hendrick","dob":"1853-07-18T00:00:00.000Z","regNo":"041","age":166}]',
     oldest: 166,
     sum: 497,
     regNos: [ 14, 41, 63 ] },
  group7:
   { members:
      '[{"name":"Neils","dob":"1853-10-07T00:00:00.000Z","regNo":"02","age":166},{"name":"Owen","dob":"1853-04-26T00:00:00.000Z","regNo":"052","age":166},{"name":"Karl","dob":"1852-12-05T00:00:00.000Z","regNo":"120","age":167}]',
     oldest: 167,
     sum: 499,
     regNos: [ 2, 52, 120 ] },
  group8:
   { members:
      '[{"name":"Louis","dob":"1852-08-15T00:00:00.000Z","regNo":"022","age":167}]', 
     oldest: 167,
     sum: 167,
     regNos: [ 22 ] } }

How can I accomplish the same using reduce. I have tried the code below

function classifier(input) {
    let newInput = JSON.parse(JSON.stringify(input));

    let exampleOutput = {
        noOfGroups:0,
        group: {
            member: [],
            oldest: 0,
            regNos: []
        }

    }
    if (!Array.isArray(newInput)) {

        throw new Error("invalid")
    }
    if (newInput.length < 1) {
        exampleOutput = { noOfGroups: 0 }
    }
    function compare(a, b) {
        const { age: ageA } = a
        const { age: ageB } = b
        return ageA-ageB
    }

    const ages = newInput.map(function (each) {
        let datDob = new Date(each.dob).getFullYear()
        return each.age = new Date().getFullYear() - datDob
    })

    sortedInput = newInput.sort(compare)
   
    const member = (arr)=>{
        let result = []
        return arr.length < 1 ? { noOfGroups: 0} : 
            arr.reduce((acc, cur, index, arr) => {
                index= index-1
                let num = 0
                // console.log(cur.age)
                let item = arr.findIndex(item => item.age +5 >= cur.age)
                item == 0 ? result.push(cur) : result
                
                result.length > 3 ? result.pop() : result
                num = num+1
                acc.noOfGroups = num
                acc[`group${num}`] = {}
                acc[`group${num}`].members = []
                acc[`group${num}`].members.push(result)
                acc[`group${num}`].oldest = result.map(item => item.age).reduce((a, b) => Math.max(a, b))
                acc[`group${num}`].regNos = result.map(item => item.age)
                // console.log(arr.slice)
                index = index-1
                return index < 0 ? member(arr.slice(acc[`group${num}`].regNos.length, 16)) : acc
                
                return acc


        }, [{noOfGroups: 0}, ])

    }
    
    return member(sortedInput)
    return exampleOutput
 

}

But got output for one group like so:

{ noOfGroups: 1,
  group1: { members: [ [Array] ], oldest: 66, regNos: [ 65, 66 ] } }
Community
  • 1
  • 1
Darotudeen
  • 1,914
  • 4
  • 21
  • 36
  • 1
    Just a side note: `let newInput = JSON.parse(JSON.stringify(input));` is a **very** poor way to clone an object. It's lossy and slow. Instead: https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript – T.J. Crowder Sep 30 '19 at 14:09

2 Answers2

0

Here's how I'd do it:

  1. First compute age from dob for every person.
  2. Then use reduce() to transform the data. If a group exists satisfying given criteria, then we add current member to the group and recalculate sum, oldest, and other properties. If not, we create a new group with current member.

const input = [ { name: 'Hendrick', dob: '1853-07-18T00:00:00.000Z', regNo: '041', }, { name: 'Albert', dob: '1910-03-14T00:00:00.000Z', regNo: '033', }, { name: 'Marie', dob: '1953-11-07T00:00:00.000Z', regNo: '024', }, { name: 'Neils', dob: '1853-10-07T00:00:00.000Z', regNo: '02', }, { name: 'Max', dob: '1853-04-23T00:00:00.000Z', regNo: '014', }, { name: 'Erwin', dob: '1854-08-12T00:00:00.000Z', regNo: '09', }, { name: 'Auguste', dob: '1854-01-28T00:00:00.000Z', regNo: '08', }, { name: 'Karl', dob: '1852-12-05T00:00:00.000Z', regNo: '120', }, { name: 'Louis', dob: '1852-08-15T00:00:00.000Z', regNo: '022', }, { name: 'Arthur', dob: '1892-09-10T00:00:00.000Z', regNo: '321', }, { name: 'Paul', dob: '1902-08-08T00:00:00.000Z', regNo: '055', }, { name: 'William', dob: '1890-03-31T00:00:00.000Z', regNo: '013', }, { name: 'Owen', dob: '1853-04-26T00:00:00.000Z', regNo: '052', }, { name: 'Martin', dob: '1854-02-15T00:00:00.000Z', regNo: '063', }, { name: 'Guye', dob: '1854-10-15T00:00:00.000Z', regNo: '084', }, { name: 'Charles', dob: '1954-02-14T00:00:00.000Z', regNo: '091', }, ];

// Compute age from DOB.
const data = input.map(item => {
  let age = new Date().getFullYear() - new Date(item.dob).getFullYear();
  return {...item, age};
});

var result = data.reduce((acc, curr) => {
  let group = Object.values(acc).find(group => group.members && group.members.length < 3 
    && group.members.every(member => Math.abs(member.age - curr.age) < 5));

  if (group) {
    group.members.push(curr);
    group.regNos.push(curr.regNo);
    group.oldest = Math.max(...group.members.map(member => member.age));
    group.sum = group.sum + curr.age; 
  } else {
    acc.noOfGroups = acc.noOfGroups + 1 || 1;
    let groupName = "group" + acc.noOfGroups;
    acc[groupName] = {
      "members": [curr],
      "oldest": curr.age,
      "sum": curr.age,
      "regNos": [curr.regNo],
    };
  }

  return acc;
}, {});

console.log(result);
Nikhil
  • 6,493
  • 10
  • 31
  • 68
0

I wrote this first using Ramda (disclaimer: I'm one of its authors.) This was the code:

const transform = pipe (
  map (addAge),
  sortBy (prop ('age')),
  makeGroups (doesItFit),
  nameGroups,
  map (constructGroup)
)

with the additional helper functions, addAge, makeGroups, doesItFit, nameGroups, and constructObj). I really like this style of transforming my output step-by-step.

But Ramda is simply a collection of helper functions (designed to allow a certain specific style of coding, to be sure) and it's easy to write versions of these ourselves.

But putting such functions as

const pipe = (...fns) => (arg) => 
  fns.reduce((o, f) => f(o), arg)

into our own utility libraries, we can build code as clean as the above relatively easily.

For this case, I ended up using a number of Ramda functions, as well as two helpers that might belong in such a library (numericSort and makeGroups.) But these are easy to write for ourselves, and we can build this function on top of them.

// Utility functions
const pipe = (...fns) => (arg) => 
  fns.reduce((o, f) => f(o), arg)

const map = (fn) => (xs) => 
  xs .map (x => fn(x))

const sort = (comparator) => (xs) => 
  xs .sort (comparator)

const numericSort = sort ((a, b) => a - b)

const sortBy = (fn) => (xs) => 
  xs .sort ((a, b, x = fn(a), y = fn(b)) => x < y ? -1 : x > y ? 1 : 0)

const sum = (xs) =>
  xs.reduce((a, b) => a + b, 0)

const prop = (name) => (obj) =>
  obj [name]

const pluck = (prop) => (xs) =>
  xs .map (x => x[prop])

const mapObject = (fn) => (obj) => 
  Object.assign(...Object.entries(obj).map(([k, v]) => ({[k]: fn(v)})))

const makeGroups = (test) => (xs) =>
  xs.reduce((groups, x) => {
    const idx = groups.findIndex(g => test (g, x))
    return idx > -1 
      ? [...groups.slice(0, idx), [...groups[idx], x], ...groups.slice(idx + 1)]
      : [...groups, [x]]
  }, [])


// Helpers
const addAge = ({dob, ...rest}) =>  
  ({...rest, dob, age: new Date().getFullYear() - new Date(dob).getFullYear()})

const nameGroups = groups =>
  groups .reduce ((i => (a, g) => ({...a, [`group${i++}`]: g}))(1), {})

const doesItFit = (group, item) =>
  group.length < 3 && group.every(x => Math.abs(x.age - item.age) < 5)

const constructGroup = (group) => ({
  members: group,
  // members: JSON.stringify(group),
  oldest: Math.max(...pluck ('age') (group)),
  sum: sum (pluck ('age') (group)),
  regNos: numericSort (map(Number) (pluck('regNo') (group))),
})


// Main function
const transform = pipe (
  map (addAge),
  sortBy (prop ('age')),
  makeGroups (doesItFit),
  nameGroups,
  mapObject (constructGroup)
)


// Demonstration
const input = [{name: "Hendrick", dob: "1853-07-18T00:00:00.000Z", regNo: "041"}, {name: "Albert", dob: "1910-03-14T00:00:00.000Z", regNo: "033"}, {name: "Marie", dob: "1953-11-07T00:00:00.000Z", regNo: "024"}, {name: "Neils", dob: "1853-10-07T00:00:00.000Z", regNo: "02"}, {name: "Max", dob: "1853-04-23T00:00:00.000Z", regNo: "014"}, {name: "Erwin", dob: "1854-08-12T00:00:00.000Z", regNo: "09"}, {name: "Auguste", dob: "1854-01-28T00:00:00.000Z", regNo: "08"}, {name: "Karl", dob: "1852-12-05T00:00:00.000Z", regNo: "120"}, {name: "Louis", dob: "1852-08-15T00:00:00.000Z", regNo: "022"}, {name: "Arthur", dob: "1892-09-10T00:00:00.000Z", regNo: "321"}, {name: "Paul", dob: "1902-08-08T00:00:00.000Z", regNo: "055"}, {name: "William", dob: "1890-03-31T00:00:00.000Z", regNo: "013"}, {name: "Owen", dob: "1853-04-26T00:00:00.000Z", regNo: "052"}, {name: "Martin", dob: "1854-02-15T00:00:00.000Z", regNo: "063"}, {name: "Guye", dob: "1854-10-15T00:00:00.000Z", regNo: "084"}, {name: "Charles", dob: "1954-02-14T00:00:00.000Z", regNo: "091"}];


console .log (
  transform (input)
)

This does not give the JSON-style members listing in the output. I find that output quite odd, but if you want it, it's simply a matter of replacing

  members: group,

with

  members: JSON.stringify(group),

in constructGroup.


You asked about the use of reduce. While there are several uses of it in this example, the key one is the function makeGroups. (This function should probably be added to Ramda. It's not very efficient, with a O (n^2) worst-case performance, but I don't know of anything more efficient. And it's obviously useful.)

makeGroups takes a binary predicate and a list of items and returns the result of grouping them by finding for each item the first group for which the predicate returns true when applied to the group and the item, adding the item to that group. If none is found, it starts a new group containing only the item. The predicate must take a group and an item and report whether the item belongs in the group. In the current case, the predicate we supply is doesItFit, which checks that the length of that group is less than three and that the age of the item is within five of all items in the group.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103