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.