Related
I'm trying to understand the .reduce() function and the best way to go about the following.
I've got the following code:
const products = [
{ name: 'apples', category: 'fruits' },
{ name: 'oranges', category: 'fruits' },
{ name: 'potatoes', category: 'vegetables' }
];
const groupByCategory = products.reduce((group, product) => {
const { category } = product;
group[category] = group[category] ?? [];
group[category].push(product);
return group;
}, {});
I want to add a product with no 'category' property in it, and I want it pushed into a specific key rather than getting grouped in "undefined", so I edited it to:
const products = [
{ name: "apples", category: "fruits" },
{ name: "oranges", category: "fruits" },
{ name: "potatoes", category: "vegetables" },
{ name: "guava"}
];
const groupByCategory = products.reduce((group, product) => {
const { category } = product ;
// check if 'category' exists, if it doesn't store it as an empty array to push to
group[category] = group[category] ?? [];
// if category is undefined, push it into 'nocategory'. Otherwise push into relevant.
if(!category){
group['nocategory'].push(product);
} else {
group[category].push(product);
};
return group;
}, {'nocategory':[]});
console.log(JSON.stringify(groupByCategory, null, 2));
For the most part it works (there's still an 'undefined' group, but at least the object gets pushed into the right group).
I'm sure there's a better solution/proper way to do this. Any pointers would be appreciated.
Instead of a whole new conditional block you could just set a default in the destructuring and then group as usual.
const { category = 'nocategory' } = product;
const products = [
{ name: "apples", category: "fruits" },
{ name: "oranges", category: "fruits" },
{ name: "potatoes", category: "vegetables" },
{ name: "guava"}
];
const groupByCategory = products.reduce((group, product) => {
const { category = 'nocategory' } = product;
group[category] ??= [];
group[category].push(product);
return group;
}, {});
console.log(JSON.stringify(groupByCategory, null, 2));
Note: you can also make use of logical nullish assignment (??=)
You are creating undefined here
group[category] = group[category] ?? []; // category can be undefined
Move creation into if-else statement
const products = [
{ name: "apples", category: "fruits" },
{ name: "oranges", category: "fruits" },
{ name: "potatoes", category: "vegetables" },
{ name: "guava"}
];
const groupByCategory = products.reduce((group, product) => {
const { category } = product ;
// check if 'category' exists, if it doesn't store it as an empty array to push to
// removed
// if category is undefined, push it into 'nocategory'. Otherwise push into relevant.
if(!category){
group['nocategory'].push(product);
} else {
group[category] = group[category] ?? [] // HERE
group[category].push(product);
};
return group;
}, {'nocategory':[]});
console.log(JSON.stringify(groupByCategory, null, 2));
I am trying to remove all the _er and _bx from the array, how can I do it? The way I tried doesn't seem to work. I'd like to see a solution where it removes all after _, and aswell only the letter that I put in for e.g remove all _ with er after.
const nullValue = {
collection: [{
name: "test_er"
},
{
name: "test_bx"
},
{
name: "fred"
},
{
name: "test_er"
}
]
};
const newArr = []
for (let [key, item] of nullValue.collection.entries()) {
item.name.replace(/_er/g, '')
newArr.push(item)
}
console.log(newArr)
Is this what you're looking for?
const nullValue = {
collection: [
{
name: 'test_er',
},
{
name: 'test_bx',
},
{
name: 'fred',
},
{
name: 'test_er',
},
],
};
nullValue.collection = [
...nullValue.collection.map(item => ({
name: item.name.replace(/_.*$/, ''),
})),
];
console.log(nullValue);
You can also use .split('_')[0] with the map method similar to Dmitry's answer... This gives you the first string of the split array, split at the underscore...
const nullValue = {
collection: [{
name: "test_er"
},
{
name: "test_bx"
},
{
name: "fred"
},
{
name: "test_er"
}
]
};
nullValue.collection = [ ...nullValue.collection.map( names => ({ name: names.name.split('_')[0], })),]
console.log(nullValue)
If you want to keep the original array of objects...
const nullValue = {
collection: [{
name: "test_er"
},
{
name: "test_bx"
},
{
name: "fred"
},
{
name: "test_er"
}
]
};
const newArr = { collection :
[ ...nullValue.collection.map( names =>
({ name: names.name.split('_')[0], })),
]}
console.log('newArr = ', newArr)
console.log('nullValue = ', nullValue)
You were VERY close with your original code, but the mistake was that String.replace() does not operate in-place, but rather returns its result. I've modified your code and added a comment below:
const nullValue = {
collection: [{
name: "test_er"
},
{
name: "test_bx"
},
{
name: "fred"
},
{
name: "test_er"
}
]
};
const newArr = []
for (let [key, item] of nullValue.collection.entries()) {
// My change is here
newArr.push( item.name.replace(/_er/g, '') )
}
console.log(newArr)
const nullValue = {
collection: [
{
name: "test_er"
},
{
name: "test_bx"
},
{
name: "fred"
},
{
name: "test_er"
}
]
};
nullValue.collection = nullValue.collection.map(i => i.name.replace(/_.*$/, ''))
console.log(nullValue)
This is preferable to .map() since you don't need a new array. You just want to change the strings:
const nullValue = {
collection: [
{ name: "test_er" },
{ name: "test_bx" },
{ name: "fred" },
{ name: "test_er" }
]
};
nullValue.collection.forEach(i => i.name = i.name.replace(/_.*$/, ''));
console.log(nullValue.collection);
I need to implement a search function for a table.
I got an array of objects with unnecessary object properties.
I need to map the array to get necessary properties and then do the filtration.
This is my code.
const items = [
{
name: 'pathum',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'SL',
code: 12,
},
},
{
name: 'kasun',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'USA',
code: 23,
},
},
{
name: 'hansi',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'GERMANY',
code: 34,
},
},
];
const tableColumns = ['name', 'country.name'];
const onSearch = (e) => {
e = e.toLowerCase();
const mappedItems = items.map((item) => {
Object.keys(item).forEach((key) => {
if (!tableColumns.includes(key)) delete item[key];
});
return item;
});
if (e) {
const result = mappedItems.filter((item) => {
const str = JSON.stringify(item).toLowerCase();
if (str.search(e) >= 0) return item;
});
return result;
} else {
return mappedItems;
}
};
console.log(onSearch('GERMANY'));
In an item object, I only need to get these two fields
const tableColumns = ['name', 'country.name'];
But this only gives me the name property
const mappedItems = items.map((item) => {
Object.keys(item).forEach((key) => {
if (!tableColumns.includes(key)) delete item[key];
});
return item;
});
My first question is how to map to expect a result like this
{
name: 'pathum',
country: {
name: 'SL',
},
},
Second question is JSON.stringtfy map whole object. So If I search "name" it will return all the objects becasue "name" is there in the all records in the stringtify string.
How do I avoid keys in the object when doing the stringify?
Hope my question is clear to you all.
How do I modify this code to get that expected functionality?
const tableColumns = ['name', 'country'];
const deleteProp = ['code'];
const mappedItems = items.map((item) => {
Object.keys(item).forEach((key) => {
console.log(key);
if (!tableColumns.includes(key)) delete item[key];
if(key == 'country') delete item[key][deleteProp[0]];
});
return item;
});
This may answer your first question.
You can check if the object has any of the tableColumns paths which includes the searched text. And then get a subset of the filtered objects and only include the tableColumns properties
const items=[{name:"pathum",id:1,status:true,createdAt:"KKKK",country:{name:"SL",code:12,},},{name:"kasun",id:1,status:true,createdAt:"KKKK",country:{name:"USA",code:23,},},{name:"hansi",id:1,status:true,createdAt:"KKKK",country:{name:"GERMANY",code:34}}],
tableColumns = ['name', 'country.name'];
function onSearch(array, e) {
const output = [];
for (const o of array) {
const hasProp = tableColumns.some(path => getProperty(o, path).includes(e))
if (hasProp)
output.push(subSet(o, tableColumns))
}
return output
}
function getProperty(o, path) {
return path.split('.').reduce((acc, p) => acc?.[p], o) || ''
}
function subSet(o, paths) {
const output = {}
for (const path of paths) {
let keys = path.split('.'),
last = keys.pop(),
value = o;
const final = keys.reduce((acc, k) => {
value = value?.[k]
return acc[k] ||= {}
}, output);
final[last] = value?.[last];
}
return output;
}
console.log(onSearch(items, 'pat'));
console.log(onSearch(items, 'kasun'));
First, don't change data. You can clone the data and change it.
And, search should be search. Don't put the data formation in it.
Let's start.
const items = [
{
name: 'pathum',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'SL',
code: 12,
},
},
{
name: 'kasun',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'USA',
code: 23,
},
},
{
name: 'hansi',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'GERMANY',
code: 34,
},
},
];
// We will use object to get the fields you want. To reuse, you can add more fields you want.
const tableColumns = {
// id: 1,
name: 1,
country: {
name: 1
}
}
// getting the mapped items
const mappedItems = items.map((item) => {
const temp = {};
Object.keys(item).forEach((key) => {
const target = tableColumns[key];
if (target) {
if (typeof target === 'number'){
temp[key] = item[key];
} else {
temp[key] = {};
Object.keys(target).forEach(subKey => temp[key][subKey] = item[key][subKey]);
}
}
});
return temp;
});
// search function, use local varibles
const onSearch = (array, countryName) => {
return array.find(element => element.country.name.toLowerCase() === countryName.toLowerCase())
}
const searchResult = onSearch(mappedItems, 'germany');
console.log(searchResult);
You can just create a new array using Array.map
const items = [{
name: 'pathum',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'SL',
code: 12,
},
},
{
name: 'kasun',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'USA',
code: 23,
},
},
{
name: 'hansi',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'GERMANY',
code: 34,
},
},
];
let minItems = items.map(function(item) {
return {
"name": item.name,
"country": {
"name": item.country.name
}
}
});
console.log(minItems);
How can i check the available data from two array and return new array. For example i want to compare data from one array and check with another array and if it available then it will return with new array with count . Below is the two array and my expected result code.
const restaurant = [
{ name: 'La mesa', cuisine: ['chiness', 'arabic'] },
{ name: 'Purnima', cuisine: ['thai'] },
{ name: 'Red Bull', cuisine: ['french', 'arabic'] },
{ name: 'Pasta', cuisine: ['indian'] },
];
const cuisine = [
{ name: 'chiness' },
{ name: 'arabic' },
{ name: 'thai' },
{ name: 'french' },
{ name: 'italian' },
{ name: 'indian' },
{ name: 'mexican' },
];
// Expected Output a new array like this below
const myCuisine = [
{ name: 'chiness', restaurant: 1 },
{ name: 'arabic', restaurant: 2 },
{ name: 'thai', restaurant: 1 },
{ name: 'french', restaurant: 1 },
{ name: 'italian', restaurant: 0 },
{ name: 'indian', restaurant: 1 },
{ name: 'mexican', restaurant: 0 },
];
Thank you
You can use the functions map, reduce, and some all together to build the desired output as follow:
const restaurant = [ { name: 'La mesa', cuisine: ['chiness', 'arabic'] }, { name: 'Purnima', cuisine: ['thai'] }, { name: 'Red Bull', cuisine: ['french', 'arabic'] }, { name: 'Pasta', cuisine: ['indian'] }],
cuisine = [ { name: 'chiness' }, { name: 'arabic' }, { name: 'thai' }, { name: 'french' }, { name: 'italian' }, { name: 'indian' }, { name: 'mexican' }],
myCuisine = cuisine.map(({name}) => ({name, restaurant: restaurant.reduce((r, {cuisine}) => r + cuisine.some(c => c === name) , 0)}));
console.log(myCuisine)
.as-console-wrapper { max-height: 100% !important; top: 0; }
With map and filter, this way:
const myCuisine = cuisine.map(
item => {
return {
...item,
restaurant: restaurant.filter(
res => res.cuisine.indexOf(item.name) >= 0
).length
}
}
);
You can map the cuisines and filter the restaurants to get the number of restaurants
cuisine.map((cuisineObject) => {
const numberOfRestaurants = restaurant.filter((restaurantObject) => restaurantObject.cuisine.includes(cuisineObject.name)).length
return {
...cuisineObject,
restaurant: numberOfRestaurants
}
})
First we can format the restaurants with cuisine information into an object and then use the same object for finding out the number of restaurants serving the particular cuisine. This can be achieved using Array.reduce and Array.map.
const restaurant = [{name:'La mesa',cuisine:['chiness','arabic']},{name:'Purnima',cuisine:['thai']},{name:'Red Bull',cuisine:['french','arabic']},{name:'Pasta',cuisine:['indian']}];
const cuisine = [{name:'chiness'},{name:'arabic'},{name:'thai'},{name:'french'},{name:'italian'},{name:'indian'},{name:'mexican'}];
const getFormattedList = (cuisines, restaurants) => {
return cuisines.map(cuisine => {
return {
...cuisine,
restaurant: restaurants[cuisine.name] || 0
}
})
}
const formatRestaurantCuisines = (restaurants) => {
return restaurants.reduce((result, restaurant) => {
restaurant.cuisine.forEach(cuisine => {
result[cuisine] = (result[cuisine]||0) + 1;
})
return result;
}, {});
}
//Formatted object to convert the restaurant with cuisine info to count
const formattedObj = formatRestaurantCuisines(restaurant);
console.log(formattedObj);
console.log(getFormattedList(cuisine, formattedObj))
.as-console-wrapper {
max-height: 100% !important;
}
You can build an object which stores all the frequencies of each cuisine with .reduce() and then use .map() on your cuisine array like so:
const restaurant = [ { name: 'La mesa', cuisine: ['chiness', 'arabic'] }, { name: 'Purnima', cuisine: ['thai'] }, { name: 'Red Bull', cuisine: ['french', 'arabic'] }, { name: 'Pasta', cuisine: ['indian'] }, ];
const cuisine = [ { name: 'chiness' }, { name: 'arabic' }, { name: 'thai' }, { name: 'french' }, { name: 'italian' }, { name: 'indian' }, { name: 'mexican' }, ];
const cusineFreq = restaurant.reduce((o, {cuisine}) => {
cuisine.forEach(type => o[type] = (o[type] || 0) + 1);
return o;
}, {});
const res = cuisine.map(o => ({...o, restaurant: (cusineFreq[o.name] || 0)}));
console.log(res);
This approach of creating an object for look-up is particularly useful if restaurant is large, as it allows for a time complexity of O(n + k) rather than O(n*k). Thus, it will allow for better overall performance compared to nested loops and is more scalable.
Using map, flatMap and filter
Edited: using flatMap instead of map.flat
cuisine.map(({name}) => ({name: name,restaurant: restaurant.flatMap(v => v.cuisine).filter(v=>v === name).length}))
I am trying to create an object, and within this object will be the name and an array of other objects under children. Really I want to create a hierarchy from another object.
I have tried to create a recursive function but what I end up with is a vertical slice rather than the whole picture. I am unsure how to adjust my recursion to go back add iterate through the other horizontal objects.
buildHierarchy(json) {
console.log("Entered Build Hierarchy");
let newObject;
newObject = this.buildChildren(json);
console.log(newObject);
return newObject
}
buildChildren(json) {
let returnObject;
for (var key in json) {
returnObject = {
name: key,
children: []
};
var subObject = json[key];
if (Array.isArray(subObject)) {
returnObject = {
name: key,
_proficiency: subObject
}
} else {
returnObject["children"].push(this.buildChildren(subObject))
}
}
return returnObject;
}
Imagine you have this json file below
{users:
{sandy: {
posts: [
{ title: 'Bar', comments: [ 'Ok' ] },
]
followers: [
{ name: 'Foo' },
]
}
ron: {
photos: [
{ title: 'Foo', comments: [ 'Ok' ] },
]
}
}
}
I am looking for something like this...
{
name: "users",
children: [
{
name: "sandy",
children: [
{
name: "posts",
children: [
{
name: "Bar",
comments: "OK"
}],
{ name: "followers"
children: [
{
name: "Foo"
}
]
}
}
]
},
{
name: "ron",
photos: [
{
name: "photos",
children: [
{
name: "Foo",
comments: "OK"
}
]
}
]
}
]
}
From what I see as the output I'm making these inferences:
If the child object is an array, it is blindly copied to children key
If the child element is an object, then they are changed to {name: <KEY>, children: <VALUE>}
function buildChildren(json) {
let returnObject = [];
if (typeof json !== 'object') {
return json;
}
for (var key in json) {
if (Array.isArray(json)) {
returnObject.push(buildChildren(json[key]));
} else {
returnObject.push({
name: key,
children: buildChildren(json[key])
});
}
}
return returnObject;
}
function buildHierarchy(json) {
console.log("Entered Build Hierarchy");
let newObject;
newObject = buildChildren(json);
console.log(newObject);
}
function buildChildren(json) {
if (Array.isArray(json)) {
return {
_proficiency: json
}
}
var children = Object.keys(json);
let final = [];
for (var i = 0; count = children.length, i < count; i++) {
let result = {
name: children[i]
}
let d = buildChildren(json[children[i]]);
if (d._proficiency) {
result._proficiency = d._proficiency;
} else {
result.children = d;
}
final.push(result);
}
return final;
}