using array as key to loop through objects in JavaScript - javascript

I am learning JavaScript and have spent a good deal of time practicing looping through arrays and arrays of Objects. I wanted to learn how to use an array as a filter on an array of Objects. I couldn't find any articles that explained how to do this, so I had a go myself using a nested loop. However, I cannot get it to work.
Var catKey[]; is the array holding the data I want to use to filter through var posts[]; , identify which objects have a match in the Property cat: [] and return the title properties. I know I could use array.Filter but I want to be able to do this on the assumption I wont always know the number of items in the catKey array. The use case would be for a situation where I use an event handler that when a link I add is clicked on a Post in WordPress and returns the category Ids, I would then search through the list of Posts to find others that have the same category Ids. Can anyone tell me where I am going wrong.
var catKey = [2, 6];
var posts = [
{
id: 1,
cat: [1, 2, 3],
title: "Hello World"
},
{
id: 2,
cat: [5, 6, 7],
title: "Hello JavaScript"
},
{
id: 3,
cat: [8, 9],
title: "Hello Arrays!"
}
];
for (var i = 0; i < catKey.length; i++) {
for (var j = 0; j < posts.length[i]; j++) {
if (catKey[i] === posts[j].cat) {
document.write(posts[j].title);
}
}
}

To find the first entry to match your conditions you can make use of Array.prototype.find() function:
var catKey = [2, 6];
var posts = [
{ id: 1, cat: [1, 2, 3], title: "Hello World" },
{ id: 2, cat: [5, 6, 7], title: "Hello JavaScript" },
{ id: 3, cat: [8, 9], title: "Hello Arrays!" }
];
const resObj = posts
.find(p => p.cat.some(c => catKey.includes(c)))
.title;
console.log(resObj)
Or to find all, use Array.prototype.filter():
var catKey = [2, 6];
var posts = [
{ id: 1, cat: [1, 2, 3], title: "Hello World" },
{ id: 2, cat: [5, 6, 7], title: "Hello JavaScript" },
{ id: 3, cat: [8, 9], title: "Hello Arrays!" }
];
const resObjs = posts
.filter(p => p.cat.some(c => catKey.includes(c)))
.map(o => o.title);
resObjs.forEach((t) => console.log(t));

Based on your question, I assume catKey contains a whitelist of numbers that the nested cat array should match, i.e. as long as any value in the cat array is found in catKeys, you want to keep them.
In that case, you can simply use .filter() to iterate through all the posts, and evaluate if there is any intersection between the individual post's cat array against the whitelist:
var filteredPosts = posts.filter(function(post) {
return post.cat.filter(function(c) { return catKey.indexOf(c) !== -1; }).length;
});
If you want to try and write in ES6, that's also not a problem: and it's even more concise!
const filteredPosts = posts.filter(post => post.cat.filter(c => catKey.includes(c)).length);
See proof-of-concept below:
var catKey = [2, 6];
var posts = [{
id: 1,
cat: [1, 2, 3],
title: "Hello World"
},
{
id: 2,
cat: [5, 6, 7],
title: "Hello JavaScript"
},
{
id: 3,
cat: [8, 9],
title: "Hello Arrays!"
}
];
var filteredPosts = posts.filter(function(post) {
return post.cat.filter(function(c) { return catKey.indexOf(c) !== -1; }).length;
});
console.log(filteredPosts);

You can use map and find together to check the values in the array with the values in the array of objects. Using map the catKey array is iterated and for every element find is used to find that element in the array inside the object inside the array named post using the .includes() method.
var catKey = [2, 6];
var posts = [{
id: 1,
cat: [1, 2, 3],
title: "Hello World"
},
{
id: 2,
cat: [5, 6, 7],
title: "Hello JavaScript"
},
{
id: 3,
cat: [8, 9],
title: "Hello Arrays!"
}];
console.log(catKey.map((e) => posts.find((x) => x.cat.includes(e))))

var catKey = [2, 6];
var posts = [
{
id: 1,
cat: [1, 2, 3],
title: "Hello World"
},
{
id: 2,
cat: [5, 6, 7],
title: "Hello JavaScript"
},
{
id: 3,
cat: [8, 9],
title: "Hello Arrays!"
}
];
var result = posts.filter(({cat})=>{
return catKey.filter((key)=>{
return cat.includes(key)
}).length > 0
})
console.log(result);
short version
posts.filter(({ cat }) => catKey.filter(key => cat.includes(key)).length > 0);

Related

Convert Nested object into custom array of objects: Javascript

I am having array of objects that look like this
const test = {
a: { name: "A", selected: [1, 2, 3], display: [1, 2, 3] },
b: { name: "B", selected: [4, 5, 6], display: [4, 5, 6] },
c: { name: "C", selected: [7, 8, 9], display: [7, 8, 9] },
d: { name: "D", selected: [], display: [] }
};
I want the above to be converted as below
const output = [
{ field: "A", selectedValues: [1, 2, 3] },
{ field: "B", selectedValues: [4, 5, 6] },
{ field: "C", selectedValues: [7, 8, 9] }
];
Basically key in the input object to be made as field in the final object of that array and selected in input object should be made as selectedValues in the final object. Note only the object that has some entries selected should be put into final arrray
Also when all the objects in the input object have selected as empty then just return empty array else return the above output.
Code that I tried
const result = Object.entries(test).map(([name, v]) => ({
field: name,
selectedValues: v
}));
console.log(result);
Because you want
selected in input object should be made as selectedValues in the final object.
you should navigate to the .selected property (the subarray) while mapping, instead of referencing the whole object. Afterwards, filter by whether that array has any values in it.
const test = {
a: { name: "A", selected: [1, 2, 3], display: [1, 2, 3] },
b: { name: "B", selected: [4, 5, 6], display: [4, 5, 6] },
c: { name: "C", selected: [7, 8, 9], display: [7, 8, 9] },
d: { name: "D", selected: [], display: [] }
};
const result = Object.entries(test)
.map(([name, obj]) => ({
field: name,
selectedValues: obj.selected
}))
.filter(({ selectedValues }) => selectedValues.length);
console.log(result);

How can I replace array item with matched object keys?

Let's say I have an array of id's
const ids = [[1, 2, 3], [2, 3], [3]]
And have array with objects that have name for each id
const obj = [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
];
What is the most proper way to get ids = [["One", "Two", "Three"], ["Two", "Three"], ["Three"]], I'm worrying that nested mapping could cause performance issues.
Use a combination of map and find. There may be a more performant way of doing it, but I'd suggest worrying about that only if you run into performance issues :-
const ids = [[1, 2, 3], [2, 3], [3]];
const obj = [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
];
const mapped = ids.map(arr => arr.map(id => obj.find(obj => obj.id === id).name));
const obj = [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
];
const ids = [[1, 2, 3], [2, 3], [3]] ;
// dedicated object to keep association between id and names
let names = {} ;
obj.forEach( o => {
names[ o.id ] = o.name ;
} ) ;
// map each sub array content to their real name
for( let i = 0; i <= ids.length-1; i++){
ids[i] = ids[i].map( id => names[id] ) ;
}
console.log( ids ) ;
=>
[ [ 'One', 'Two', 'Three' ], [ 'Two', 'Three' ], [ 'Three' ] ]

JavaScript array of objects contains every element of another array

I have an array of objects (array1).
I want to filter every element, that includes ALL of the tags ([1, 2, 3).
So the result should be id 1. But i cannot make it work. The results i get are id 1, 2 ,4 and i do not understand why exactly it acts like this.
let array1 = [
{ id: 1, tags: [1, 2, 3] },
{ id: 2, tags: [2, 3] },
{ id: 3, tags: [0, 3] },
{ id: 4, tags: [1, 3] }
];
let tags = [1, 2, 3];
let includesAll = array1.filter((a1) =>
a1.tags.every((tag) => tags.includes(tag))
);
console.log(includesAll);
You should do the opposite. Instead of verifying that every value in a1.tags is also in tags, you'll want to verify that every value in tags is also in a1.tags:
let includesAll = array1.filter((a1) =>
tags.every((tag) => a1.tags.includes(tag))
);
let array1 = [
{ id: 1, tags: [1, 2, 3] },
{ id: 2, tags: [2, 3] },
{ id: 3, tags: [0, 3] },
{ id: 4, tags: [1, 3] }
];
let tags = [1, 2, 3];
let includesAll = array1.filter((a1) =>
tags.every((tag) => a1.tags.includes(tag))
);
console.log(includesAll);

Sum array of arrays (matrix) vertically

How can I sum vertically all data from an array of arrays?
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
var result = arrayOfArrays.reduce(function(array1, array2) {
return array1.data.map(function(value, index) {
return value + array2.data[index];
}, 0);
});
console.log(result)
The output should be the vertical sum of arrays.
[3,6,9,12,15,18,21,24]
The problem is that array1 return always as undefined.
You code is almost correct but with 1 issues.
You are looping on accumulator. This will be an array of number in second iteration. Instead loop over array2 or current item.
Idea of .reduce is to have same signature for all iteration. If you do not pass default value for accumulator, first iteration will be of type Array<{ label: string, data: Array<number>}> and second iteration will be just Array<number>. So you can skip behavior for first iteration by passing default value as []. Now the calculation will break as array[n] will be undefined. For this, you can use a default value of 0.
So your calculation will look like:
value + (array1[index] || 0)
Following is a sample:
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
var result = arrayOfArrays.reduce(function(array1, array2) {
return array2.data.map(function(value, index) {
return value + (array1[index] || 0);
}, 0);
}, []);
console.log(result)
Use the index/key of map and add to the previous value.
const arrayOfArrays = [{label:'First Value', data:[1,2,3,4,5,6,7,8]},{label:'Second Value', data:[1,2,3,4,5,6,7,8]},{label:'Third Value', data:[1,2,3,4,5,6,7,8]}];
const res = arrayOfArrays.reduce((acc, cur) => (cur.data.map((i, k) => {acc[k] = acc[k] ? acc[k] += i : i}), acc), [])
console.log(res)
you're using reduce in a wrong way, but heres a for loop that does the same job:
arrayOfArrays = [{
label:'First Value', data:[1,2,3,4,5,6,7,8]},{
label:'Second Value', data:[1,2,3,4,5,6,7,8]},{
label:'Third Value', data:[1,2,3,4,5,6,7,8]
}];
const newArr = [];
for(let x = 0; x < arrayOfArrays[0].length; x++){
newArr.push(arrayOfArrays[0].data[x]+arrayOfArrays[1].data[x]+arrayOfArrays[2].data[x])
}
console.log(newArr); // new array
You can flatten the array by looping the array of objects and pushing the data property to a new array, then use reduce/map on the flattened data:
arrayOfArrays = [
{label:'First Value', data:[1,2,3,4,5,6,7,8]},
{label:'Second Value', data:[1,2,3,4,5,6,7,8]},
{label:'Third Value', data:[1,2,3,4,5,6,7,8]}
];
var data = [];
arrayOfArrays.forEach((element)=> {
data.push(element.data)
})
var sum = (r, a) => r.map((b, i) => a[i] + b);
var result = data.reduce(sum);
console.log(result);
Which outputs:
[3, 6, 9, 12, 15, 18, 21, 24]
Working fiddle
If you know that the length of each array is same. you can do as follows
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
let out = arrayOfArrays.reduce((acc, {data}) => acc.map((e, i) => e+data[i]), new Array(8).fill(0));
console.log(out)
You are passing the wrong accumulator which should be an array also in wrong place, it must be with reduce not with map
var result = arrayOfArrays.reduce(function (array1, array2) {
return array1.map(function (value, index) {
return value + array2.data[index];
});
}, Array(8).fill(0));
I would do it like this:
Introduce a helper transport function:
const transport = (arr) => arr[0].map((col, i) => arr.map(row => row[i]));
Get a proper matrix:
const matrix = arrayOfArrays.map(el => el.data)
Then the task becomes trivial:
const res = transport(matrix).map(arr => arr.reduce((x, y) => x + y))
// > (8) [3, 6, 9, 12, 15, 18, 21, 24]
You could take advantage of function generators in case you need to later transform or alterate the values, or just iterate them without needing the entire result set.
In this solution, a function generator is used and the logic applied is:
Get the array with the longest length (assuming length might change)
Get all the elements at index i from 0 to longest length and yield their sum.
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
/**
Sums elements of arrays inside the array vertically.
*/
function* sumVertically(arr) {
// Get the longest array.
const longestArrayLength = arr.sort(({length: l1}, {length: l2}) => l1 - l2)[0].length;
// Acquire all elements at index [i] of each array and sum them. Yield the sum.
for (let i = 0; i < longestArrayLength; i++) yield arr.map(e => e[i]).reduce((a,b) => a + b, 0);
}
const result = [...sumVertically(arrayOfArrays.map(i => i.data))];
console.log(result);

underscore js mapping array of objects multiple properties to new array

var items = [{
//other properties... above
item_name: [
[1],
[2, 3]
],
item_description: [
[1],
[3, 4]
],
item_quantity: [
[1],
[4, 5]
],
item_value: null,
}, {
//other properties... above
item_name: 1,
item_description: 2,
item_quantity: 3,
item_value: 4,
}, {
//other properties... above
item_name: [1, 2, 3],
item_description: [1, 2, 3],
item_quantity: [1, 2, 3],
item_value: [1, 2, 3],
}];
var itemList = [];
items.forEach(function(item) {
if (!_.isArray(item.item_name)) {
itemList.push({
name: item.item_name,
description: item.item_description,
quantity: item.item_quantity,
value: item.item_value
});
}
var names = item.item_name ? _.flatten(item.item_name) : [];
var descriptions = item.item_description ? _.flatten(item.item_description) : [];
var quantity = item.item_quantity ? _.flatten(item.item_quantity) : [];
var values = item.item_value ? _.flatten(item.item_value) : [];
names.forEach(function(name, index) {
itemList.push({
name: names[index],
description: descriptions[index],
quantity: quantity[index],
values: values[index]
});
})
});
console.log(itemList);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.1/underscore-min.js"></script>
is there a way I can perform this faster in underscore, to remove all of the flattens?
for each item in the array I am taking
item_name[i]
item_description[i]
item_quantity[i]
item_value[i]
and adding them to the itemList
item properties in items can be [[],[]] or [] or integer or null
currently it is outputting what is expected (unless a name is null and it can skip items) however I do not like all of the loops this is performing and I am wondering if I can make a better use of underscore library
You can use this:
var myKeys = ['name', 'description', 'quantity', 'value'];
var result = _.flatten(items.map(function(item) {
return _.zip.apply(_, myKeys.map(function(key) {
return _.flatten([item['item_'+key]]);
})).map(function(arr) {
return _.object(myKeys, arr);
});
}));
Demo:
var items = [{
//other properties... above
item_name: [
[1],
[2, 3]
],
item_description: [
[1],
[3, 4]
],
item_quantity: [
[1],
[4, 5]
],
item_value: null,
}, {
//other properties... above
item_name: 1,
item_description: 2,
item_quantity: 3,
item_value: 4,
}, {
//other properties... above
item_name: [1, 2, 3],
item_description: [1, 2, 3],
item_quantity: [1, 2, 3],
item_value: [1, 2, 3],
}];
var myKeys = ['name', 'description', 'quantity', 'value'];
var result = _.flatten(items.map(function(item) {
return _.zip.apply(_, myKeys.map(function(key) {
return _.flatten([item['item_'+key]]);
})).map(function(arr) {
return _.object(myKeys, arr);
});
}));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.1/underscore-min.js"></script>

Categories

Resources