Recursive counting in arbitrary nested object - javascript

I'm attempting to do some data visualization and dealing with this dataset. Object with arbitrary nested objects. I'm trying to count how many times different values appear in a key. This is just a snippet of the dataset, in the original the owns nested objects go 7+ levels deep.
Example dataset I'm working with:
var companyData = [{
company: 'Pepsico',
type: 'parent',
owns: [
{
company: 'Cheetos',
type: 'chips',
owns: [{
company: 'CheezyChipCo',
type: 'chips',
owns: []
}]
},
{
company: 'Gatorade',
type: 'drink',
owns: [{
company: 'Powerade',
type: 'drink',
owns: []
}]
},
],
}];
I'm thinking I'll have to do a Recursion or a Flatten type operation. So I can put all the type values into an array like this.
What I'm trying to achieve:
[ 'drink', 'drink', 'chips', 'chips', 'parent' ]
I need to open up owns so I can properly count the type values. I feel like there are two ways I can go about it. Either recursion to go deep into an object. OR flatten the objects, so that all the nests are on the same level. I'll probably use Object[keys] in combination with .filter, .some, or .reduce. But I am so stumped on how and in what order, and would love some help! Here's my psuedo:
if (type of object's key is an Object)
&& if (object's key === "type")
then push that type.value to an array
else if (type of object's key !=== object)
then just return the new sorted array
Sorry, real frontend dev hours. I don't know if that made sense and if posting all my failed code attempts would help.

Using straightforward recursion...
var companyData = [{ company: 'Pepsico', type: 'parent', owns: [{ company: 'Cheetos', type: 'chips', owns: [{ company: 'CheezyChipCo', type: 'chips', owns: [] }] }, { company: 'Gatorade', type: 'drink', owns: [{ company: 'Powerade', type: 'drink', owns: [] }] },], }];
function mapTypes(arr, acc = []) {
for (const o of arr) {
acc.push(o.type);
if (o.owns.length > 0) {
acc = mapTypes(o.owns, acc)
}
}
return acc;
}
console.log(mapTypes(companyData));

You can write a simple recursive flatten -
const flatten = ({ type, owns = [] }) =>
[ type, ...owns.flatMap(flatten) ]
const input =
[{company:'Pepsico',type:'parent',owns:[{company:'Cheetos',type:'chips',owns:[{company:'CheezyChipCo',type:'chips',owns:[]}]},{company:'Gatorade',type:'drink',owns:[{company:'Powerade',type:'drink',owns:[]}]}]}]
console.log(input.flatMap(flatten))
[
"parent",
"chips",
"chips",
"drink",
"drink"
]

Here is my solution. You can do it with a reduce function.
var companyData = [{
company: 'Pepsico',
type: 'parent',
owns: [
{
company: 'Cheetos',
type: 'chips',
owns: [{
company: 'CheezyChipCo',
type: 'chips',
owns: []
}]
},
{
company: 'Gatorade',
type: 'drink',
owns: [{
company: 'Powerade',
type: 'drink',
owns: []
}]
},
],
}];
let arr = []
const formattedData = (data) => data.reduce((acc, curr) => {
arr.push(curr.type);
if (Array.isArray(curr.owns)) {
formattedData(curr.owns)
}
return arr;
}, []);
console.log(formattedData(companyData))

Related

Construct an array of objects after doing a filter for an array in JavaScript

I was able to filter the array but when I'm trying to create an array of objects out of the filtered data, the result appears to be undefined. How do I construct an array of objects in the below format. Could anyone please help?
[{ brand: 'BMW'}, { brand: 'Audi'}]
Snippet
const cars = [{
name: 'BMW',
type: 'Sedan'
}, {
name: 'Audi',
type: 'SUV'
}, {
name: 'BMW',
type: 'SUV'
}]
const result = cars.filter(({
type
}) => type === 'SUV').map((car) => {
brand: car.name
})
console.log(result)
If you want to return an object literal from the arrow function, you need to enclose that object literal in parentheses to distinguish it from a code block, which also happens to be enclosed in curly braces:
result = cars.map(car => ({
brand: car.name
}));
It's funny that your code doesn't cause an error. It's just because there's a label syntax in JavaScript, so your code inside the arrow function basically creates a brand label to a loose value of car.name.
Basically you need to wrap the object in parentheses to distinguish it from a block statement.
const
cars = [{ name: 'BMW', type: 'Sedan' }, { name: 'Audi', type: 'SUV' }, { name: 'BMW', type: 'SUV' }],
result = cars
.filter(({ type }) => type === 'SUV')
.map(({ name: brand }) => ({ brand }));
// ^^^^^^^^^^^ wrap it
console.log(result);
You are missing a pair of parenthesis around the new implicitly returned object from the map function. This is a tricky syntax of es6.
const cars = [{
name: 'BMW',
type: 'Sedan'
}, {
name: 'Audi',
type: 'SUV'
}, {
name: 'BMW',
type: 'SUV'
}]
const result = cars.filter(({
type
}) => type === 'SUV').map((car) => ({
brand: car.name
}))
console.log(result)
For doing this you can declare a variable and return it.
const cars = [{
name: 'BMW',
type: 'Sedan'
}, {
name: 'Audi',
type: 'SUV'
}, {
name: 'BMW',
type: 'SUV'
}]
const result = cars.filter(({
type
}) => type === 'SUV').map((car) => {
let obj = {brand: car.name}
return obj
})
console.log(result)

Merge 2 arrays of objects using ES6 and display objects in one array that doesn't appear in the other

I have 2 arrays of objects. One is the "master" containing objects with a name and a data field. The other array ("updates") is used to mutate the first one and contains objects of the same form.
I want to (a) merge "updates" into "master" but only if there is a matching name field (b) display a list of objects in the "updates" array that doesn't appear in the master.
I've been able to do it with plain old JavaScript but I would like to do it more elegantly with ES6.
let arr1 = [
{ name: "aaaa", data: 56 },
{ name: "bbbb", data: 34 },
{ name: "cccc", data: 25 },
{ name: "dddd", data: 78 },
{ name: "eeee", data: 12 },
{ name: "ffff", data: 12 }
];
let arr1 = [
{ name: "cccc", data: 101 },
{ name: "err1", data: 0 },
{ name: "dddd", data: 204 },
{ name: "err2", data: 0 }
];
// some clever ES6 code that generates result[] and errors[] arrays
result = [
{ name: "aaaa", data: 56 },
{ name: "bbbb", data: 34 },
{ name: "cccc", data: 101 },
{ name: "dddd", data: 204 },
{ name: "eeee", data: 12 },
{ name: "ffff", data: 12 }
]
errors = [
{ name: "err1", data: 0 },
{ name: "err2", data: 0 }
]
I expect there is a very elegant 2 or 3 line solution but so far, I have been unable to make it work.
Use a .forEach() to loop over the updates Array, use Destructuring to take the name and data values
Use .findIndex() to check if the name exists in Update - If it does, update that entry. If it doesn't, push the update to Errors Array instead
const master = [
{ name: "aaaa", data: 56 },
{ name: "bbbb", data: 34 },
{ name: "cccc", data: 25 },
{ name: "dddd", data: 78 },
{ name: "eeee", data: 12 },
{ name: "ffff", data: 12 }
];
const updates = [
{ name: "cccc", data: 101 },
{ name: "err1", data: 0 },
{ name: "dddd", data: 204 },
{ name: "err2", data: 0 }
];
const errors = [];
updates.forEach(({name, data}) => {
let index = master.findIndex(x => x.name === name);
index !== -1 ? master[index] = {name, data} : errors.push({name, data});
});
console.log(master);
console.log(errors);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Adopting a set-based approach to this, you can provide functions to convert to/from your name/data pairs into a Map:
const toMapEntries = arr => arr.map(({ name, data }) => [name, data]);
const toMap = arr => new Map(toMapEntries(arr));
const fromMap = map => [...map].map(([k, v]) => ({ name: k, data: v }));
Now you can create some reusable functions that work over maps, first to merge all existing keys:
const merge = (map1, map2) =>
new Map([...map1, ...[...map2].filter(([k]) => map1.has(k))]);
...then to split-out non-existing keys:
const diff = (map1, map2) => new Map([...map1].filter(([k]) => !map2.has(k)));
using them as follows:
const map1 = toMap(arr1);
const map2 = toMap(arr2);
const mergedResults = fromMap(merge(map1, map2));
const errorResults = fromMap(diff(map2, map1));
The advantage of using the map-based approach is that key-lookup on a map is an O(1) operation, meaning that the merge and diff functions have linear time-complexity. This might be important with large datasets.

How to groupBy array of objects based on properties in vanilla javascript

How do you groupBy array of objects based on specific properties in vanilla javascript? For example this given:
const products = [
{
category: "Sporting Goods",
price: "$49.99",
stocked: true,
name: "Football"
},
{
category: "Sporting Goods",
price: "$9.99",
stocked: true,
name: "Baseball"
},
{
category: "Sporting Goods",
price: "$29.99",
stocked: false,
name: "Basketball"
},
{
category: "Electronics",
price: "$99.99",
stocked: true,
name: "iPod Touch"
},
{
category: "Electronics",
price: "$399.99",
stocked: false,
name: "iPhone 5"
},
{ category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7" }
];
i want to run a reduce function that would result to a new array of objects like this:
Intended Output:
const categorize = [
{
category:"Sporting Goods",
products: [
{
name:"Football",
price: "$49.99",
stocked: true
},
{
name:"Baseball",
price: "$9.99",
stocked: true
},
{
name:"Basketball",
price: "$29.99",
stocked: true
}
]
},
{
category: "Electronics",
products: [
{
name: "iPod Touch",
price: "$99.99",
stocked: true
},
{
name: "iPhone 5",
price: "$399.99",
stocked: false
},
{
name: "Nexus 7",
price: "$199.99",
stocked: true
}
]
}
]
i based my solution from the tutorial here: https://www.consolelog.io/group-by-in-javascript/ using the reduce function.
Here's my code:
const groupBy = (arr,prop)=>{
return arr.reduce((groups,item)=>{
let val = item[prop];
groups[val] = groups[val]||[];
groups[val].push(item);
return groups
},{});
}
const categorize = groupBy(products,'category');
console.log(categorize);
/* returns an Object like
Object {Sporting Goods: Array[3], Electronics: Array[3]}
however it's not the intended output.
*/
I tried to return Object.values(obj) or Object.entries(obj) inside the groupBy function but it just returns an array of 2 arrays like [Array[3],Array[3]] and if i set the initial value (2nd parameter of reduce) to empty [] instead of {}, the output is just an empty array. Need help, thanks!
Because you want an array containing objects (rather than just an array of plain values), create a { category, products: [] } object if it doesn't exist in the accumulator:
const products=[{category:"Sporting Goods",price:"$49.99",stocked:!0,name:"Football"},{category:"Sporting Goods",price:"$9.99",stocked:!0,name:"Baseball"},{category:"Sporting Goods",price:"$29.99",stocked:!1,name:"Basketball"},{category:"Electronics",price:"$99.99",stocked:!0,name:"iPod Touch"},{category:"Electronics",price:"$399.99",stocked:!1,name:"iPhone 5"},{category:"Electronics",price:"$199.99",stocked:!0,name:"Nexus 7"}];
const output = Object.values(
products.reduce((a, { category, ...item }) => {
if (!a[category]) {
a[category] = { category, products: [] };
}
a[category].products.push(item);
return a;
}, {})
);
console.log(output);
function (){
var map = {};
products.forEach((p) => {
if (map[p.category]) {
var c = p.category;
delete p.category;
map[c].push(p);
} else {
var c = p.category;
delete p.category;
map[c]=[c]
}
});
Object.keys(map).forEach((m) => {
ans.push({category:m, products: map[m]})
})
}
you can collect in one go products and map them.
Then make your resultinng array

filter array from nested array in javascript

I have two arrays, I need to filter this based on name.
const arr1 = ["Saab", "Volvo", "BMW", "Car3", "Car2", "Van", "Bus"];
const arr2 = [
{
name: "Saab",
url: "saab/"
},
{
name: "Volvo",
url: "volvo/",
children:[
{
name: "Van",
url: "van/"
},
{
name: "Bus",
url: "bus/"
},
]
},
{
name: "BMW",
url: "bmw/"
},
{
name: "SUV",
url: "suv/",
children:[
{
name: "Car1",
url: "car1/"
},
{
name: "Car2",
url: "car2/"
},
{
name: "Car3",
url: "car3/"
},
]
}
]
I tried with this code.
const result = arr2.filter((item) => arr1.includes(item.name);
This code working fine but it does not check children object in arr2, I need to check based on the arr1 values and if I have children object I need to check that name also. For example: name SUV is in the arr1 but car1 and car2 value are not in arr1 that time I need to remove that SUV object.
My output should be:
[
{
name: "Saab",
url: "saab/"
},
{
name: "Volvo",
url: "volvo/",
children:[
{
name: "Van",
url: "van/"
},
{
name: "Bus",
url: "bus/"
},
]
},
{
name: "BMW",
url: "bmw/"
},
]
How can I do this using javascript, is it possible to do in es6?
You can use another filter function to check children inside the filter function for that.
Explanation:
checking item and item.length to be sure children exists and is not empty (you can remove the second one if not necessary, but it's safer because in most cases it assures that children is an array and that .every will not throw error)
.every and .includes test if all children are in arr1
for the second part of the alternative with || it is not executed if first part is true
in this second part, you have to test !item.children because if first alternative fails on children, it would fallback on the result of arr1.includes(item.name) and "SUV" would come back in the result
const arr1 = ["Saab", "Volvo", "BMW", "SUV", "Car3", "Van", "Bus"];
const arr2 = [
{
name: "Saab",
url: "saab/"
},
{
name: "Volvo",
url: "volvo/",
children:[
{
name: "Van",
url: "van/"
},
{
name: "Bus",
url: "bus/"
},
]
},
{
name: "BMW",
url: "bmw/"
},
{
name: "SUV",
url: "suv/",
children:[
{
name: "Car1",
url: "car1/"
},
{
name: "Car2",
url: "car2/"
},
{
name: "Car3",
url: "car3/"
},
]
}
];
const result = arr2.filter( (item) => ( (item.children && item.children.length && item.children.every(v => arr1.includes(v.name))) || (!item.children && arr1.includes(item.name)) ) );
console.log(result);
EDIT: fixed code to meet requirements
EDIT2: turned into a pure ES6 answer, old fashioned not necessary now
you can use foreach on result and check the items value by last method (filter your nested array).
Please try this.
res = arr2.filter(item2 => (arr1.findIndex(item1 => item2['name'] == item1) > -1))

Reconstruct an array of objects in javascript that contains nested arrays

I'm struggling of coping with an issue here.
lets suppose I have an array of objects like this:
[
{name:Alex, products: [{type: 1, code: 1213}, {type: 2, code: 2312}]},
{name:Alex, products: [{type: 1, code: 1213}, {type: 2, code: 2312}]}
]
I would like somehow to reconstruct my object to be something like this:
[
{name:Alex, products: [{name:Alex, type: 1, code: 1213}, {name:Alex, type: 2, code: 2312}]},
{name:Alex, products: [{{name:Alex, type: 1, code: 1213}, {{name:Alex, type: 2, code: 2312}]}
]
so each nested array contains the name. Is that possible in javascript?
You can use map() method with spread syntax in object to create new array with updated objects and keep original data.
const data = [
{name:'Alex', products: [{type: 1, code: 1213}, {type: 2, code: 2312}]},
{name:'Alex', products: [{type: 1, code: 1213}, {type: 2, code: 2312}]}
]
const result = data.map(e => ({
...e,
products: e.products.map(p => {
return {...p, name: e.name}
})
}))
console.log(result)
I want somehow to reconstruct my object so as to be something like
this:
Use map
arr.map( s => (s.products.map( i => (i.name = s.name, i) ), s) );
Demo
var arr = [{
name: "Alex",
products: [{
type: 1,
code: 1213
}, {
type: 2,
code: 2312
}]
},
{
name: "Alex",
products: [{
type: 1,
code: 1213
}, {
type: 2,
code: 2312
}]
}
]
var output = arr.map(s => (s.products.map(i => (i.name = s.name, i)), s));
console.log(output);
You could create new array/objects with Object.assign.
var data = [{ name: 'Alex', products: [{ type: 1, code: 1213 }, { type: 2, code: 2312 }] }, { name: 'Alex', products: [{ type: 1, code: 1213 }, { type: 2, code: 2312 }] }],
converted = data.map(o => Object.assign(
{},
o,
{ products: o.products.map(p => Object.assign({ name: o.name }, p)) }
));
console.log(converted);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use nested Array.map() calls with Object.assign() to create the new array without changing the original data:
const data = [{"name":"Alex","products":[{"type":1,"code":1213},{"type":2,"code":2312}]},{"name":"Alex","products":[{"type":1,"code":1213},{"type":2,"code":2312}]}]
const result = data.map((o) => Object.assign({}, o, {
products: o.products.map((p) => Object.assign({ name: o.name }, p))
}))
console.log(result)
If you just want to change (mutate) the current array, use nested Array.forEach() calls:
const data = [{"name":"Alex","products":[{"type":1,"code":1213},{"type":2,"code":2312}]},{"name":"Alex","products":[{"type":1,"code":1213},{"type":2,"code":2312}]}]
data.forEach((o) => o.products.forEach((p) => p.name = o.name));
console.log(data)
You have typo in name property.Alex should be 'Alex' (string).You can get help from two array methods. Array.prototype.map() and Array.prototype.forEach() to achieve your goal
var initialArr = [
{name:'Alex1', products: [{type: 1, code: 1213}, {type: 2, code: 2312}]},
{name:'Alex2', products: [{type: 1, code: 1213}, {type: 2, code: 2312}]}
]
var newArr = initialArr.map(function(elem){
elem.products.forEach(function(obj){
obj.name = this.name
},elem)
return elem
})
console.log(newArr)

Categories

Resources