Write a function called excludeItems where the first input argument represents your dataset (list of objects) and the second input represents the key value properties you want to exclude (list of objects) and returns the list excluding the key value properties specified in your second argument.
For Example
const items = [
{ color: 'red', type: 'tv', age: 18 },
{ color: 'red', type: 'phone', age: 20 },
{ color: 'silver', type: 'tv', age: 18 },
{ color: 'silver', type: 'phone', age: 20 }
];
const excludes = [
{ k: 'color', v: 'red' },
{ k: 'color', v: 'blue' },
{ k: 'type', v: 'phone' },
];
expectedOutput = [
{ type: 'tv', age: 18 },
{ age: 20 },
{ color: 'silver', type: 'tv', age: 18 },
{ color: 'silver', age: 20 }
];
I came up with two similar solutions, where I think the second one is slightly better as you don't have to iterate over the values list to check if the value from the original object exists there.
I would like to understand if there is an even better approach than this.
// Option 1
function excludeItems(items, excludes) {
let exclusionMap = {};
let results = [];
excludes.forEach(item => {
if(!exclusionMap[item.k])
exclusionMap[item.k] = [];
let itemDoesNotExist = exclusionMap[item.k].indexOf(item.v) === -1;
if(itemDoesNotExist) {
exclusionMap[item.k].push(item.v);
}
})
results = items.map(item => {
for(let key in exclusionMap) {
let exclusionValues = exclusionMap[key];
if(item[key]) {
let itemValue = item[key]
let itemValueIndexInExclusionValue = exclusionValues.indexOf(itemValue);
if(itemValueIndexInExclusionValue !== -1) {
delete item[key];
}
}
}
return item;
});
return results;
}
// Option 2
function excludeItems(items, excludes) {
let exclusionMap = {};
let results = [...items];
excludes.forEach(item => {
exclusionMap[item.k + '_' + item.v] = true;
})
results.map(item => {
for(const key in exclusionMap) {
const k = key.split('_')[0]; // type
const v = key.split('_')[1]; // phone
if(v == item[k]) {
delete item[k]
}
}
return item;
})
return results;
}
Thanks
You could filter the entries and rebuild the objects while mapping a new array.
function excludeItems(items, excludes) {
return items.map(o => Object.fromEntries(Object
.entries(o)
.filter(([k, v]) => !excludes.some(q => q.k === k && q.v === v))
));
}
const
items = [{ color: 'red', type: 'tv', age: 18 }, { color: 'red', type: 'phone', age: 20 }, { color: 'silver', type: 'tv', age: 18 }, { color: 'silver', type: 'phone', age: 20 }],
excludes = [{ k: 'color', v: 'red' }, { k: 'color', v: 'blue' }, { k: 'type', v: 'phone' }];
console.log(excludeItems(items, excludes));
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you do not mind the types, you could take an shorter approach with an object.
function excludeItems(items, excludes) {
const pairs = Object.fromEntries(excludes.map(({ k, v }) => [`${k}|${v}`, true]));
return items.map(o => Object.fromEntries(Object
.entries(o)
.filter(([k, v]) => !pairs[`${k}|${v}`])
));
}
const
items = [{ color: 'red', type: 'tv', age: 18 }, { color: 'red', type: 'phone', age: 20 }, { color: 'silver', type: 'tv', age: 18 }, { color: 'silver', type: 'phone', age: 20 }],
excludes = [{ k: 'color', v: 'red' }, { k: 'color', v: 'blue' }, { k: 'type', v: 'phone' }];
console.log(excludeItems(items, excludes));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I came up with a simple solution with comments that is easy to understand and follow
function excludeItems(items, excludes) {
let exclusionMap = {};
// exclusionMap will be represented as
// { type: ['red', etc...]}
excludes.forEach(item => {
if(!exclusionMap[item.k]) {
exclusionMap[item.k] = [];
}
exclusionMap[item.k].push(item.v);
});
// Filter items
return items.filter(item => {
// Get each object key in item and value independently
for(let key in item) {
// ex: key = color
let itemValue = item[key]; // ex: red
// Check if exclusionMap has that key, if it does, check if the key values contain the value
if(exclusionMap[key] && exclusionMap[key].includes(itemValue)) {
// If so, exclude it
return false;
}
// otherwise don't do anything
}
// return true for all other cases
return true;
});
}
Related
This question already has answers here:
Group array items using object
(19 answers)
Closed last year.
I have an array of objects and I would like to group the objects which have same name and make an array containing the other values which differs. How can I achieve that?
const arr = [
{
name: 'A',
color: 'blue',
},
{
name: 'A',
color: 'purple',
},
{
name: 'B',
color: 'Yellow',
},
{
name: 'B',
color: 'Green',
},
];
What I would like to get:
const result = [
{
name: 'A',
color: ['blue', 'purple'],
},
{
name: 'B',
color: ['Yellow', 'Green'],
},
];
This looks like something reduce() should be used for.
Use find() to find in the existing array element based on some condition.
If element exists, push into colors property of the element.
Else push into the array a new object.
const arr = [
{
name: 'A',
color: 'blue',
},
{
name: 'A',
color: 'purple',
},
{
name: 'B',
color: 'Yellow',
},
{
name: 'B',
color: 'Green',
},
];
let ans = arr.reduce((agg,curr) => {
let found = agg.find((x) => x.name === curr.name);
if(found){
found.colors.push(curr.color);
}
else{
agg.push({
name : curr.name,
colors : [curr.color]
});
}
return agg;
},[]);
console.log(ans);
const found = acc.find(item => item.name === curr.name);
if (found) {
found.color.push(curr.color);
} else {
acc.push({
name: curr.name,
color: [curr.color],
});
}
return acc;
}
, []);
Here is one way to do it:
const arrNames = Array.from(new Set(arr.map((x) => x.name))); // make an array of unique names
const result = arrNames
.map((x) => arr.filter((y) => y.name === x)) // filter by name
.map((x, i) => ({ name: arrNames[i], color: x.map((y) => y.color) })); // make new objects
Create set of props then loop over possible names and filter their values O(n^2)
const set = new Set(arr.map((obj) => obj.name));
const res = [];
for(const name of set.keys()) {
const colors = arr.filter((obj) => obj.name === name).map((obj) => obj.color);
res.push({name, colors});
}
Or create a dictionary whose keys will be name-s, and values - array O(n)
const mp = new Map();
for (const obj of arr) {
if (mp.has(obj.name)) {
mp.get(obj.name).push(obj.color);
} else {
mp.set(obj.name, [obj.color]);
}
}
const result = [];
for (const [name, color] of mp.entries()) {
result.push({name, color});
}
let results = [];
const arr = [
{
name: "A",
color: "blue",
},
{
name: "A",
color: "purple",
},
{
name: "B",
color: "Yellow",
},
{
name: "B",
color: "Green",
},
];
const names = arr.map((element) => element.name);
const uniqueNames = [...new Set(names)];
uniqueNames.forEach((element) => {
let temp = {};
temp.name = element;
temp.color = [];
arr.forEach((element2) => {
if (element === element2.name) {
temp.color.push(element2.color);
}
});
results.push(temp);
});
console.log("results", results);
I am trying to create a filter that is working when the object is a single value, but when introducing an array of keywords I am having issues.
My code looks like the following:
const filter = {
colors: ["white"],
sizes: [9, 12],
brands: ["adidas"],
keywords: ["running", "weights"]
};
const shoes = [{
brand: "adidas",
size: 9,
color: "white",
keywords: ["running"]
},
{
brand: "adidas",
size: 12,
color: "white",
keywords: ["weigths"]
},
{
brand: "nike",
size: 7,
color: "red",
keywords: ["running", "tennis"]
}
];
const properties = {
colors: 'color',
sizes: 'size',
brands: 'brand',
keywords: 'keywords',
}
const filters = Object
.entries(filter)
.filter(([, {
length
}]) => length)
.map(([k, v]) => [properties[k], v]);
const result = shoes.filter(shoe => filters.every(([k, v]) => v.includes(shoe[k])));
console.log('result', result)
The result that I am looking for is
const results = {
brand: "nike",
size: 9,
color: "white",
keywords: ["running"]
},
{
brand: "adidas",
size: 12,
color: "white",
keywords: ["swimming"]
}]
By having matching words withou spellin g differences, you could create an array of every value and check against the wanted values.
const
filter = { colors: ["white"], sizes: [9, 12], brands: ["adidas"], keywords: ["running", "weights"] },
shoes = [{ brand: "nike", size: 9, color: "white", keywords: ["running"] }, { brand: "adidas", size: 12, color: "white", keywords: ["weights"] }, { brand: "nike", size: 7, color: "red", keywords: ["running", "tennis"] }],
properties = { colors: 'color', sizes: 'size', brands: 'brand', keywords: 'keywords' },
filters = Object
.entries(filter)
.filter(([, { length }]) => length)
.map(([k, v]) => [properties[k], v]),
result = shoes.filter(
shoe => filters.every(
([k, v]) => [].concat(shoe[k]).some(value => v.includes(value))
)
);
console.log('result', result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
My assumption, you are filtering by size, color and keywords not by brand and based on that I produced the code below, but if the assumption is not valid please provide more details
You can use array.filter to filter an array by passing conditions
let result = shoes.filter(f => filter.sizes.includes(f.size)
&& filter.colors.includes(f.color)
&& filter.keywords.some(k => f.keywords.includes(k))
)
console.log(result);
The following should take filter out any object that has a value inconsistent with the filters:
const filterObjectArray = (filters, array) => {
return array.filter(obj => {
// iterate through each key in filters
for (let i in filters) {
const objVal = obj[properties[i]]
// here, we can use flatMap to "spread" objVal and filters[i] into
// an array, whether or not objVal is an array
const combinedValues = [objVal, filters[i]].flatMap(x => x)
// convert the combinedValues into a Set to remove duplicates
const set = new Set(combinedValues)
// If the size of the set matches the length of combinedValues
// it means that objVal and filters[i] have no values in common, so we
// can just return false.
if (set.size === combinedValues.length) return false
}
// If we reach here, it means every value in object has been validated by every key/value in filters.
return true
})
}
Here's a repl where you can see this code in action: https://replit.com/#isaacsan123/filtering-objects
I have the next code:
const arr = [
{
name:'john',
cars:[
{audi:1},
{bmw:2}
]
},
{
name:'bill',
cars:[
{audi:10},
{bmw:0}
]
}
]
const arr1 = arr.map(i => {
if(i.name === 'john') {
return i.cars.map( a => {
return {
...i,
test:[2]
}
})
}
return i
})
console.log(arr1)
Here i want too loop through the array and for the first object to change the cars array, adding test:[2]. For this i used:
const arr1 = arr.map(i => {
if(i.name === 'john') {
return i.cars.map( a => {
return {
...i,
test:[2]
}
})
}
return i
})
The issue is that my code don't return what i want. I get the first object like:
0: Object
name: "john"
cars: Array[2]
test: 2
1: Object
name: "john"
cars: Array[2]
test: 2
but i need like this:
{
name:'john',
cars:[
{
audi:1,
test: [2],
},
{bmw:2}
]
},
How to solve my issue?
Since you only want to change the first item in the cars array, I don't think map is right - instead, just list the first changed car as an object literal inside an array, then spread the remaining cars into the array with .slice(1):
const arr = [
{
name:'john',
cars:[
{audi:1},
{bmw:2}
]
},
{
name:'bill',
cars:[
{audi:10},
{bmw:0}
]
}
]
const arr1 = arr.map(person => (
person.name !== 'john'
? person
: ({
name: person.name,
cars: [
{ ...person.cars[0], test: [2] },
...person.cars.slice(1)
]
})
));
console.log(arr1)
You could address the right position and add the wanted property.
const
data = [{ name: 'john', cars: [{ audi: 1 }, { bmw: 2 }] }, { name: 'bill', cars: [{ audi: 10 }, { bmw: 0 }] }],
add = { target: [0, 0], value: { test: [2] } }
result = data.map((o, i) => i === add.target[0]
? { ...o, cars: o.cars.map((p, j) => j === add.target[1]
? {... p, ...add.value }
: p)
}
: o);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Given the desired result, a non mapping solution may be viable?
const arr = [{
name: 'john',
cars: [{
audi: 1
},
{
bmw: 2
}
]
},
{
name: 'bill',
cars: [{
audi: 10
},
{
bmw: 0
}
]
}
];
// clone the initial arr
const arrModified = Object.assign([], arr);
// find John
const indexJohn = arrModified.findIndex(v => v.name === "john");
if (indexJohn > -1) {
// modify the desired value
arrModified[indexJohn].cars[0].test = [2];
}
console.log(arrModified[0].cars);
.as-console-wrapper { top: 0; max-height: 100% !important; }
I have an array of arrays, of the same size, of objects like this:
const array = [
[{ name: 'John' }, { name: 'Julie' }, { name: 'Zack' }],
[{ color: 'blue' }, { color: 'orange' }, { color: 'green' }],
[{ age: 12 }, { age: 10 }, { age: 35 }]
];
How do I merge these arrays object by object to have an output like this?
const result = [{ name: 'John', color: 'blue', age: 12 }, { name: 'Julie', color: 'orange', age: 10 } ...]
If it could be using lodash, it would be nice too. Thanks in advance
You can use map and destructuring
const array1 = [{ name: 'John' }, { name: 'Julie' }, { name: 'Zack' }];
const array2 = [{ color: 'blue' }, { color: 'orange' }, { color: 'green' }];
const array3 = [{ age: 12 }, { age: 10 }, { age: 35 }];
let final = array1.map((a, i) => ({ ...a,
...array2[i],
...array3[i]
}))
console.log(final)
But would it work with several arrays?
const array = [
[{ name: 'John' }, { name: 'Julie' }, { name: 'Zack' }],
[{ color: 'blue' }, { color: 'orange' }, { color: 'green' }],
[{ age: 12 }, { age: 10 }, { age: 35 }],
[{ someProp: 1 }, { someProp: 2 }, { someProp: 3 }]
];
let remaining = array.slice(1,)
let final = array[0].map((v,i)=>{
let temp = {...v}
remaining.forEach(v2=>{
temp = {...temp, ...v2[i]}
})
return temp
})
console.log(final)
A caveman approach - pushing to an array from a for-loop.
const array1 = [{ name: 'John' }, { name: 'Julie' }, { name: 'Zack' }];
const array2 = [{ color: 'blue' }, { color: 'orange' }, { color: 'green' }];
const array3 = [{ age: 12 }, { age: 10 }, { age: 35 }];
let result = [];
for ( let i = 0; i < array1.length; i++ ){
result.push( { ...array1[i], ...array2[i], ...array3[i] } );
}
console.log( result );
const result = array1.map((el, index) => ({
...el,
...array2[index],
...array3[index]
}))
you don't need lodash, its easy enough with vanilla JS using array.map.
I'm assuming you have same number of object in all 3 arrays, if you need to also handle the case that there aren't- let me know.
array1.map( (el, index) =>
({ ...el, ...array2[index], ...array3[index] })
);
For a variable amount of arrays aka 2 dimensional array:
const array1 = [{ name: 'John' }, { name: 'Julie' }, { name: 'Zack' }];
const array2 = [{ color: 'blue' }, { color: 'orange' }, { color: 'green' }];
const array3 = [{ age: 12 }, { age: 10 }, { age: 35 }];
const array4 = [{ foo: 'bar'}, {foo: 'baz'}];
const mergeFirst = (arrays) => {
return [...arrays.map(arr => arr[0])].reduce((obj, val) => ({
...obj,
...val,
}), {});
}
const mergeAttributes = (arrays, results = []) => {
return (arrays[0].length) ? mergeAttributes(arrays.map(arr => arr.slice(1, arr.length)), results.concat(mergeFirst(arrays))) : results;
}
console.log( mergeAttributes([array1,array2,array3,array4]) );
In my code I have two arrays, first one contains multiple objects. While the second one is to store serialized form data (mapped to JSON). So both arrays are having identical keys.
What I want to achieve is to update the values of an object in the original array based on the values of an object in new array dynamically, by ID in the object.
Found some examples online but hard to get them to work. Because most of them are showing either one level of objects but I'm working on complex nested objects in the array.
var products = [
{
Id: 1,
Name: 'Product1',
Attributes: {
Storage: 'Normal',
Size: 'Small'
}
},
{
Id: 2,
Name: 'Product2',
Attributes: {
Storage: 'Normal',
Size: 'Small'
}
}
];
var newData = [
{
Id: 2,
Name: 'French Fries'
},
{
Id: 1,
Attributes: {
Size: 'Medium'
}
}
];
The expected outcome is the products array now updated with the values from the second array.
Output:
[
{
Id: 1,
Name: 'Product1',
Attributes: {
Storage: 'Normal',
Size: 'Medium'
}
},
{
Id: 2,
Name: 'French Fries',
Attributes: {
Storage: 'Normal',
Size: 'Small'
}
}
]
You could take a Map for the update items and iterate products.
If an item is found for an update, take a recursive approach and iterate the entries and check if the value is an object, then iterate the nested properties.
If no nested object found update the property.
This works for arrays as well.
function update(target, source) {
Object.entries(source).forEach(([key, value]) => {
if (value && typeof value === 'object') {
update(target[key] = target[key] || (Array.isArray(value) ? [] : {}), value);
} else if (target[key] !== value) {
target[key] = value;
}
});
}
var products = [{ Id: 1, Name: 'Product1', Attributes: { Storage: 'Normal', Size: 'Small' } }, { Id: 2, Name: 'Product2', Attributes: { Storage: 'Normal', Size: 'Small' } }],
newData = [{ Id: 2, Name: 'French Fries' }, { Id: 1, Attributes: { Size: 'Medium' } }],
map = new Map(newData.map(o => [o.Id, o]));
products.forEach(o => map.has(o.Id) && update(o, map.get(o.Id)));
console.log(products);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can create a function which combine nested objects. And then use map() and find() to create combined array of objects.
var products = [
{
Id: 1,
Name: 'Product1',
Attributes: {
Storage: 'Normal',
Size: 'Small'
}
},
{
Id: 2,
Name: 'Product2',
Attributes: {
Storage: 'Normal',
Size: 'Small'
}
}
];
var newData = [
{
Id: 2,
Name: 'French Fries'
},
{
Id: 1,
Attributes: {
Size: 'Medium'
}
}
];
const haveNested = obj => Object.values(obj).some(x => typeof x === "object");
function combine(obj1,obj2){
if(!haveNested(obj1)) return ({...obj1,...obj2})
let res = obj1
for(let key in obj1){
if(typeof obj1[key] === "object"){
res[key] = combine(obj1[key],obj2[key]);
}
else if(obj2[key]) res[key] = obj2[key]
}
return res;
}
const result = products.map(x => {
let temp = newData.find(a => a.Id === x.Id);
return temp ? combine(x,temp) : x;
})
console.log(result)