Sarah
Sarah

Reputation: 78

How to find all unique values for specific key in array of objects in Javascript?

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);
 }
}, []);

Upvotes: 4

Views: 12749

Answers (6)

Abdel
Abdel

Reputation: 97

You can use 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 uniqueCities = [...new Set(employees.map((employee) => employee.city))];    
 console.log(uniqueCities); // [ 'New York', 'San Francisco', 'Chicago', 'Los Angeles' ]

Or you can use feature of Object in JavaScript as well

const uniqueCities = {};
cities = employees.map((employee) => {
uniqueCities[employee.city] = "";
return;
 });

console.log(Object.keys(uniqueCities )); // [ 'New York', 'San Francisco', 'Chicago', 'Los Angeles' ]

Upvotes: 1

PeterKA
PeterKA

Reputation: 24638

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 );

Upvotes: 3

jsN00b
jsN00b

Reputation: 3691

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.

Upvotes: 0

Andy
Andy

Reputation: 63524

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

Upvotes: 2

Pellay
Pellay

Reputation: 802

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);

Upvotes: 5

Nina Scholz
Nina Scholz

Reputation: 386560

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);

Upvotes: 2

Related Questions