¿Cómo creo una función usando reduce que tomará una matriz de objetos y devolverá un directorio de objetos con claves únicas, pero que permita valores repetidos?
Me gustaría que la salida se viera así:
{ 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 } }Este es mi código, los resultados se comen entre sí si ocurren dos nombres repetidos, y distribuye el primer objeto en la clave sin agregarle una clave numérica.
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));Usando su enfoque actual, debería poder detectar, al iterar, si el nombre ya existe, si el objeto anidado en él es un objeto de votante simple (en cuyo caso tendría que eliminarse y reemplazarse con el objeto similar a una matriz), o si es el objeto similar a una matriz. Si bien eso sería posible , sería complicado. Un mejor enfoque sería agrupar incondicionalmente en matrices indexadas por nombre y luego transformar cada matriz en el objeto de votante único o en el objeto {0: .. 1: } después.
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));O mapeando las entradas
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)); Recomiendo no usar .reduce cuando el acumulador tiene el mismo valor en cada iteración: un bucle simple es más fácil for escribir y comprender.
A continuación se presenta una posible forma de lograr el objetivo deseado.
Fragmento de código
// 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 }Explicación
Se agregaron comentarios en línea al fragmento anterior.