Home > Software engineering >  How to find all unique values for specific key in array of objects in Javascript?
How to find all unique values for specific key in array of objects in Javascript?

Time:04-05

I have an array of javascript objects and I am trying to get an array of all unique values for a specific property in each object. I tried to do this using reduce(), my example code is below, but it results in an error that it "Cannot read properties of undefined (reading 'includes')" even though I provided an initial value of an empty array. The intended result is an array with

['New York', 'San Francisco', 'Chicago', 'Los Angeles']

The end goal is to create a bar graph with the cities on the X-axis and the calculated average wage for each city on the Y-axis, so I need the unique list of cities. Is there a way to avoid this error, or a better way to do this altogether?

const employees= [
 {id: 0, city: 'New York', wagePerHour: '15'}, 
 {id: 1, city: 'San Francisco', wagePerHour: '18'}, 
 {id: 2, city: 'New York', wagePerHour: '16'}, 
 {id: 3, city: 'Chicago', wagePerHour: '14'}, 
 {id: 4, city: 'Chicago', wagePerHour: '12'}, 
 {id: 5, city: 'San Francisco', wagePerHour: '15'}, 
 {id: 6, city: 'New York', wagePerHour: '18'}, 
 {id: 7, city: 'Los Angeles', wagePerHour: '10'}
];
const cities = employees.reduce((foundValues, nextEmployee) => {
 if(! foundValues.includes(nextEmployee.city)) {
  foundValues.push(nextEmployee.city);
 }
}, []);

CodePudding user response:

You need to return the accumulator for the next iteration or as result.

const
    employees = [{ id: 0, city: 'New York', wagePerHour: '15' }, { id: 1, city: 'San Francisco', wagePerHour: '18' }, { id: 2, city: 'New York', wagePerHour: '16' }, { id: 3, city: 'Chicago', wagePerHour: '14' }, {  id: 4, city: 'Chicago', wagePerHour: '12' }, { id: 5, city: 'San Francisco', wagePerHour: '15' }, {  id: 6, city: 'New York', wagePerHour: '18' }, { id: 7, city: 'Los Angeles', wagePerHour: '10' }],
    cities = employees.reduce((foundValues, nextEmployee) => {
        if (!foundValues.includes(nextEmployee.city)) {
            foundValues.push(nextEmployee.city);
        }
        return foundValues;
    }, []);

console.log(cities);

A shorter approach takes a Set with mapped cities for the constructor.

const
    employees = [{ id: 0, city: 'New York', wagePerHour: '15' }, { id: 1, city: 'San Francisco', wagePerHour: '18' }, { id: 2, city: 'New York', wagePerHour: '16' }, { id: 3, city: 'Chicago', wagePerHour: '14' }, {  id: 4, city: 'Chicago', wagePerHour: '12' }, { id: 5, city: 'San Francisco', wagePerHour: '15' }, {  id: 6, city: 'New York', wagePerHour: '18' }, { id: 7, city: 'Los Angeles', wagePerHour: '10' }],
    cities = Array.from(new Set(employees.map(({ city }) => city)));

console.log(cities);

CodePudding user response:

An even simpler way would be to extract all the cities names and use the Set function to build a unique array:

const employees = [{ id: 0, city: 'New York', wagePerHour: '15' }, { id: 1, city: 'San Francisco', wagePerHour: '18' }, { id: 2, city: 'New York', wagePerHour: '16' }, { id: 3, city: 'Chicago', wagePerHour: '14' }, {  id: 4, city: 'Chicago', wagePerHour: '12' }, { id: 5, city: 'San Francisco', wagePerHour: '15' }, {  id: 6, city: 'New York', wagePerHour: '18' }, { id: 7, city: 'Los Angeles', wagePerHour: '10' }]

let cities = [... new Set(employees.map(x=>x.city))];

console.log(cities);

CodePudding user response:

mapping over the data to get an array of city names and putting them into a Set to dedupe it might be a cleaner approach.

const employees=[{id:0,city:"New York",wagePerHour:"15"},{id:1,city:"San Francisco",wagePerHour:"18"},{id:2,city:"New York",wagePerHour:"16"},{id:3,city:"Chicago",wagePerHour:"14"},{id:4,city:"Chicago",wagePerHour:"12"},{id:5,city:"San Francisco",wagePerHour:"15"},{id:6,city:"New York",wagePerHour:"18"},{id:7,city:"Los Angeles",wagePerHour:"10"}];

const cities = new Set(employees.map(obj => obj.city));

console.log([...cities]);

Additional documentation

CodePudding user response:

With Array#reduce you have to return the updated previousValue. The easiest way to fix your code is to add return foundValues, which you could re-write as:

const cities = employees.reduce((foundValues, nextEmployee) => 
    foundValues.includes(nextEmployee.city) ? 
    foundValues : foundValues.concat(nextEmployee.city), []
);

However, you're free to explore other more efficient approaches, especially with the use of Array#map and [...new Set()]

const employees= [
 {id: 0, city: 'New York', wagePerHour: '15'}, 
 {id: 1, city: 'San Francisco', wagePerHour: '18'}, 
 {id: 2, city: 'New York', wagePerHour: '16'}, 
 {id: 3, city: 'Chicago', wagePerHour: '14'}, 
 {id: 4, city: 'Chicago', wagePerHour: '12'}, 
 {id: 5, city: 'San Francisco', wagePerHour: '15'}, 
 {id: 6, city: 'New York', wagePerHour: '18'}, 
 {id: 7, city: 'Los Angeles', wagePerHour: '10'}
];
const cities = employees.reduce((foundValues, nextEmployee) => {
   if(!foundValues.includes(nextEmployee.city)) {
      foundValues.push(nextEmployee.city);
   }
   return foundValues;
}, []);

console.log( cities );

rewrite

const employees= [
 {id: 0, city: 'New York', wagePerHour: '15'}, 
 {id: 1, city: 'San Francisco', wagePerHour: '18'}, 
 {id: 2, city: 'New York', wagePerHour: '16'}, 
 {id: 3, city: 'Chicago', wagePerHour: '14'}, 
 {id: 4, city: 'Chicago', wagePerHour: '12'}, 
 {id: 5, city: 'San Francisco', wagePerHour: '15'}, 
 {id: 6, city: 'New York', wagePerHour: '18'}, 
 {id: 7, city: 'Los Angeles', wagePerHour: '10'}
];
const cities = employees.reduce((foundValues, nextEmployee) => 
    foundValues.includes(nextEmployee.city) ? 
    foundValues : foundValues.concat(nextEmployee.city), []
);

console.log( cities );

CodePudding user response:

The excellent answers already provided are accurate and to-the-point in addressing what precisely is required. This one, deviates from those in order to achieve the end goal.

The end goal is to create a bar graph with the cities on the X-axis and the calculated average wage for each city on the Y-axis

Creating two separate arrays, one with x-axis values and then a separate one with y-axis values may be accomplished quicker by combining both the required logic into the same .reduce() iteration.

// this method directly gets the x-axis and y-axis info required
const getXYaxisData = arr => (
  Object.values(
    arr.reduce(                           // this '.reduce' provides an object
      (fin, {city, wagePerHour}) => ({
        ...fin,
        [city]: {
          city,                           // object has props 'city', 'wagePerHour', 'count'
          wagePerHour: (
            (fin[city]?.wagePerHour ?? 0)  
             wagePerHour
          ),
          count: (fin[city]?.count ?? 0)   1
        }
      }),
      {}
    )
  ).map(                                    // this '.map' transforms the 'values' of the object
    ({city, wagePerHour, count}) => ({
      xAxis: city,
      yAxis: (wagePerHour/count).toFixed(2)
    })
  )
);


const employees= [
 {id: 0, city: 'New York', wagePerHour: '15'}, 
 {id: 1, city: 'San Francisco', wagePerHour: '18'}, 
 {id: 2, city: 'New York', wagePerHour: '16'}, 
 {id: 3, city: 'Chicago', wagePerHour: '14'}, 
 {id: 4, city: 'Chicago', wagePerHour: '12'}, 
 {id: 5, city: 'San Francisco', wagePerHour: '15'}, 
 {id: 6, city: 'New York', wagePerHour: '18'}, 
 {id: 7, city: 'Los Angeles', wagePerHour: '10'}
];

const result = getXYaxisData(employees);
// console.log('combined-result: ', result);
console.log('x-axis array: ', result.map(({xAxis}) => xAxis));
console.log('y-axis array: ', result.map(({yAxis}) => yAxis));
.as-console-wrapper { max-height: 100% !important; top: 0 }

The above answer provides both x-axis and y-axis data.

  • Related