Mapping 2 related arrays together - javascript

I'm looking for the cleanest and coolest way to merge related arrays together in JavaScript.
My example is this:
I get two JSON arrays from my API: Issues and Locations.
Issues have a location_id and as a result I want to give each Issue a location field which has the correct location object depending on the Issue's location_id.
If I had this data:
var issues = [{id: 1, title: 'issue 1', location_id: 1}, {id: 12, title: 'issue 1', location_id: 2}];
var locations = [{id: 1, name: 'location 1'}, {id: 2, name: 'location 2'}];
The ugly solution would be:
for(i = 0; i < issues.length; ++i) {
for(j = 0; j < locations.length; ++j) {
if(issues[i].location_id == locations[j].id) {
issues[i].location = locations[j];
break;
}
}
}
The resulting issues array would be:
[[object Object] {
id: 1,
location: [object Object] {
id: 1,
name: "location 1"
},
location_id: 1,
title: "issue 1"
}, [object Object] {
id: 12,
location: [object Object] {
id: 2,
name: "location 2"
},
location_id: 2,
title: "issue 1"
}]
I was trying (and failing) to come up with a shorter solution or even a one liner using .map().
Any guidance appreciated!! :)

Use map and filter:
issues.map(function (issue) {
issue.location = locations.filter(function(location) {
return issue.location_id === location.id;
})[0];
return issue;
});

Use an object to keep the map, then time complexity will be O(n+m) instead of O(n*m).
var issues = [{id: 1, title: 'issue 1', location_id: 1}, {id: 12, title: 'issue 1', location_id: 2}];
var locations = [{id: 1, name: 'location 1'}, {id: 2, name: 'location 2'}];
var binds = function(locationList, issueList) {
var locMap = {};
// clone. If you want to directly modify the issues, this line is no need.
issueList = JSON.parse(JSON.stringify(issueList));
// construct map from location list.
locationList.forEach(function(location) {
locMap[location.id] = location;
});
// Use the map to bind location and issue.
issueList.forEach(function(issue) {
var loc = locMap[issue.location_id];
if (loc) {
issue.location = loc;
}
});
// If you don't want to clone, this line is no need.
return issueList;
};
var bindResult = binds(locations, issues);
console.log(bindResult);

You can use forEach and filter
var result = issues.forEach(function (issue) {
issue.location = locations.filter(function (location) {
return location.id == issue.location_id;
})[0]
})
which gives you this output when you console.log(result)
[{
"id": 1,
"title": "issue 1",
"location_id": 1,
"location": {
"id": 1,
"name": "location 1"
}
}, {
"id": 12,
"title": "issue 1",
"location_id": 2,
"location": {
"id": 2,
"name": "location 2"
}
}]

1. you can optimize your code to :
var locationObj = {};
for(i = 0; i < location.length; i++) {
locationObj[location[i].id] = location[i];
}
for(i = 0; i < issues.length; i++) {
if(issues[i].location_id){
issues[i].location = locationObj[location_id]
}
}
It will cache all location in one object and will directly use that location detail it as that object's property.It will be faster in execution rather than using filter or map on location array each time.
2. if your location's id and location array's index are in sync then following would be a better and faster solution.
for(i = 0; i < issues.length; i++) {
if(issues[i].location_id){
issues[i].location = locations[location_id-1]
}
}

Related

Filter the object depending on the field's value in javascript

I'm kinda new to javascript. Here I have the following object:
obj = {
0:{id:1, location: loc1, title:title1},
1:{id:2, location: loc2, title:title2},
2:{id:3, location: loc1, title:title3},
3:{id:4, location: loc3, title:title4},
4:{id:5, location: loc1, title:title5}
}
What I need is to filter the object by location depending on its value and create a new object like the following:
obj = {
loc1:{
0:{id:1, location: loc1, title:title1},
1:{id:3, location: loc1, title:title3},
2:{id:5, location: loc1, title:title5}
},
loc2:{
0:{id:2, location: loc2, title:title2}
}
loc3:{
0:{id:4, location: loc3, title:title4}
}
}
How can I achieve the above object?
I tried using for and push to a new array but the location should be dynamic and may change in the future and I want to have one object to manage like above.
var theLoc1 = [], theLoc2 = [];
for(var i = 0; i < response.length; i++) {
if(response[i].location == 'loc1'){
theLoc1.push(response[i]);
}else if(response[i].location == 'loc2'){
theLoc2.push(response[i]);
}
}
This Code is what u really need:
obj = [
{ id: 1, location: 'loc1', title: 'title1' },
{ id: 2, location: 'loc2', title: 'title2' },
{ id: 3, location: 'loc1', title: 'title3' },
{ id: 4, location: 'loc3', title: 'title4' },
{ id: 5, location: 'loc1', title: 'title5' }
];
var locations = {};
for (var i = 0; i < obj.length; i++) {
locations[obj[i].location] = [];
}
console.log(locations);
for (var i = 0; i < obj.length; i++) {
locations[obj[i].location].push(obj[i]);
}
console.log(locations);
**Update:It Can be done in a single for loop but for simplicity reasons i wrote it like this. **
let obj;
for(var i = 0; i < response.length; i++) {
if( !Object.hasOwnProperty(obj, response[i].location)
{ obj[response[i].location] = []; }
obj[response[i].location].push(response[i]);
}
You can dynamically create JS object properties if you just address them. This means:
let obj = {};
obj.bark = "how-how";
console.log(obj.bark); // "how-how";
obj[bark2] = "waf-waf";
console.log(obj.bark2); // "waf-waf";
you can use it to struct your new object with the locations names, so even if someday you get "location999" it'll still work.
I put the if that checks if the object laready has that property because you want the property to be an array. If it wasn't you could've just put the value inside like in my example, but im not sure if push would work on it so I initialize it to be empty array just in case. You can check it yourself and ommit the if if its not needed.
My solution using functional programming.
const obj = {
0: { id: 1, location: 'loc1', title: 'title1' },
1: { id: 2, location: 'loc2', title: 'title2' },
2: { id: 3, location: 'loc1', title: 'title3' },
3: { id: 4, location: 'loc3', title: 'title4' },
4: { id: 5, location: 'loc1', title: 'title5' }
};
const result = Object.keys(obj).reduce((newObject, item) => {
const location = obj[item].location;
const index = newObject[location] ? Object.keys(newObject[location]).length : 0;
return {
...newObject,
[location]: {
...newObject[location],
[index]: obj[item]
}
};
}, {});
console.log(result);
In order to group your items by location you can iterate your array, see whether its location was already grouped and if not, create a new group for it. Afterwards add the item to the corresponding group.
var obj = [
{id: 1, location: "loc1", title: "title1"},
{id: 2, location: "loc2", title: "title2"},
{id: 3, location: "loc1", title: "title3"},
{id: 4, location: "loc3", title: "title4"},
{id: 5, location: "loc1", title: "title5"}
];
var formattedArray = new Array();
for (var i = 0; i < obj.length; i++) {
if (!formattedArray[obj[i].location]) {
formattedArray[obj[i].location] = new Array();
}
formattedArray[obj[i].location].push(obj[i]);
}
console.log(formattedArray);
JsFiddle example code:
JsFiddle
You can try the following if your loc1, loc2, loc3 are fixed. (That is what I understood after reading your query)
var response = [
{id:1, location: "loc1", title:"title1"},
{id:2, location: "loc2", title:"title2"},
{id:3, location: "loc1", title:"title3"},
{id:4, location: "loc3", title:"title4"},
{id:5, location: "loc1", title:"title5"}
]
var resObj = {
published:[],
private: [],
pending:[]
}
for(var i = 0; i < response.length; i++) {
if(response[i].location == 'loc1'){
resObj.published.push(response[i]);
}else if(response[i].location == 'loc2'){
resObj.private.push(response[i]);
}else {
resObj.pending.push(response[i]);
}
}
console.log(resObj)
I think the better way to do this, is to group your objects already in your backend. You can use the linq function .GroupBy(x => x.location).
This is near the same problem:
How to count rows of a table grouped by shortdatestring?
// create an array of arrays;
var groupOfLocations[];
// loop on your locations
for(var i = 0; i < response.length; i++) {
// push if already existing
for(var iGroup = 0; iGroup < groupOfLocations.length; iGroup++) {
if(groupOfLocations[iGroup][0].location == response[i].location) {
groupOfLocations[iGroup].push(response[i]); break;
}
// create a new array if not found
if(iGroup >= groupOfLocations.length) groupOfLocations.push(new array(response[i]));
}
May contains syntax mistakes, but the idea is here.

problems with for loop inside another for loop Javascript

I have problems in going through these two for loops, I need to get the same elements from the first array within the cycle, but the values ​​are being repeated. I know that they are repeated depending on the data of the second array.
I tried to make comparisons but I could not get the result I want.
var array = [
{
grouper: 1
},
{
grouper: 2
},
{
grouper: 3
},
{
grouper: 4
},
];
var array2 = [
{
value: 1,
grouper: 1,
status: 100
},
{
value: 2,
grouper: 2,
status: 100
},
{
value: 3,
grouper: 3,
status: 100
}
];
for(var i = 0; i<array.length; i++){
for(var j = 0; j<array2.length; j++){
if(array2[j].grouper == array[i].grouper){
console.log(array[i].grouper+'-'+array2[j].value);
}
}
}
This is the result I want, I need all the groupers from the first array and the values from the second array:
1-1
2-2
3-3
4-
The grouper 4, does not have value, but I need to show it.
I need the second array because I'm going to compare with the data from the second array
I do not know if I am doing the process wrong. I hope you can help me.
You could simply track if there was a match (variable shown), and if there were not any, display a "half" line:
var array = [{grouper: 1},{grouper: 2},{grouper: 3},{grouper: 4},];
var array2 = [
{value: 1, grouper: 1, status: 100},
{value: 2, grouper: 2, status: 100},
{value: 3, grouper: 3, status: 100}
];
for(var i = 0; i<array.length; i++){
var shown=false;
for(var j = 0; j<array2.length; j++){
if(array2[j].grouper == array[i].grouper){
console.log(array[i].grouper+'-'+array2[j].value);
shown=true;
}
}
if(!shown){
console.log(array[i].grouper+"-");
}
}
First of all, with the example you provided I believe you want to get back:
1,2,3
There is no 4th object inside of array2, so your conditional (array2[j].grouper == array[i].grouper will never evaluate to true.
The question here is whether you are always comparing the same indexes? In this example, you're comparing array[0] to array2[0] to see if grouper in array equals grouper in array2... that's it????
In that case you just do one loop:
for (var i = 0; i < array.length; i++) {
if (array[i].grouper == array2[i].grouper) {
console.log(array[i].grouper+'-'+array2[j].value);
}
}
#FabianSierra ... with your provided example one just needs to handle the not fulfilled if clause/condition in the most inner loop.
A more generic approach additionally might take into account changing field names (keys). Thus a function and Array.reduce / Array.find based approach provides better code reuse. An example implementation then might look similar to that ...
var array = [{ // in order.
grouper: 1
}, {
grouper: 2
}, {
grouper: 3
}, {
grouper: 4
}];
var array2 = [{ // not in the order similar to `array`.
value: 22,
grouper: 2,
status: 200
}, {
value: 33,
grouper: 3,
status: 300
}, {
value: 11,
grouper: 1,
status: 100
}];
function collectRelatedItemValuesByKeys(collector, item) {
var sourceKey = collector.sourceKey;
var targetKey = collector.targetKey;
var targetList = collector.targetList;
var resultList = collector.result;
var sourceValue = item[sourceKey];
var targetValue;
var relatedItem = targetList.find(function (targetItem) {
return (targetItem[sourceKey] === sourceValue);
});
if (typeof relatedItem !== 'undefined') {
targetValue = relatedItem[targetKey];
} else if (typeof targetValue === 'undefined') {
targetValue = ''; // `relatedItem` does not exist.
}
resultList.push([sourceValue, targetValue].join('-'));
return collector;
}
var resultList = array.reduce(collectRelatedItemValuesByKeys, {
sourceKey: 'grouper',
targetKey: 'value',
targetList: array2,
result: []
}).result;
console.log('resultList : ', resultList);
resultList = array.reduce(collectRelatedItemValuesByKeys, {
sourceKey: 'grouper',
targetKey: 'status',
targetList: array2,
result: []
}).result;
console.log('resultList : ', resultList);
.as-console-wrapper { max-height: 100%!important; top: 0; }

Sort API call response with js

After I request some data with an API call, I want to sort this data. The call returns the following structure:
var values =[
{id: 1, type: "Gas", name: "G1,6", contractedPower: "2.5"},
{id: 2, type: "Gas", name: "G10", contractedPower: "2.5"},
{id: 3, type: "Gas", name: "G2,6", contractedPower: "2.5"},
{id: 4, type: "Gas", name: "G100", contractedPower: "2.5"},
{id: 5, type: "Electricity", name: "1X4A", contractedPower: "0.8"},
{id: 6, type: "Electricity", name: "1X6A", contractedPower: "0.8"},
{id: 7, type: "Electricity", name: "1X10A", contractedPower: "0.8"}, ....]
I've tried sorting it using the following algorithm:
var PhysicalCapacities = [];
for (var i = 0; i < values.length; i++){
if (values[i].type == type){
PhysicalCapacities.push(values[i]);
}
}
var sortedArray = _(PhysicalCapacities).chain().sortBy(function(PhysicalCapacity) {
return PhysicalCapacity.name;}).value();
This algorithm first filters out the ones with the right type. Then, it sorts it by the name value. The only problem is, it sorts them in a semi-right order:
For gas:
G1,6, G10, G100, G2,6
While it should be:
G1,6, G2,6, G10, G100
And for electricity:
1x10A, 1X4A, 1X6A
While it should be:
1X4A, 1X6A, 1X10A
Does anyone know how to tweak it so it will give it back in the order I want it to be?
you should do this:
to replace letter of the key "name"
replace the comma for dot
convert the number in float
sort the array
Like this:
var PhysicalCapacities = [];
for (var i = 0; i < values.length; i++){
if (values[i].type == type){
PhysicalCapacities.push(values[i]);
}
}
var sortedArray = _(PhysicalCapacities).chain().sortBy(function(PhysicalCapacity) {
return parseFloat(PhysicalCapacity.name.replace(/[^0-9$.,]/g, '').replace(',','.'));
}).value();

JS HighCharts.js Pie data undefined error in code

I am having a bit of an issue with the following code:
//The Code:
var data = [];
for (var i = 0; i < mydata.length; i++) { //looping through data received
var obj = mydata[i]; //current obj in loop
var newObj = { //creating new obj with same structure as the 'data' that works
name: obj.name,
y: obj.subhere.subhere1,
id: i
};
data.push(newObj); //pushing each object into the data array
}
//THE DATA:
var data = [{ name: 'Name 1', y: 20, id: 0 },{ name: 'Name 2', y: 10, id: 1 },{ name: 'Name 3', y: 10, id: 2 }];
//THE CHART CODE:
chart = new Highcharts.Chart({
series:[
{
"data": data,
type: 'pie',
animation: false,
point:{
events:{
click: function (event) {
//alert(this.id);
}
}
}
}
],
"chart":{
"renderTo":"container"
},
});
//The above with create a pie chart with 3 names
//The Data
var mydata =[{
"001":{
"name":"Name 1",
"subhere":{
"subhere1":2
}
},
"002":{
"name":"Name 2",
"subhere":{
"subhere1":20
}
},
}];
The console is giving me the following error:
TypeError: obj.subhere is undefined y: obj.subhere.subhere1,
I can see that the subhere.subhere1 names actually exists so in theory it should not be giving me an error, right?.
How can I fix this issue ... any ideas?
myData doesn't look correctly formatted. It has an extra comma after the bracket before last:
},
}];
You can loop through your object properties:
var data = [];
for (var i = 0; i < mydata.length; i++) { //looping through data received
var obj = mydata[i]; //current obj in loop
for(var key in obj){
var newObj = { //creating new obj with same structure as the 'data' that works
name: obj[key].name,
y: obj[key].subhere.subhere1,
id: i
};
data.push(newObj); //pushing each object
}
}
To work with your existing code, you could change the definition of mydata to this:
var mydata =[
{
"name":"Name 1",
"subhere":{
"subhere1":2
}
},
{
"name":"Name 2",
"subhere":{
"subhere1":20
}
}
];

Updating an array of objects in javascript

I have an array of javascript objects like the following:
var food = [
{id: 1, name: 'Apples', owned: true },
{id: 2, name: 'Oranges', owned: false },
{id: 3, name: 'Bananas', owned: true }
];
Then I receive another array with the following data:
var newFood = [
{id: 1, name: 'Peas'},
{id: 2, name: 'Oranges'},
{id: 3, name: 'Bananas'},
{id: 4, name: 'Grapefruits'}
];
How can I update the previous food array with the new information in newFeed, without overwriting the original owned property, while adding an owned: false to any new object?
Keep in mind this is plain javascript, not jQuery.
You'd probably want to index food by id so make food an object instead of an array:
var food = {
1: {name: "Apples", owned: true},
//...
}
then iterate over newFood and update the fields appropriately.
I think you can use underscore.js for fix the problem.
var arrayObj = [
{Name:'John',LastName:'Smith'},
{Name:'Peter',LastName:'Jordan'},
{Name:'Mike',LastName:'Tyson'}
];
var element = _.findWhere(arrayObj, { Name: 'Mike' });
element.Name="SuperMike";
console.log(arrayObj);
This works:
var temp = {};
for (var i = 0, l = food.length; i < l; i += 1) {
temp[food[i].name] = true;
}
for (var i = 0, l = newFood.length; i < l; i += 1) {
if ( !temp[newFood[i].name] ) {
food.push( { id: food.length + 1, name: newFood[i].name, owned: false });
}
}
The first for statement will populate the temp object with the fruit names from the food array, so that we know which fruits exist in it. In this case, temp will be this:
{ "Apples": true, "Oranges": true, "Bananas": true }
Then, the second for statement checks for each fruit in newFood if that fruit exists in temp, and if it doesn't, if pushes a new array item into the food array.
some thing like this? JSFiddle Example
JavaScript
function updateFood( newFood, oldFood ) {
var foodLength = oldFood.length - 1;
for (var i = 0; i < newFood.length; i++) {
if (i > foodLength) { //add more if needed
newFood[i].owned = false;
oldFood.push(newFood[i]);
} else if (!food[i].owned) { //replace if needed
newFood[i].owned = false;
oldFood[i] = newFood[i];
}
}
}

Categories

Resources