4

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

1
  • 1
    Your reduce function doesn't return anything. Noting that reduce may not be the best approach if you're also filtering. Commented Apr 4, 2022 at 15:56

6 Answers 6

6

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

Sign up to request clarification or add additional context in comments.

Comments

3

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

Comments

2

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

Comments

2

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

Comments

1

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' ]

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.