Related
let products = [
{
name: "A",
color: "Blue",
size: {
size1: 1,
size2: 2,
size3: 3,
},
},
{
name: "B",
color: "Blue",
size: {
size1: 5,
size2: 19,
size3: 22,
},
},
{ name: "C", color: "Black", size: 70 },
{ name: "D", color: "Green", size: 50 },
];
filters = ['Blue','2'];
the result must be the object that checks all strings in the array for example
{
name: "A",
color: "Blue",
size: {
size1: 1,
size2: 2,
size3: 3,
},
},
the research must be accepted whatever the value in the
You can resolve the nest via using a stack in some manner, either by recursion or iteratively using a stack explicitly. Here's a recursive solution:
function getFiltered(obj, filters, found = null) {
let outermostCall = (found === null);
if (outermostCall) { //outermost call
found = [];
for (let index = 0; index < filters.length; index++) {
found[index] = false;
}
}
for (let key in obj) {
if (typeof obj[key] === 'object') {
let tempFound = getFiltered(obj[key], filters, found);
for (let index = 0; index < found.length; index++) {
if (tempFound[index]) found[index] = true;
}
} else {
let foundIndex = -1;
for (let index = 0; index < filters.length; index++) {
if (filters[index] == obj[key]) {
foundIndex = index;
index = filters.length;
}
}
if (foundIndex >= 0) {
found[foundIndex] = true;
}
}
}
if (outermostCall) {
return !found.filter(item => !item).length;
}
return found;
}
function getAllFiltered(array, filters) {
let output = [];
for (let obj of array) {
if (getFiltered(obj, filters)) output.push(obj);
}
return output;
}
let products = [
{
name: "A",
color: "Blue",
size: {
size1: 1,
size2: 2,
size3: 3,
},
},
{
name: "B",
color: "Blue",
size: {
size1: 5,
size2: 19,
size3: 22,
},
},
{ name: "C", color: "Black", size: 70 },
{ name: "D", color: "Green", size: 50 },
];
let filters = ['Blue','2'];
console.log(getAllFiltered(products, filters));
You could take a closure over any of the search values and check if all of them are in the object or nested objest for filtering.
const
has = f => {
const check = o => o && typeof o === 'object'
? Object.values(o).some(check)
: f === o;
return check;
},
products = [{ name: "A", color: "Blue", size: { size1: 1, size2: 2, size3: 3 } }, { name: "B", color: "Blue", size: { size1: 5, size2: 19, size3: 22 } }, { name: "C", color: "Black", size: 70 }, { name: "D", color: "Green", size: 50 }],
search = ['Blue', 2],
result = products.filter(o => search.every(f => has(f)(o)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Array.every to check if all the filters are present in the object, if that's what you mean.
const products = [
{ name: "A", color: "Blue", size: { size1:1, size2:2, size3:3 } },
{ name: "B", color: "Blue", size: { size1:5, size2:19, size3:22 } },
{ name: "C", color: "Black", size: 70 },
{ name: "D", color: "Green", size: 50 },
];
const filters = ['Blue','2'];
const filtered = products.filter(product => {
return Object.values(product).every(value => {
return filters.includes(value);
});
});
console.log(filtered);
The function strings returns all nested strings within an object, converting any numbers to strings. Then, we just filter any product where the list of strings includes all necessary matches.
const products = [{"name":"A","color":"Blue","size":{"size1":1,"size2":2,"size3":3}},{"name":"B","color":"Blue","size":{"size1":5,"size2":19,"size3":22}},{"name":"C","color":"Black","size":70},{"name":"D","color":"Green","size":50}];
const filters = ['Blue','2'];
const subsetMatch=(a,s)=>s.every(i=>a.includes(i));
const strings=i=>typeof i==='object'?Object.values(i).flatMap(strings):i+'';
console.log(products.filter(i=>subsetMatch(strings(i),filters)));
Having an array like this:
const data = [
{
"name": "Dave",
"coins": 14,
"weapons": 2,
"otherItems": 3,
"color": "red"
},
{
"name": "Vanessa",
"coins": 18,
"weapons": 1,
"otherItems": 5,
"color": "blue"
},
{
"name": "Sharon",
"coins": 9,
"weapons": 5,
"otherItems": 1,
"color": "pink"
},
{
"name": "Walter",
"coins": 9,
"weapons": 2,
"otherItems": 4,
"color": "white"
}
]
How to count sum of coins, weapons and otherItems using ES6 features? (I'm not attached to this: any simple method would be good.)
data.reduce((first, last) => first + last) generates a chain of [object Object][object Object]s...
You have to process every field separately (note that when you don't specify second parameter for reduce it will take first array object as seed and start processing from the second one):
const data = [
{
"name": "Dave",
"coins": 14,
"weapons": 2,
"otherItems": 3,
"color": "red"
},
{
"name": "Vanessa",
"coins": 18,
"weapons": 1,
"otherItems": 5,
"color": "blue"
},
{
"name": "Sharon",
"coins": 9,
"weapons": 5,
"otherItems": 1,
"color": "pink"
},
{
"name": "Walter",
"coins": 9,
"weapons": 2,
"otherItems": 4,
"color": "white"
}
]
let result = data.reduce((a,c)=> ({
coins: a.coins + c.coins,
weapons: a.weapons + c.weapons,
otherItems: a.otherItems + c.otherItems })
)
console.log(result);
You could take an array of wanted keys for the sums and create an object for the sums and add the wanted values.
const
data = [{ name: "Dave", coins: 14, weapons: 2, otherItems: 3, color: "red" }, { name: "Vanessa", coins: 18, weapons: 1, otherItems: 5, color: "blue" }, { name: "Sharon", coins: 9, weapons: 5, otherItems: 1, color: "pink" }, { name: "Walter", coins: 9, weapons: 2, otherItems: 4, color: "white" }],
keys = ['coins', 'weapons', 'otherItems'],
sums = data.reduce(
(r, o) => (keys.forEach(k => r[k] += o[k]), r),
Object.fromEntries(keys.map(k => [k, 0]))
);
console.log(sums);
You can use Array.prototype.reduce for this.
To make it a little bit more flexible and dynamic, make a Set of keys you want to get a count of.
Then go through each key in the Set and if that key is in the obj, sum it up in an accumulator object in the reduce callback:
const data = [{"name":"Dave","coins":14,"weapons":2,"otherItems":3,"color":"red"},{"name":"Vanessa","coins":18,"weapons":1,"otherItems":5,"color":"blue"},{"name":"Sharon","coins":9,"weapons":5,"otherItems":1,"color":"pink"},{"name":"Walter","coins":9,"weapons":2,"otherItems":4,"color":"white"}]
//Keys to count
const keys = new Set(["coins", "weapons", "otherItems"]);
const count = data.reduce((acc, obj) => {
const objKeys = keys.forEach(key => {
if (obj.hasOwnProperty(key)) {
acc[key] = (acc[key] || 0) + obj[key];
}
});
return acc;
}, {});
console.log(count);
Your idea is right, you need to use reduce method. The problem is that you're summing two objects, not their properties. All you need to do is change the code to the following (to sum the coins):
data.reduce((first, last) => first.coins + last.coins, 0)
And following for weapons:
data.reduce((first, last) => first.weapons + last.weapons, 0)
I've got two arrays:
arrayOne = ["green","blue","purple"]
and
arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
]
I want the return array to be [1, 8, 9], with "purple" being pushed as an object at the end of arrayTwo (with a new id).
What's the most efficient way to go about this?
The following code uses map to either retrieve the id of the element in the second array or, if that element doesn't exist, create a new one by incrementing the last id in that array by 1.
arrayOne = ["green","blue","purple"]
arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
]
const newArr = arrayOne.map(color => {
const found = arrayTwo.find(el => el.name === color);
if (found) {
return found.id;
}
const newId = arrayTwo[arrayTwo.length - 1].id + 1;
arrayTwo.push({ name: color, id: newId });
return newId;
});
console.log(newArr);
console.log(arrayTwo);
Edit: Note that it may be brittle to assume the last item in arrayTwo contains the highest id. In that case, you can always find the max ID:
const newArr = arrayOne.map(color => {
let maxId = 0;
const found = arrayTwo.find(el => {
if (el.id > maxId) {
maxId = el.id;
}
return el.name === color
});
if (found) {
return found.id;
}
const newId = maxId + 1;
arrayTwo.push({ name: color, id: newId });
return newId;
});
A thought on efficiency
If you have any concerns about efficiency if this is going to be a large (hundreds/thousands/more) element arrays, you can consider changing arrayTwo to an object with color as a key:
const arrayOne = ["green","blue","purple"];
const arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
];
let maxId = 0;
// Create map
const arrayTwoMap = arrayTwo.reduce((acc, el) => {
if (el.id > maxId) maxId = el.id;
acc[el.name] = el.id;
return acc;
}, {});
// Find elements
const newArr = arrayOne.map(el => {
const found = arrayTwoMap[el];
if (found !== undefined) {
return found;
}
const newId = maxId + 1;
arrayTwoMap[el] = newId;
return newId;
});
console.log(newArr);
this code can achieve what you want:
let arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
];
let indexes = {}, lastId = 0;
arrayTwo.forEach(({name, id}) => {
if(indexes[name] = id, id > lastId) lastId = id
});
function getResult(a){
return a.map(e => indexes[e] || (arrayTwo.push({name: e, id: ++lastId}), indexes[e] = lastId))
}
// arrayOne contents
let result = getResult(["green", "blue", "purple"]);
console.log(arrayTwo, result);
// with other data
let result2 = getResult(["cyan", "blue", "purple", "azure"]);
console.log(arrayTwo, result2);
Hope it helps
You can convert arrayTwo into a Map (for efficiency), where the name is the key and the id is the value. Then, once you have done that, you can .map() arrayOne into an array of id by using each name element as a look-up key to get its associated id. If you find a name which is not in the Map, then you can add a new object, to your arrayTwo array, and increment the id counter:
const arrayOne = ["green","blue","purple"];
const arrayTwo = [{ name: "green", id: 1 }, { name: "red", id: 2 }, { name: "yellow", id: 3 }, { name: "blue", id: 8 },];
let [{id:l_id}] = arrayTwo.slice(-1);
const lut = new Map(arrayTwo.map(({name, id}) => [name, id]));
const res = arrayOne.map(name => lut.get(name) || (arrayTwo.push({name, id: ++l_id}), l_id));
console.log(res);
console.log(arrayTwo);
If you're only concerned about the return value (and not changing array 2), you can simplify the code down:
const arrayOne = ["green","blue","purple"];
const arrayTwo = [{ name: "green", id: 1 }, { name: "red", id: 2 }, { name: "yellow", id: 3 }, { name: "blue", id: 8 },];
let [{id:l_id}] = arrayTwo.slice(-1);
const lut = new Map(arrayTwo.map(({name, id}) => [name, id]));
const res = arrayOne.map(
name => lut.get(name) || ++l_id
);
console.log(res);
Assuming an array of objects as follows:
const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6},
]
A duplicate entry would be if label and color are the same. In this case Objects with id = 1 and id = 5 are duplicates.
How can I filter this array and remove duplicates?
I know solutions where you can filter against one key with something like:
const unique = [... new Set(listOfTags.map(tag => tag.label)]
But what about multiple keys?
As per request in comment, here the desired result:
[
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
]
Late one, but I don't know why nobody suggests something much simpler:
listOfTags.filter((tag, index, array) => array.findIndex(t => t.color == tag.color && t.label == tag.label) == index);
You could use a Set in a closure for filtering.
const
listOfTags = [{ id: 1, label: "Hello", color: "red", sorting: 0 }, { id: 2, label: "World", color: "green", sorting: 1 }, { id: 3, label: "Hello", color: "blue", sorting: 4 }, { id: 4, label: "Sunshine", color: "yellow", sorting: 5 }, { id: 5, label: "Hello", color: "red", sorting: 6 }],
keys = ['label', 'color'],
filtered = listOfTags.filter(
(s => o =>
(k => !s.has(k) && s.add(k))
(keys.map(k => o[k]).join('|'))
)
(new Set)
);
console.log(filtered);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6},
]
const unique = [];
listOfTags.map(x => unique.filter(a => a.label == x.label && a.color == x.color).length > 0 ? null : unique.push(x));
console.log(unique);
One way is create an object (or Map) that uses a combination of the 2 values as keys and current object as value then get the values from that object
const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6},
]
const uniques = Object.values(
listOfTags.reduce((a, c) => {
a[c.label + '|' + c.color] = c;
return a
}, {}))
console.log(uniques)
I would tackle this by putting this into temporary Map with a composite key based on the properties you're interested in. For example:
const foo = new Map();
for(const tag of listOfTags) {
foo.set(tag.id + '-' tag.color, tag);
}
Based on the assumption that values can be converted to strings, you can call
distinct(listOfTags, ["label", "color"])
where distinct is:
/**
* #param {array} arr The array you want to filter for dublicates
* #param {array<string>} indexedKeys The keys that form the compound key
* which is used to filter dublicates
* #param {boolean} isPrioritizeFormer Set this to true, if you want to remove
* dublicates that occur later, false, if you want those to be removed
* that occur later.
*/
const distinct = (arr, indexedKeys, isPrioritizeFormer = true) => {
const lookup = new Map();
const makeIndex = el => indexedKeys.reduce(
(index, key) => `${index};;${el[key]}`, ''
);
arr.forEach(el => {
const index = makeIndex(el);
if (lookup.has(index) && isPrioritizeFormer) {
return;
}
lookup.set(index, el);
});
return Array.from(lookup.values());
};
Sidenote: If you use distinct(listOfTags, ["label", "color"], false), it will return:
[
{id: 1, label: "Hello", color: "red", sorting: 6},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
]
You can use reduce here to get filtered objects.
listOfTags.reduce((newListOfTags, current) => {
if (!newListOfTags.some(x => x.label == current.label && x.color == current.color)) {
newListOfTags.push(current);
}
return newListOfTags;
}, []);
const keys = ['label', 'color'],
const mySet = new Set();
const duplicateSet = new Set();
const result = objList.filter((item) => {
let newItem = keys.map((k) => item[k]).join("-");
mySet.has(newItem) && duplicateSet.add(newItem);
return !mySet.has(newItem) && mySet.add(newItem);
});
console.log(duplicateSet, result);
This can be used to filter duplicate and non duplicate
const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6},
];
let keysList = Object.keys(listOfTags[0]); // Get First index Keys else please add your desired array
let unq_List = [];
keysList.map(keyEle=>{
if(unq_List.length===0){
unq_List = [...unqFun(listOfTags,keyEle)];
}else{
unq_List = [...unqFun(unq_List,keyEle)];
}
});
function unqFun(array,key){
return [...new Map(array.map(o=>[o[key],o])).values()]
}
console.log(unq_List);
We can find the unique value by the below script, we can expand the array using forEach loop and check the value exists on the new array by using some() method and after that create the new array by using push() method.
const arr = [{ id: 1 }, { id: 2 }, { id: 4 }, { id: 1 }, { id: 4 }];
var newArr =[];
arr.forEach((item)=>{
if(newArr.some(el => el.id === item.id)===false){
newArr.push(item);
}
}
);
console.log(newArr);
//[{id: 1}, {id: 2}, {id: 4}];
const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6},
];
const objRes=listOfTags.filter((v,i,s)=>s.findIndex(v2=>['label','color'].every(k=>v2[k]===v[k]))===i);
console.log(objRes);
Maybe helpful. Extract duplicate items from array then delete all duplicates
// Initial database data
[
{ key: "search", en:"Search" },
{ key: "search", en:"" },
{ key: "alert", en:"Alert" },
{ key: "alert", en:"" },
{ key: "alert", en:"" }
]
// Function called
async function removeDuplicateItems() {
try {
// get data from database
const { data } = (await getList());
// array reduce method for obj.key
const reduceMethod = data.reduce((x, y) => {
x[y.key] = ++x[y.key] || 0;
return x;
}, {});
// find duplicate items by key and checked whether "en" attribute also has value
const duplicateItems = data.filter(obj => !obj.en && reduceMethod[obj.key]);
console.log('duplicateItems', duplicateItems);
// remove all dublicate items by id
duplicateItems.forEach(async (obj) => {
const deleteResponse = (await deleteItem(obj.id)).data;
console.log('Deleted item: ', deleteResponse);
});
} catch (error) {
console.log('error', error);
}
}
// Now database data:
[
{ key: "search", en:"Search" },
{ key: "alert", en:"Alert" }
]
One solution is to iterate the array and use a Map of maps to store the value-value pairs that have been encountered so far.
Looking up duplicates this way should be reasonably fast (compared to nested loops or .filter + .find approach).
Also the values could be any primitive type; they are not stringified or concatenated for comparison (which could lead to incorrect comparison).
const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6}
];
let map = new Map();
let result = [];
listOfTags.forEach(function(obj) {
if (map.has(obj.label) === false) {
map.set(obj.label, new Map());
}
if (map.get(obj.label).has(obj.color) === false) {
map.get(obj.label).set(obj.color, true);
result.push(obj)
}
});
console.log(result);
Say I have an array of nested objects like
let vendors = [
{
v_id: 'red',
count: 2,
},
{
v_id: 'blue',
count: 3,
},
{
v_id: 'green',
count: 1,
},
];
And another object "foo" with many properties, one of which is "v_id".
Based on the value of foo.v_id, I want to either update the counts in the "vendors" array or add a new object to "vendors".
If foo.v_id matches one of those in the "vendors" array, the corresponding count increases by 1.
Example:
let foo = {
user_type: 'Other',
v_id: 'blue'
};
Then "vendors" would become:
[
{
v_id: 'red',
count: 2,
},
{
v_id: 'blue',
count: 4,
},
{
v_id: 'green',
count: 1,
},
];
Else, if there is no match of v_id, a new object is added to the "vendors" array with the corresponding v_id & count = 1.
Example:
let foo = {
user_type: 'client',
v_id: 'yellow',
};
Then "vendors" would become:
[
{
v_id: 'red',
count: 2,
},
{
v_id: 'blue',
count: 3,
},
{
v_id: 'green',
count: 1,
},
{
v_id: 'yellow',
count: 1,
},
];
How can I efficiently & elegantly do this in Javascript? I know I can use .filter() to get the specific object in "vendors" that has to be updated but how would I update the "vendors" array itself?
Use Array.find() to search for the object in the array by v_id. If the object is in the array, increment count. If not push a new object:
const vendors = [{"v_id":"red","count":2},{"v_id":"blue","count":3},{"v_id":"green","count":1}];
const foo1 = { user_type: 'Other', v_id: 'blue' };
const foo2 = { user_type: 'client', v_id: 'yellow' };
const addUpdate = (arr, obj) => {
const current = arr.find((o) => o.v_id === obj.v_id);
if(current) current.count += 1;
else arr.push({
v_id: obj.v_id,
count: 1
});
};
addUpdate(vendors, foo1);
addUpdate(vendors, foo2);
console.log(vendors);
function upsert(arr, obj){
const index = arr.findIndex(item => item.v_id === obj.v_id);
index === -1 ? arr.push({v_id: obj.v_id, count: 1}) : arr[index].count++
}
let vendors = [{v_id: 'red',count: 2}, {v_id: 'blue',count: 3}, {v_id: 'green',count: 1}];
let foo1 = {user_type: 'Other', v_id: 'blue'};
let foo2 = {user_type: 'client', v_id: 'yellow'};
upsert(vendors, foo1);
upsert(vendors, foo2);
console.log(vendors);