Concatenate Nested Array after Mapping - javascript

I have an array with several category objects, each of which has an items property containing an array of item objects. I want to map each item in each category to an object[] with objects that have the properties value and label. For some reason, I can't perform the concatenation.
var categories = [{
name: "category1",
items: [{
itemId: 1,
name: "Item1"
}, {
itemId: 2,
name: "Item2"
}]
}, {
name: "category2",
items: [{
itemId: 3,
name: "Item3"
}, {
itemId: 4,
name: "Item4"
}]
}];
var items = [];
for(var i = 0; i < categories.length; i++){
items.concat($.map(categories[i].items,function(elem){
return {value:elem.itemId, label:elem.name};
}));
}
console.log(items); //prints []
Expected Result
[{
label: "Item1",
value: "1"
},
{
label: "Item2",
value: "2"
},{
label: "Item3",
value: "3"
},{
label: "Item4",
value: "4"
}
I feel as if I am missing something very basic. I logged the result of the $.map function and it appears to be returning an []. Can anyone figure out the issue?
JSFiddle: http://jsfiddle.net/vymJv/

Another method using straight Javascript:
var x = categories.map(function(val) {
return val.items;
}).reduce(function(pre, cur) {
return pre.concat(cur);
}).map(function(e,i) {
return {label:e.name,value:e.itemId};
});
Output: x = [{label: "Item1", value: 1}, {label: "Item2", value: 2}, …]

The concat() method is used to join two or more arrays.
This method does not change the existing arrays, but returns a new
array, containing the values of the joined arrays.
http://jsfiddle.net/vymJv/1/
for(var i = 0; i < categories.length; i++){
items = items.concat($.map(categories[i].items, function(elem) {
return {value: elem.itemId, label: elem.name};
}));
}

updated with flatMap(not compatible with IE)
categories.flatMap((categories) => categories.items)
flatMap() method returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.

const items = categories
.map(category => category.items)
.reduce((prev, current) => [...prev, ...current])
.map((e, i) => ({ label: e.name, value: e.itemId }));

We could extend the array prototype by creating concatAll which will concatenate all of your arrays by iterating over each subarray, dumping each value into a new array.
Array.prototype.concatAll = function() {
var results = [];
this.forEach(function(subArray) {
subArray.forEach(function(subArrayValue) {
results.push(subArrayValue);
});
});
return results;
};
Then, we can get the desired result with the following:
let items = categories.map(function(category) {
return category.items.map(function(item) {
return {label: item.name, value: item.itemId};
});
}).concatAll();
We get the items by translating each category into an array of items. Because we have two categories, we will end up with two arrays of items. By applying concatAll on the final result, we flatten those two arrays of items and get the desired output.

This piece of code solves your task using a functional programming approach:
var items = [].concat.apply([], categories.map(cat =>
cat.items.map(elem => ({ value:elem.itemId, label:elem.name })))
)
Explanation: Function.prototype.apply() has the syntax fun.apply(thisArg, [argsArray]) and lets us provide parameters to a function in an array. Array.prototype.concat() combines an arbitrary amount of parameters into one array. If we now write Array.prototype.concat.apply([], [category1Items, ..., categoryNItems]), it actually equals [].concat(category1Items, ..., categoryNItems), which concatenates all parameters together. You can also replace Array.prototype.concat by [].concat to keep it shorter. Otherwise we just use standard mapping to get the job done.
You could also split the code apart a bit more for clarity:
function getItem(elem){
return {value:elem.itemId, label:elem.name};
}
function getCategoryItems(cat) {
return cat.items.map(getItem);
}
function flatten(arr) {
return Array.prototype.concat.apply([], arr);
}
var items = flatten(categories.map(getCategoryItems));

Related

removing object from array with filters doesnt work in javascript

I am trying to remove an object from an array but it I dont know what I am doing wrong.
I have this array declared:
listA: [
{ title: 'Food', value: 'Patato' },
{ title: 'Drink', value: 'Cola' },
{ title: 'Desert', value: 'Cheesecake' },
],
I am trying to remove the object where its value is 'Cola', what I have tried is this:
this.listA.filter(x => x.value !== 'Cola');
And it returns me the same list
I want to return this:
listA: [
{ title: 'Food', value: 'Patato' },
{ title: 'Desert', value: 'Cheesecake' },
],
Your code should be filtering just fine, I think the issue here is that filter does not modify the original array, it returns a new array with the filtered results. If you want it to overwrite the original array, you'll need to say this.listA = this.listA.filter(...)
const listA = [
{ title: "Food", value: "Patato" },
{ title: "Drink", value: "Cola" },
{ title: "Desert", value: "Cheesecake" },
];
const result = listA.filter((obj) => obj.value !== 'Cola');
Looks like you need to do something like
this.listA = this.listA.filter(x => x.value !== 'Cola')
The filter method is immutable hence the original array isn't changed
As a complement to https://stackoverflow.com/a/70688107/6316468, here is what filter does under the hood (the original array this remains untouched):
var xs = [1, 2, 3, 4];
var ys = filter.call(
xs, x => x % 2
);
console.log(
"xs = [", xs.join(), "]"
);
console.log(
"ys = [", ys.join(), "]"
);
function filter(predicate) {
var xs = []; // new array
for (let x in this) {
if (predicate(x)) {
xs.push(x);
}
}
return xs;
}

javascript how to form an array of objects by combining first Array and their values are in other arrays

Im facing a small issue in Javascript.
I have below Arrays.
var labels = ["labelOne", "labelTwo"];
var values1 = ["89", "9"];
var values2 = ["32", "78"];
Here we can place n number of values arrays like value3,value4....
Now how can i form an array of Objects by combining labels Array and their values are in values arrays. Im expecting the below output after combining above 3 arrays..
var mainArray = [{
label:"labelOne",
value:"89"
},
{
label:"labelTwo",
value:"9"
},
{
label:"labelOne",
value:"32"
},
{
label:"labelTwo",
value:"78"
}]
Can someone please help me to achieve the above output.
Thank you in advance
All that you need is a variable to know how many arrays should be added and access them in a loop using the advantage that Javascript lets you get them like this: window['variableName'] when they are defined in global scope.
var labels = ["labelOne", "labelTwo"];
var values1 = ["89", "9"];
var values2 = ["32", "78"];
var mainArray = [];
// Define a variable to know how many arrays should be added
var maxValues = 2;
function addValues(values) {
// Create new elements and push them into mainArray
mainArray.push({label:labels[0], value:values[0]});
mainArray.push({label:labels[1], value:values[1]});
}
// Do a loop from 1 to maxValues
for(let i = 1; i <= maxValues; i++) {
// Call the function with dynamic variable name
addValues(window['values' + i]);
}
console.log(mainArray);
If the order of your array isn't critical (and then, you might sort it later if it is), you can do like this:
const output = labels.map((label, index) => {
return [{ label, value: values1[index] }, { label, value: values2[index] }];
}).flat();
The map step, will give you an array like this:
[
[{ label: 'labelOne', value: 89 }, { label: 'labelOne', value: 32 }],
[{ label: 'labelTwo', value: 9}, { label: 'labelTwo', value: 78}]
]
By then calling flat, it'll transform it into:
[{ label: 'labelOne', value: 89 }, { label: 'labelOne', value: 32 }, { label: 'labelTwo', value: 9}, { label: 'labelTwo', value: 78}]
Which is what you wanted, from here you can sort the array if that matters for your use case.

Iterate through object keys and values and return a new object

I've got an array of objects, which share common property names, but have different values. For example, they go like this:
let data = [
{
value1: "11:00",
value2: 0.737462,
value3: 1.345341,
value4: 0.684655
},
{
value1: "12:00",
value2: 0.894368,
value3: 1.55891,
value4: 0.784655
},
{
value1: "13:00",
value2: 1.140516,
value3: 1.938695,
value4: 0.454655
}
]
From these objects, I need to form a new array of objects, similar to this:
let datasets = [
{
label: "value1",
data: ["11:00", "12:00", "13:00"]
},
{
label: "value2",
data: [0.737462,0.894368,1.140516,]
}
// and so on
]
So that each object in my dataset would contain the original object's property name as a value for label and the array of properties that correspond to that name. I've tried to do it like this:
let datasets = data.map((n) => {
for (i in n) {
return {
label: i,
data: data.map(obj => obj[i])
}
}
});
But it doesn't work as expected -- it returns just an array of objects with only the first property name as a label throughout them all. I guess it's because it returns as soon as it gets to the first item in the for...in loop, but I can't figure out a better way to achieve what I need.
The number of elements in the expected array is equal to the number of keys in an array element so map over that, then run another simple map to generate the data array.
var out = Object.keys(data[0]).map(function(key) {
return {
label: key,
data: data.map(function(obj) {
return obj[key];
})
};
}
You can do this with reduce() and ES6 Map.
let data = [{"value1":"11:00","value2":0.737462,"value3":1.345341,"value4":0.684655},{"value1":"12:00","value2":0.894368,"value3":1.55891,"value4":0.784655},{"value1":"13:00","value2":1.140516,"value3":1.938695,"value4":0.454655}]
var result = [...data.reduce(function(r, e) {
return Object.keys(e).forEach(k => {
if(!r.has(k)) r.set(k, {label: k, data: [e[k]]})
else r.get(k).data.push(e[k])
}), r
}, new Map).values()]
console.log(result)
const datasets = data.reduce((acc, a) => {
return acc.map(set => Object.assign({}, set, { data: set.data.concat(a[set.label])}));
}, Object.keys(data[0]).map(k => ({label: k, data: []})));
Working fiddle here.

How to combine _.map and _.filter in a more efficient way?

I am using Lodash in my Angular project and I was wondering if there is a better way to write the following code:
$scope.new_arr = _.map(arr1, function(item){
return _.assign(item, {new_id: _.find(arr2, {id: item.id})});
});
$scope.new_arr = _.filter($scope.new_arr, function (item) {
return item.new_id !== undefined;
});
I am trying to combine values from one array to same objects in other array, and I want to ignore the objects that not appear in both arrays (it is something like join or left outer join in the sql language).
Here is a fiddle with an example of this code: Click me!
i think is better to use chaining
$scope.new_arr = _.chain(arr1)
.map(function(item) {
return _.merge(
{}, // to avoid mutations
item,
{new_id: _.find(arr2, {id: item.id})}
);
})
.filter('new_id')
.value();
https://jsfiddle.net/3xjdqsjs/6/
try this:
$scope.getItemById = (array, id) => {
return array.find(item => item.id == id);
};
$scope.mergeArrays = () => {
let items_with_ids = arr1.filter(item => !_.isNil($scope.getItemById(arr2,item.id)));
return items_with_ids.map(item => _.assign(item, {new_id: $scope.getItemById(arr2,item.id)}));
};
The answers provided here are all runtime of O(n^2), because they first run an outer loop on the first array, with an inner loop on the second array. You can instead run this in O(n). First, create a hashmap of all the ids in arr2 in a single loop; this will allow us an order 1 lookup. In the second loop on arr1, check this hashmap to determine if those items exist with O(n). Total Complexity is n + n = 2n, which is just O(n).
// provision some test arrays
var arr1 = [
{
id: 2
},
{
id: 4
},
{
id: 6
}
]
var arr2 = [
{
id: 3
},
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
// First, we create a map of the ids of arr2 with the items. Complexity: O(n)
var mapIdsToArr2Items = _.reduce(arr2, function(accumulator, item) {
accumulator[item.id] = item;
return accumulator;
}, {});
// Next, we use reduce (instead of a _.map followed by a _.filter for slightly more performance.
// This is because with reduce, we loop once, whereas with map and filter,
// we loop twice). Complexity: O(n)
var combinedArr = _.reduce(arr1, function(accumulator, item) {
// Complexity: O(1)
if (mapIdsToArr2Items[item.id]) {
// There's a match/intersection! Arr1's item matches an item in arr 2. Include it
accumulator.push(item);
}
return accumulator;
}, []);
console.log(combinedArr)
You could first make a Map with arr1 and then map the items of arr2 with the properties of arr1.
var arr1 = [{ id: 1, title: 'z' }, { id: 2, title: 'y' }, { id: 3, title: 'x' }, { id: 4, title: 'w' }, { id: 5, title: 'v' }],
arr2 = [{ id: 2, name: 'b' }, { id: 3, name: 'c' }, { id: 4, name: 'd' }, { id: 5, name: 'e' }],
map = new Map(arr1.map(a => [a.id, a])),
result = arr2.map(a => Object.assign({}, a, map.get(a.id)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Create array of keys by sorting values

I have an object that looks like this
myList: {
id1:{
ts:'2010-01-12T00:51:00',
name:"roger"
},
id2:{
ts:'2011-01-12T05:22:00',
name: "Tom"
},
id3:{
ts:'2013-01-12T11:32:00',
name:"Jack"
}
}
I know objects cant be sorted so i wanted to know how i can generate an array of just the keys,which are sorted according to the key "ts". I want this in descending order.
So the array for the above object will be [id3,id2,id1]
once i have this array i can make operations like this where arr is sorted array and myList is the object
for(var i=0:i<arr.length;i++)
{
alert(myList[arr[i]].name);
}
var keys = Object.keys(myList).sort(function(a, b) {
if (myList[a].ts == myList[b].ts) {
return 0;
}
return myList[a].ts < myList[b].ts ? 1 : -1;
});
console.log(keys);
jsfiddle: http://jsfiddle.net/pWq2L/
Explanation:
First you export keys from the object: Object.keys(myList)
You sort using custom comparison function and in it you refer to the .ts attribute of the compared values. a and b are the keys of the compared elements
References:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Given this data structure, and assuming there is a good reason for it to not be an array:
var myList = {
id1: {
ts: new Date('2010-01-12T00:51:00'),
name: 'Roger'
},
id2: {
ts: new Date('2011-01-12T05:22:00'),
name: 'Tom'
},
id3:{
ts: new Date('2013-01-12T11:32:00'),
name: 'Jack'
}
}
We can create and sort an array like so:
var arr = [];
Object.keys(myList).forEach(function(item) {
arr.push(myList[item]);
});
arr.sort(function(a, b) {
return a.ts > b.ts ? -1 : a.ts < b.ts ? 1 : 0;
})
One way to extend zerkms solution is to work with an object augmented with smart properties. The versatile reduce comes in handy:
var result = keys.reduce(function(arr, k, i) {
var item = {
key: k,
index: i,
value: myList[k]
};
arr.push(item);
return arr;
}, []);
//result:
[
{ index:0 , key: id3, value: {...} },
{ index:1 , key: id2, value: {...} },
{ index:2 , key: id1, value: {...} }
]
reduce is part of the ECMAScript 5th edition; so you may fill in some gap with a polyfill.
http://jsfiddle.net/pWq2L/3/

Categories

Resources