Home > Software design >  Using reduce, how to make an object directory where every key is a unique value and stores repeated
Using reduce, how to make an object directory where every key is a unique value and stores repeated

Time:05-28

How do I create a function using reduce which will take in an array of objects and return an object directory with unique keys, but allowing for repeated values?

  1. each key is the name of the person
  2. each repeated key has a nested array with a directory of numbers
  3. if there are no repeated keys, the key should holds only one object

I would like the output to look like this:

{
  Bob: {
    '0': { name: 'Bob', age: 30, voted: true },
    '1': { name: 'Bob', age: 31, voted: true },
    '2': { name: 'Bob', age: 32, voted: true },
       },
  Jake: {
    '0': { name: 'Jake', age: 20, voted: false },
    '1': { name: 'Jake', age: 32, voted: true }
       },
  Phil: { name: 'Phil', age: 21, voted: true },
  Ed: { name: 'Ed', age: 55, voted: true },
  Tami: { name: 'Tami', age: 54, voted: true },
  Mary: { name: 'Mary', age: 31, voted: false },
  Becky: { name: 'Becky', age: 43, voted: false },
  Joey: { name: 'Joey', age: 41, voted: true },
  Jeff: { name: 'Jeff', age: 30, voted: true },
  Zack: { name: 'Zack', age: 19, voted: false }
}

This is my code, the results eat one another if two repeated names happen, and it spreads the first object into the key without adding a number key to it.

let count = 0
const toNamedObj = (arr) => {
  return arr.reduce((acc, cur) => {
    let key = Object.values(cur)[0]
    if(acc.hasOwnProperty(key)){
      count   
      console.log(count)
      return {[key]: {...acc[key], [count]: cur} }
    }
    return {...acc, [key]: cur}
  } ,{})
}

const voters = [
  {name:'Bob' , age: 30, voted: true},
  {name:'Bob' , age: 31, voted: true},
  {name:'Bob' , age: 32, voted: true},
  {name:'Jake' , age: 32, voted: true},
  {name:'Kate' , age: 25, voted: false},
  {name:'Jake' , age: 20, voted: false},
  {name:'Phil' , age: 21, voted: true},
  {name:'Ed' , age:55, voted:true},
  {name:'Tami' , age: 54, voted:true},
  {name: 'Mary', age: 31, voted: false},
  {name: 'Becky', age: 43, voted: false},
  {name: 'Joey', age: 41, voted: true},
  {name: 'Jeff', age: 30, voted: true},
  {name: 'Zack', age: 19, voted: false}
];
console.log(toNamedObj(voters)); 

CodePudding user response:

Using your current approach, you'd have to be able to detect, when iterating, if the name already exists, if the object nested in it is a plain voter object (in which case it'd have to be removed and replaced with the array-like object), or if it's the array-like object. While that'd be possible, it'd be convoluted. A better approach would be to unconditionally group into arrays indexed by name, and then transform each array into either the single voter object, or into the {0: .. 1: } object afterwards.

const toNamedObj = (arr) => {
  const votersByName = {};
  for (const voter of voters) {
    votersByName[voter.name] ??= [];
    votersByName[voter.name].push(voter);
  }
  for (const [name, voters] of Object.entries(votersByName)) {
    votersByName[name] = voters.length === 1
      ? voters[0]
      : { ...voters };
  }
  return votersByName;
}

const voters = [
  {name:'Bob' , age: 30, voted: true},
  {name:'Bob' , age: 31, voted: true},
  {name:'Bob' , age: 32, voted: true},
  {name:'Jake' , age: 32, voted: true},
  {name:'Kate' , age: 25, voted: false},
  {name:'Jake' , age: 20, voted: false},
  {name:'Phil' , age: 21, voted: true},
  {name:'Ed' , age:55, voted:true},
  {name:'Tami' , age: 54, voted:true},
  {name: 'Mary', age: 31, voted: false},
  {name: 'Becky', age: 43, voted: false},
  {name: 'Joey', age: 41, voted: true},
  {name: 'Jeff', age: 30, voted: true},
  {name: 'Zack', age: 19, voted: false}
];
console.log(toNamedObj(voters));

Or by mapping the entries

const toNamedObj = (arr) => {
  const votersByName = {};
  for (const voter of voters) {
    votersByName[voter.name] ??= [];
    votersByName[voter.name].push(voter);
  }
  return Object.fromEntries(
    Object.entries(votersByName)
      .map(([name, voters]) => ([
        name,
        voters.length === 1 ? voters[0] : { ...voters }
      ]))
  );
}

const voters = [
  {name:'Bob' , age: 30, voted: true},
  {name:'Bob' , age: 31, voted: true},
  {name:'Bob' , age: 32, voted: true},
  {name:'Jake' , age: 32, voted: true},
  {name:'Kate' , age: 25, voted: false},
  {name:'Jake' , age: 20, voted: false},
  {name:'Phil' , age: 21, voted: true},
  {name:'Ed' , age:55, voted:true},
  {name:'Tami' , age: 54, voted:true},
  {name: 'Mary', age: 31, voted: false},
  {name: 'Becky', age: 43, voted: false},
  {name: 'Joey', age: 41, voted: true},
  {name: 'Jeff', age: 30, voted: true},
  {name: 'Zack', age: 19, voted: false}
];
console.log(toNamedObj(voters));

I recommend against using .reduce when the accumulator is the same value on each iteration - a plain for loop is simpler to write and understand.

CodePudding user response:

Presented below is one possible way to achieve the desired objective.

Code Snippet

// method to transform arr to desired format
const toNamedObj = arr => (
  arr.reduce(
    (acc, {name, voted, ...rest}, idx) => (
      (acc[name] ??= []).push({ name, voted, ...rest }),
      (idx === arr.length - 1 && Object.entries(acc).forEach(
          ([k, v]) => (acc[k] = (v.length === 1) ? v[0] : {...v})
      )),
      acc
    ),
    {}
  )
);
/* with explanation
// method to transform arr to desired format
const toNamedObj = arr => (
  // iterate using ".reduce()"
  arr.reduce(
    (acc, {name, voted, ...rest}, idx) => {
      // if "name" not present in "acc", set it as empty array
      acc[name] ??= [];
      // push elt into array
      acc[name].push({ name, voted, ...rest });
      // when iterating over the last item, special processing
      if (idx === arr.length - 1) {    // processed last elt in arr
        // review each value in "acc" and convert to object
        Object.entries(acc)
        .forEach(([k, v]) => {
          if (v.length === 1) {       // only one elt in value-array
            // place the elt as-is
            acc[k] = v[0];
          } else {                    // multiple elts in val-arr
            // convert val-arr to object
            acc[k] = {...v};
          }
        });
      };
      // always return the accumulator "acc" in ".reduce()" callback
      return acc;
    },
    {}        // "acc" is set as an empty object initially
  )
);
*/

const voters = [
  {name:'Bob' , age: 30, voted: true},
  {name:'Bob' , age: 31, voted: true},
  {name:'Bob' , age: 32, voted: true},
  {name:'Jake' , age: 32, voted: true},
  {name:'Kate' , age: 25, voted: false},
  {name:'Jake' , age: 20, voted: false},
  {name:'Phil' , age: 21, voted: true},
  {name:'Ed' , age:55, voted:true},
  {name:'Tami' , age: 54, voted:true},
  {name: 'Mary', age: 31, voted: false},
  {name: 'Becky', age: 43, voted: false},
  {name: 'Joey', age: 41, voted: true},
  {name: 'Jeff', age: 30, voted: true},
  {name: 'Zack', age: 19, voted: false}
];
console.log(toNamedObj(voters));
.as-console-wrapper { max-height: 100% !important; top: 0 }

Explanation

Inline comments added to the snippet above.

  • Related