Recursive array of property values in array of objects - javascript

What I need is an array of property values, recursively collected from an array of objects, this is what I mean:
const regions = [{
name: 'Europe',
subRegions: [{
name: 'BeNeLux',
territories: [{
code: 'NL',
name: 'Netherlands'
}, {
code: 'DE',
name: 'Germany'
}, {
code: 'LU',
name: 'Luxembourgh'
}]
}],
territories: [{
code: 'UK',
name: 'United Kingdom'
}, {
code: 'AL',
name: 'Albania'
}, {
code: 'ZW',
name: 'Switzerland'
}]
}]
I want to get an array of all the country codes in the regions array.
So like:
const expectedOutput = ['NL', 'DE', 'LU', 'AL', 'ZW', 'UK'];
This what I have tried, which partly works but it's not collecting it correctly (I'm also very curious for exploring different / functional setups to solve this problem)
const getAllTerritoryCodesFromRegions = regions => {
return regions
.reduce(function r (output, region) {
if (region?.subRegions?.length) {
output.subRegions = region.subRegions.reduce(r, output)
}
if (region?.territories?.length) {
output = [
...output,
...region.territories.map(t => t.code)
]
}
return output
}, [])
}

You could reduce the array by looking for arrays and return the codes.
function getCodes(array) {
return array.reduce((r, o) => {
if ('code' in o) {
r.push(o.code);
return r;
}
Object.values(o).forEach(v => {
if (Array.isArray(v)) r.push(...getCodes(v));
});
return r;
}, []);
}
const
regions = [{ name: 'Europe', subRegions: [{ name: 'BeNeLux', territories: [{ code: 'NL', name: 'Netherlands' }, { code: 'DE', name: 'Germany' }, { code: 'LU', name: 'Luxembourgh' }] }], territories: [{ name: 'United Kingdom', code: 'UK' }, { name: 'AL', code: 'Albania' }, { name: 'ZW', code: 'Switzerland' }] }],
codes = getCodes(regions);
console.log(codes);

Assuming that the code properties should always contain the country codes:
It would probably be easier to create one array on the first call, which gets recursively passed down as a parameter, than to create an array for every call and try to combine it later. Then you just need to forEach over the regions and territories and push the code to that array:
const regions = [{
name: 'Europe',
subRegions: [{
name: 'BeNeLux',
territories: [{
code: 'NL',
name: 'Netherlands'
}, {
code: 'DE',
name: 'Germany'
}, {
code: 'LU',
name: 'Luxembourgh'
}]
}],
territories: [{
name: 'United Kingdom',
code: 'UK'
}, {
code: 'AL',
name: 'Albania'
}, {
code: 'ZW',
name: 'Switzerland'
}]
}];
const getAllTerritoryCodesFromRegions = (regions, allCodes=[]) => {
regions.forEach(({ territories, subRegions }) => {
if (territories) {
territories.forEach(({ code }) => {
allCodes.push(code);
});
}
if (subRegions) {
getAllTerritoryCodesFromRegions(subRegions, allCodes);
}
});
return allCodes;
};
console.log(
getAllTerritoryCodesFromRegions(regions)
);

You could do it with a recursive method
const getAllTerritoryCodesFromRegions = array => {
const output = [];
array.forEach(item => {
for (const key in item) {
if (key === 'code') {
output.push(item[key]);
} else if (item.hasOwnProperty(key) && Array.isArray(item[key])) {
const childOutput = getAllTerritoryCodesFromRegions(item[key]);
output.push(...childOutput);
}
}
});
return output;
}
You can find a working example here jsfiddle. However in the dataset from your example you messed up some names and codes.

Related

fillter arrays of objects

i have two arrays.
const department = [
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' },
];
const models = [
{
id: '23',
name: 'model1',
departments: [{ id: '1', name: 'department1' }],
},
{
id: '54',
name: 'model2',
departments: [
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' },
],
},
];
i need to render accordions with department names and accordion details with matching models names. My question is how to filter those arrays to get models
We can map through the departments array, and add a models property that equals the models array, but filtered only to the ones that contain a matching department id.
const departments = [
{ id: "1", name: "department1" },
{ id: "2", name: "department2" },
];
const models = [
{
id: "23",
name: "model1",
departments: [{ id: "1", name: "department1" }],
},
{
id: "54",
name: "model2",
departments: [
{ id: "1", name: "department1" },
{ id: "2", name: "department2" },
],
},
];
const getDepartmentsWithModels = () => {
return departments.map((department) => {
return {
...department,
models: models.filter((model) => {
const modelDepartmentIds = model.departments.map(({ id }) => id);
return modelDepartmentIds.includes(department.id);
}),
};
});
};
console.log(getDepartmentsWithModels());
// [ { id: '1', name: 'department1', models: [ [Object], [Object] ] },
// { id: '2', name: 'department2', models: [ [Object] ] } ]```
I've built some code, which iterates over the departments. For each department it iterates the models and for each model it checks if the department is within the model departments.
const department =
[
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' }
]
const models =
[
{
id: '23',
name: 'model1',
departments: [{ id: '1', name: 'department1' }]
},
{
id: '54',
name: 'model2',
departments: [{ id: '1', name: 'department1' },{ id: '2', name: 'department2' }]
}
]
department.forEach( dep => {
console.log(`Department: ${dep.name}`)
models.forEach(model => {
if (model.departments.find(modelDep => dep.id===modelDep.id)) {
console.log(` Model: ${model.name}`)
}
})
})
If you could change your data objects, then your code could be much smoother.
I've changed your data objects slightly by just reducing the departments in a model to be an array of department id's. This code iterates over the departments. For each department it filters the models and iterates over the filtered models to output them to the console. This is lesser code and provides much better performance.
const department =
[
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' }
]
const models =
[
{
id: '23',
name: 'model1',
departments: ['1']
},
{
id: '54',
name: 'model2',
departments: ['1', '2']
}
]
department.forEach( dep => {
console.log(`Department: ${dep.name}`)
models.filter(model => model.departments.includes(dep.id)).forEach(model => {
console.log(` Model: ${model.name}`)
})
})
There are two solutions.
Using Array.reduce() --> returns an object where the key is department name and value is an array of the names of matching models:
let data1 = models.reduce((res, curr) => {
curr.departments.forEach(dep => {
if (!res[dep.name]) {
res[dep.name] = [curr.name]
} else {
if (!res[dep.name].includes(curr.name)) {
res[dep.name].push(curr.name);
}
}
})
return res;
}, {});
Using map and filter --> returns an array of kind:
[{department: [names of the models]},...]
let data2 = department.map(dep => {
let matchingModels = models.filter(model => {
return model.departments.filter(modDep => {
return modDep.name === dep.name;
}).length > 0;
}).map(mod => {
return mod.name;
});
return {
department: dep.name,
models: matchingModels
}
});

How does one efficiently filter the bottom most items of a nested array and object structure while also keeping the path info to such items?

I have an array of data as follows.
An array of continents, which each has an array of regions and which each has an array of countries. E.g.
continents:[
{
Name: 'Europe',
Regions: [
{
Name: Western Europe,
Countries: [
{
Name: 'United Kingdom'
},
{
Name: 'Republic of Ireland'
}
]
}
]
}
]
I have a search field in which the user can search for a country.
What I need to do is to be able to filter the results to show only the search for country but also display the region and continent.
E.g. if the user searches for 'United' it would display
Europe
-> Western Europe
-> **United** Kingdom
Americas
-> North America
-> **United** States of America
Ive hit a wall. Any advice would be appreciated.
Thanks
Looking at the given data structure and the OP's task the first thing one has to do, is to (exactly once) reassemble or to reduce the given structure into a flat list of country items where each item besides its country field also features the country's region and continent properties ... something like this ...
... ,{
country: "Republic of Ireland",
region: "Western Europe",
continent: "Europe"
}, {
country: "Indonesia",
region: "Southeast Asia",
continent: "Asia"
}, ...
The still open search/query task then can be implemented in a very straightforward way of filtering such a flat list of country items by e.g. looking for any item's lowercase name which includes the likewise lowercase name query ...
function queryCountryListByName(nameQuery) {
return listOfCountryItems.filter(item =>
item.country.toLowerCase().includes(nameQuery.toLowerCase())
);
}
function createAndCollectCountryItemWithContext(list, item) {
//const context = this; // e.g. { continent: 'Europe', region: 'Western Europe' }
return list.concat(
Object.assign({ country: item.Name }, this)
);
}
function createAndCollectCountryItemFromRegionWithContext(list, item) {
//const context = this; // e.g. { continent: 'Europe' }
return list.concat(
item.Countries.reduce(
createAndCollectCountryItemWithContext.bind(
Object.assign({ region: item.Name }, this)
), []
)
);
}
function createAndCollectCountryItemFromRegionAndContinent(list, item) {
return list.concat(
item.Regions.reduce(
createAndCollectCountryItemFromRegionWithContext.bind({
continent: item.Name,
}), []
)
);
}
const contriesGroupedByRegiosAndContinents = [{
Name: 'Europe',
Regions: [{
Name: 'Western Europe',
Countries: [{
Name: 'United Kingdom',
}, {
Name: 'Republic of Ireland',
}],
}],
}, {
Name: 'Asia',
Regions: [{
Name: 'Southeast Asia',
Countries: [{
Name: 'Indonesia',
}, {
Name: 'Philippines',
}],
}],
}, {
Name: 'Americas',
Regions: [{
Name: 'North America',
Countries: [{
Name: 'United States of America',
}, {
Name: 'Canada',
}],
}],
}];
const listOfCountryItems = contriesGroupedByRegiosAndContinents.reduce(
createAndCollectCountryItemFromRegionAndContinent, []
);
console.log('listOfCountryItems ...', listOfCountryItems);
function queryCountryListByName(nameQuery) {
return listOfCountryItems.filter(item =>
item.country.toLowerCase().includes(nameQuery.trim().toLowerCase())
);
}
console.log(
"queryCountryListByName(' united ') ...",
queryCountryListByName(' united ')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
We could start by filtering continents using Array.some (for both Regions and Countries), then looping over the matching continents to log the results:
// The string to search for.
let str = "United"
continents = [
{
Name: 'Europe',
Regions: [
{
Name: 'Western Europe',
Countries: [
{
Name: 'United Kingdom'
},
{
Name: 'Republic of Ireland'
}
]
}
]
},
{
Name: 'Americas',
Regions: [
{
Name: 'North America',
Countries: [
{
Name: 'Canada'
},
{
Name: 'United States of America'
}
]
}
]
}
]
// Find all continents with matching countries
let matchingContinents = continents.filter(continent => continent.Regions.some(region => region.Countries.some(country => country.Name.includes(str))));
for(let continent of matchingContinents) {
console.log(continent.Name);
for(let region of continent.Regions) {
console.log(" -> " + region.Name);
for(let country of region.Countries) {
if (country.Name.includes(str)) {
console.log(" -> " + country.Name.replace(str, '**' + str + '**'));
}
}
}
}
So from what I understand, you want to take your big array of objects and return a filtered version with the same structure? If so, you could do it like this.
const continents = [{
Name: 'Europe',
Regions: [{
Name: 'Western Europe',
Countries: [{
Name: 'United Kingdom',
}, {
Name: 'Republic of Ireland',
}],
}],
}, {
Name: 'Asia',
Regions: [{
Name: 'Southeast Asia',
Countries: [{
Name: 'Indonesia',
}, {
Name: 'Philippines',
}],
}],
}, {
Name: 'Americas',
Regions: [{
Name: 'North America',
Countries: [{
Name: 'United States of America',
}, {
Name: 'Canada',
}],
}],
}];
const result = continents.map(con => {
if(con.Regions.some(reg => reg.Countries.some(c => c.Name.startsWith('United')))){
return {
...con,
Regions: con.Regions.map(reg => {
if(reg.Countries.some(c => c.Name.startsWith('United'))){
return {
...reg,
Countries: reg.Countries.filter(c => c.Name.startsWith('United'))
}
}
})
}
}
});
console.log(result);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Obviously you would want to replace 'United' with whatever your search term is.

How to re-map an array to use in Ant Design Tree

I have this code structure:
const data = [
{
name: 'Lebron',
sports: 'Basketball',
},
{
name: 'Durant',
sports: 'Basketball',
},
{
name: 'Federrer',
sports: 'Tennis',
},
{
name: 'Nadal',
sports: 'Tennis',
},
];
and I'm trying to transform it into this:
const treeData = [
{
title: 'Select All',
key: 'All',
children: [
{
title: 'Basketball',
key: 'Basketball',
children: [
{
title: 'Lebron',
key: 'Lebron',
},
{
title: 'Durant',
key: 'Durant',
},
],
},
{
title: 'Tennis',
key: 'Tennis',
children: [
{
title: 'Federrer',
key: 'Federrer',
},
{
title: 'Nadal',
key: 'Nadal',
},
],
},
],
},
];
to use in Ant Design Tree link.
Right now my plan is to get all the sports like this:
let sports = data.map(({ sports }) => sports);
sports = [...new Set(sports)];
but after that I have no idea on what should be my next step to achieve the treeData
You can use .reduce() to accumulate all sports to a Map, where each sport is a key and each value for the sport is an array of associated names. Once you have built the Map, you can use Array.from() to convert your map into your desired children array. This can be done by providing a mapping function as the second argument to Array.from(), and using it to convert each entry ([key, value]) pair to an object of your desired structure.
See example below:
const data = [ { name: 'Lebron', sports: 'Basketball', }, { name: 'Durant', sports: 'Basketball', }, { name: 'Federrer', sports: 'Tennis', }, { name: 'Nadal', sports: 'Tennis', }, ];
const children = Array.from(data.reduce((m, {name, sports}) =>
m.set(sports, [...(m.get(sports) || []), name])
, new Map), ([key, arr]) => ({title: key, key, children: arr.map(title => ({title, key: title}))}));
const treeData = [{title: 'Select All', key: 'All', children}];
console.log(treeData);
const data = [
{
name: 'Lebron',
sports: 'Basketball',
},
{
name: 'Durant',
sports: 'Basketball',
},
{
name: 'Federrer',
sports: 'Tennis',
},
{
name: 'Nadal',
sports: 'Tennis',
},
];
const dataMap: Record<string, string[]> = {};
data.forEach(({ name, sports }) => {
dataMap[sports] ??= []
dataMap[sports].push(name)
})
const treeData = [{
title: 'Select All',
key: 'All',
children: Object.entries(dataMap).map(([sport, names]) => ({
title: sport,
key: sport,
children: names.map(name => ({
title: name,
key: name
}))
}))
}]
Here's a two-steps way of get the children of your final object:
First, use a reduce to create an object of sports/children, where children matches what you have in your final output:
const middle = data.reduce((transformed, item) => {
if (!transformed[item.sports]) {
transformed[item.sports] = [];
}
transformed[item.sports].push({
title: item.name,
key: item.name
});
return transformed;
}, {});
Then map the results of the reduce function to reshape the object:
const results = Object.entries(middle).map(([key, children]) => ({
title: key,
key,
children
}));

Get all values from array of objects into array [duplicate]

What I need is an array of property values, recursively collected from an array of objects, this is what I mean:
const regions = [{
name: 'Europe',
subRegions: [{
name: 'BeNeLux',
territories: [{
code: 'NL',
name: 'Netherlands'
}, {
code: 'DE',
name: 'Germany'
}, {
code: 'LU',
name: 'Luxembourgh'
}]
}],
territories: [{
code: 'UK',
name: 'United Kingdom'
}, {
code: 'AL',
name: 'Albania'
}, {
code: 'ZW',
name: 'Switzerland'
}]
}]
I want to get an array of all the country codes in the regions array.
So like:
const expectedOutput = ['NL', 'DE', 'LU', 'AL', 'ZW', 'UK'];
This what I have tried, which partly works but it's not collecting it correctly (I'm also very curious for exploring different / functional setups to solve this problem)
const getAllTerritoryCodesFromRegions = regions => {
return regions
.reduce(function r (output, region) {
if (region?.subRegions?.length) {
output.subRegions = region.subRegions.reduce(r, output)
}
if (region?.territories?.length) {
output = [
...output,
...region.territories.map(t => t.code)
]
}
return output
}, [])
}
You could reduce the array by looking for arrays and return the codes.
function getCodes(array) {
return array.reduce((r, o) => {
if ('code' in o) {
r.push(o.code);
return r;
}
Object.values(o).forEach(v => {
if (Array.isArray(v)) r.push(...getCodes(v));
});
return r;
}, []);
}
const
regions = [{ name: 'Europe', subRegions: [{ name: 'BeNeLux', territories: [{ code: 'NL', name: 'Netherlands' }, { code: 'DE', name: 'Germany' }, { code: 'LU', name: 'Luxembourgh' }] }], territories: [{ name: 'United Kingdom', code: 'UK' }, { name: 'AL', code: 'Albania' }, { name: 'ZW', code: 'Switzerland' }] }],
codes = getCodes(regions);
console.log(codes);
Assuming that the code properties should always contain the country codes:
It would probably be easier to create one array on the first call, which gets recursively passed down as a parameter, than to create an array for every call and try to combine it later. Then you just need to forEach over the regions and territories and push the code to that array:
const regions = [{
name: 'Europe',
subRegions: [{
name: 'BeNeLux',
territories: [{
code: 'NL',
name: 'Netherlands'
}, {
code: 'DE',
name: 'Germany'
}, {
code: 'LU',
name: 'Luxembourgh'
}]
}],
territories: [{
name: 'United Kingdom',
code: 'UK'
}, {
code: 'AL',
name: 'Albania'
}, {
code: 'ZW',
name: 'Switzerland'
}]
}];
const getAllTerritoryCodesFromRegions = (regions, allCodes=[]) => {
regions.forEach(({ territories, subRegions }) => {
if (territories) {
territories.forEach(({ code }) => {
allCodes.push(code);
});
}
if (subRegions) {
getAllTerritoryCodesFromRegions(subRegions, allCodes);
}
});
return allCodes;
};
console.log(
getAllTerritoryCodesFromRegions(regions)
);
You could do it with a recursive method
const getAllTerritoryCodesFromRegions = array => {
const output = [];
array.forEach(item => {
for (const key in item) {
if (key === 'code') {
output.push(item[key]);
} else if (item.hasOwnProperty(key) && Array.isArray(item[key])) {
const childOutput = getAllTerritoryCodesFromRegions(item[key]);
output.push(...childOutput);
}
}
});
return output;
}
You can find a working example here jsfiddle. However in the dataset from your example you messed up some names and codes.

Filter array of objects by object property

I got array of objects
const countryList = [
{ name: 'Afghanistan', id: 'AF' },
{ name: 'Åland Islands', id: 'AX' },
{ name: 'Albania', id: 'AL' },
{ name: 'Algeria', id: 'DZ' }]
I want to filter the array by object "id" and get name
Here is what I have already done and it is working
getName = (id) => {
let name=[]
for (var i = 0; i < countryList.length ; i++) {
if (countryList[i].id === id) {
name.push(countryList[i]);
}
}
console.log(name[0].name)
}
Is there any better way of doing it?
You could find the name, if id is unique and take for unknow items a default object.
const
getName = id => (countryList.find(o => o.id === id) || {}).name,
countryList = [{ name: 'Afghanistan', id: 'AF' }, { name: 'Åland Islands', id: 'AX' }, { name: 'Albania', id: 'AL' }, { name: 'Algeria', id: 'DZ' }];
console.log(getName('AL'));
console.log(getName('UK'));
You could make use of find array method:
const countryList = [
{ name: 'Afghanistan', id: 'AF' },
{ name: 'Åland Islands', id: 'AX' },
{ name: 'Albania', id: 'AL' },
{ name: 'Algeria', id: 'DZ' }];
getName = (id) => {
let country = countryList.find(c=>c.id === id);
return country !== undefined
? country.name
: 'not found';
}
console.log(getName('DZ'));
Note here that the getName returns now a string and doesn't have any side effects like logging the found value in the console. Also the function now does exactly what it's name says, just gets the name.
For further info regrading this method, please have a look here.
You don't need the "name" array.
getName = (id) => {
for (var i = 0; i < countryList.length ; i++) {
if (countryList[i].id === id) {
console.log(name[i].name);
}
}
}
You can use find() to get the object and then return the name of that object.
const countryList = [ { name: 'Afghanistan', id: 'AF' }, { name: 'Åland Islands', id: 'AX' }, { name: 'Albania', id: 'AL' }, { name: 'Algeria', id: 'DZ' } ]
const getName = id => (countryList.find(x => x.id === id) || {}).name
console.log(getName('AX'))
console.log(getName('DZ'))
I think this works too
const countryList = [
{ name: 'Afghanistan', id: 'AF' },
{ name: 'Åland Islands', id: 'AX' },
{ name: 'Albania', id: 'AL' },
{ name: 'Algeria', id: 'DZ' }];
getName=(id)=>{
countryList.filter(item=>{item.id===id})
console.log(countryList[0].name)
}
You could do with array.some for performance reasons, if country id is unique.
getCountryName = (countryList, id) => {
name = null;
countryList.some((x, idx) => {
result = false;
if(x.id === id) {
name = x.name;
result = true;
}
return result;
});
return name;
}
Usage is
getCountryName(countryList, 'AF')
Result is
'Afghanistan'

Categories

Resources