0

I have an object with some nested data that I need to process programmatically to achieve a certain output. It will be the easiest to explain by demonstrating with example.

Example
I conducted an experiment and measured speed of dogs and cats running. I gathered the data in the following groups object. We can see two major categories for each group, i.e., dogs and cats. Then, in each group, we have one property about the mean average speed time of the group members, as well as individuals object that holds the data about each group member.

const groups = {
  dogs: {
    groupMeanRecord: 11.7,
    individuals: {
      lassie: {
        record: 10,
        age: 2,
      },
      slinky: {
        record: 13,
        age: 4,
      },
      toto: {
        record: 11.5,
        age: 1,
      },
      marley: {
        record: 15,
        age: 1,
      },
      beethoven: {
        record: 9,
        age: 13,
      },
    },
  },
  cats: {
    groupMeanRecord: 7.75,
    individuals: {
      grumpyCat: {
        record: 6,
        age: 4,
      },
      salem: {
        record: 9,
        age: 11,
      },
      garfield: {
        record: 5,
        age: 3,
      },
      kitty: {
        record: 11,
        age: 10,
      },
    },
  },
};

I want to figure out who are the animals, in each group, whose record values are the lowest. I decided that I want the 3 lowest in each group. Hence, my desired output, given this current data, is:

const desiredOutput = {
  dogs: 'beethoven, lassie, toto',
  cats: 'garfield, grumpyCat, salem',
};

my attempt

Below is the code that I was able to come up with and it does give the desired output. However, as I'm new to JavaScript, I believe that there has to be an easier/more straightforward way to do this.

  • step 1 – clean up the data to get just what I need

    const cleanDataArrOfArrs = Object.entries(groups).map(
      ([group, groupData]) => [
        group,
        Object.entries(groupData.individuals).map(([individual, data]) => [
          individual,
          data.record,
        ]),
      ]
    );
    //   [
    //     'dogs',
    //     [
    //       ['lassie', 10],
    //       ['slinky', 13],
    //       ['toto', 11.5],
    //       ['marley', 15],
    //       ['beethoven', 9],
    //     ],
    //   ],
    //   [
    //     'cats',
    //     [
    //       ['grumpyCat', 6],
    //       ['salem', 9],
    //       ['garfield', 5],
    //       ['kitty', 11],
    //     ],
    //   ],
    // ]
    
  • step 2 – I want to make the result of step 1 an object of objects

    • utilizing this helper function from here

      const makeObject = (arr) => {
        return Object.fromEntries(
          arr.map(([key, val]) =>
            Array.isArray(val) ? [key, makeObject(val)] : [key, val]
          )
        );
      };
      
    • calling makeObject() on cleanDataArrOfArrs

      const cleanDataObj = makeObject(cleanDataArrOfArrs)
      // {
      //   dogs: { lassie: 10, slinky: 13, toto: 11.5, marley: 15, beethoven: 9 },
      //   cats: { grumpyCat: 6, salem: 9, garfield: 5, kitty: 11 },
      // };
      
  • step 3 – now I want to sort cleanDataObj.dogs and cleanDataObj.cats properties from lowest to largest (respectively).

    • utilizing a helper function from this answer:
    const sortObj = (o) =>
      Object.fromEntries(Object.entries(o).sort((a, b) => a[1] - b[1]));
    
    • and finally:
    const myOutput = Object.fromEntries(
      Object.entries(cleanDataObj).map(([k, v]) => [
        k,
        Object.keys(sortObj(v)).slice(0, 3).join(', '),
      ])
    );
    // {
    //   dogs: 'beethoven, lassie, toto',
    //   cats: 'garfield, grumpyCat, salem',
    // };
    

My worry is that this code, although working, is neither readable nor addressing the task properly, and that there might be a much simpler way to approach this. I'd therefore be happy to learn how such a task could be otherwise done.

Emman
  • 3,695
  • 2
  • 20
  • 44

1 Answers1

1

This may be one possible solution to achieve the desired objective.

Code Snippet

// helper method
const getIndNames = obj => (
  Object.entries(obj)                   // iterate over key-value pairs
  .map(([k, v]) => ({name: k, ...v}))   // get an array of objects with 'name', 'record', 'age' props
  .sort((a, b) => a.record - b.record)  // sort the array descending order of record
  .filter((_, idx) => (idx < 3))        // filter to retain top 3
  .map(({name}) => name)                // extract just the 'name' prop
  .join(', ')                           // use '.join' to generate a comma-separated string
);

// method that iterates over each group (cats, dogs)
const fastestThree = obj => (
  Object.fromEntries(                     // convert the final-result back into object
    Object.keys(obj).map(                 // iterate over key-value pairs of 'obj'
      k => ([                             // for each key, return an array of 2 elements
        [k],                              // first is the key 'k' itself
        getIndNames(obj[k].individuals)   // second is the fastest-three cats/dogs
      ])
    )
  )
);

// data from the question
const groups = {
  dogs: {
    groupMeanRecord: 11.7,
    individuals: {
      lassie: {
        record: 10,
        age: 2,
      },
      slinky: {
        record: 13,
        age: 4,
      },
      toto: {
        record: 11.5,
        age: 1,
      },
      marley: {
        record: 15,
        age: 1,
      },
      beethoven: {
        record: 9,
        age: 13,
      },
    }
  },
  cats: {
    groupMeanRecord: 7.75,
    individuals: {
      grumpyCat: {
        record: 6,
        age: 4,
      },
      salem: {
        record: 9,
        age: 11,
      },
      garfield: {
        record: 5,
        age: 3,
      },
      kitty: {
        record: 11,
        age: 10,
      },
    },
  }
};

// invoke the method and print the result
console.log(fastestThree(groups));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Explanation

Inline comments in the above code-snippet explain the significant aspects of the solution.

jsN00b
  • 3,584
  • 2
  • 8
  • 21