I recently posted this question about summing arrays in JavaScript using d3.nest()
I got a good solution (two in fact), but it turns out both have an issue when adapted to append additional information:
data = [
{
continent: 'africa',
country: 'gabon',
values: [1, 2]
}, {
continent: 'africa',
country: 'chad',
values: [3, 4]
}, {
continent: 'asia',
country: 'china',
values: [1, 2]
}
];
sum_by = 'continent';
rollupAgg = function(data, sum_by) {
return d3.nest().key(function(d) {
return d[sum_by];
}).rollup(function(group) {
return group.reduce(function(prev, cur, index, arr) {
return {
values: prev.values.map(function(d, i) {
return d + cur.values[i];
}),
sum_by: sum_by // additional information
};
});
}).entries(data);
};
reduce() doesn't run if there is only one row in that group, so this returns the following:
[
{
"key": "africa",
"values": {
"values": [4, 6],
"sum_by": "continent"
}
}, {
"key": "asia",
"values": {
"continent": "asia",
"country": "china", // country information shouldn't have been retained
"values": [1, 2]
} // sum_by information should have been added
}
];
Can you see a way to modify this function so that it returns the desired result?
Hasn't occurred to me till now that a single element array will not execute the function; but it makes sense, because you're using with a single param:
[].reduce(function(prev, curr, i, arr) {});// <-- function is the only param
When used with a single param, the behavior is that the first time the function is executed, i equals 1 (not 0), and prev and curr are the first and second elements of the array. Since you only have one element, there's no way to call it in this form.
If you use reduce with a 2nd param:
[].reduce(function(prev, curr, i, arr) {}, { foo:'bar' });// <-- 2nd param {foo:bar}
it does actually call the function, with the first call passing i equal to 0 and prev equaling { foo:'bar' }.
So I guess you have 2 options:
Either modify it to pass a 2nd param, which in your case would need to be { values:[0,0] } (and that hardcodes the fact that values is always 2 elements, which will cause an issue if it's longer).
Check if group.length == 1 and if so, return group, instead of calling reduce.
Related
I have a function which get a json as parameter, build another json with some values from given json and return builded json.
function getMyJSON(json) {
var result = {
lastUpdate: "",
legends: null
};
result.legends = (new Array(json.legends.length)).fill({
name: null,
rgb: null,
values: null
});
for (let j = 0; j < json.legends.length; j++) {
result.legends[j].name = json.legends[j].name;
result.legends[j].rgb = json.legends[j].rgb;
result.legends[j].values = (new Array(10)).fill(0);
console.log(result.legends[0].name); //PRINT ONLY FIRST ELEMENT
}
console.log(result.legends);
return result;
}
The problem appear after for loop is done. All result.legends have the same value from the last json.legends
Here is how output look:
The legends.name of first element(result.legends[0].name) is changed after every loop.
At the end, all legends.name from result are equal with the last legends.name from json. Why?
I found on google that it is something about variable scope, but I can't figure it out how to do this.
You need independent objects inside of the array. Array#fill takes the same object reference and this leads to the same result in each object.
Instead of this, you could create a new array with Array.from and map new objects with the second parameter for a callback.
result.legends = Array.from(
{ length: json.legends.length },
_ => ({ name: null, rgb: null, values: null })
);
#NinaScholz has described the problem and solved it, however as I mentioned in the comments on the question you can improve and simplify the logic by using map():
var obj = {
legends: [{
name: 'foo',
rgb: 'C00'
},{
name: 'bar',
rgb: 'FFF'
},{
name: 'fizz',
rgb: 'CCFFCC'
},{
name: 'buzz',
rgb: '000000'
}]
}
console.log(getMyJSON(obj));
function getMyJSON(o) {
return {
lastUpdate: "",
legends: o.legends.map(function(item) {
return {
name: item.name,
rgb: item.rgb,
values: (new Array(10)).fill(0)
}
})
};
}
I'm learning to manipulate JSON data and I am stuck trying to figure out how to cajole the following JSON into what I want as shown below:
Any pointers to function/terms/concepts that I should learn for this sort of problem would be greatly appreciated! Thanks
JSON object
{
car: 1,
van: 5,
cat: 99999999999999999999999
}
Desired outcome:
items: [
{ "type": "car", "value": "1"},
{ "type": "van", "value": "5"},
{ "type": "cat", "value": "99999999999999999999999"}
]
You can use a combination of Object.entries and Array.prototype.map:
const obj = { car: 1, van: 5, cat: 99999999999999999999999 };
let list = Object.entries(obj) // [["car",1],["van",5],["cat",99999999999999999999999]]
.map(x => ({ type: x[0], value: x[1] }));
console.log(list);
Or, with some destructuring:
const obj = { car: 1, van: 5, cat: 99999999999999999999999 };
let list = Object.entries(obj)
.map(([type, value]) => ({ type, value }));
console.log(list);
The callback to map:
([type, value]) => ({ type, value })
Expects an array as parameter: [type, value]. The first value in that array is assigned to type, the second one to value.
Then we use a shorthand form to set these values in our returned object:
=> ({ type, value })
I'm a beginner. I tried to solve the problem and this is the best I can come up with, tested in Node.js 10.
const obj = {"car": 1, "van": 5, "cat": 999999}
const items = []
for (let key in obj) {
items.push({"type": key, "value": obj[key]})
}
console.log(items)
One thing I am slightly confused about is the difference between for..in vs for..of, I'm currently looking into it.
Object.keys will return:
['car', 'van', 'cat'];
On this array you can use Array's map function which creates a new array with the results of calling a provided function on every element in the calling array.
var a = {
car: 1,
van: 5,
cat: 99999999999999999999999
}
m = Object.keys(a).map((v)=>{
return {
type: v,
value: a[v]
}
})
console.log(m);
#GustavMahler hope you understand. To learn more about array functions you should look map, reduce and filter.
This one uses object.keys
let js = {car:1, van:5, cat:9999}
Object.keys(js).map( x => ({type: x, value: js[x] }) )
[ { type: 'car', value: 1 },
{ type: 'van', value: 5 },
{ type: 'cat', value: 9999 } ]
I have nested arrays within an object, and I need to extract these values, and sort them into alphabetical order which will then be displayed in a table.
I am using the localeCompare method but it is returning:
Cannot read property 'localeCompare' of null
To be honest, I am not sure I am approaching this correctly. Using sort you can compare a to b to sort them into alphabetical order. I am getting confused how to compare the values from the arrays within a and b. I am using the first sort on tableData to access the array, and the using a second sort to compare the values that I pushed to array clientRefArr
if(params.sorting().clientRef) {
var clientRefArr = [];
tableData.sort(function(a, b){
a.renewalUIs.forEach(function(data, i){
clientRefArr.push(data.patentUI.clientRef);
})
return clientRefArr.sort(function(a, b){
console.log(a.localeCompare(b))
// return a.localeCompare(b)
})
})
orderedData = tableData;
}
return orderedData;
Question
Why is the error Cannot read property 'localeCompare' of null being returned?Am I approaching this issue completely wrong?
//JSON
[
0: {
transRef: "IX1000013"
renewalProgress: 30
renewalUIs: [
0: {
patentUI: {
business: null
clientRef: P0101011 // this is the required value
}
renewalAttemptsMade: 1
renewalDueDate: 1514764740000
}
]
},
1: {
transRef: "IX100333"
renewalProgress: 55
renewalUIs: [
0: {
patentUI: {
business: null
clientRef: P0101011 // this is the required value
}
renewalAttemptsMade: 1
renewalDueDate: 1514764740000
},
1: {
patentUI: {
business: null
clientRef: P5551011 // this is the required value
}
renewalAttemptsMade: 3
renewalDueDate: 174834740000
}
]
}
]
You could take a default value for both parts
(a || '').localeCompare(b || '')
for sorting the null values or even all falsy values to top.
An attempt with the given data (I changed some value, to get some sorting).
It now sorts the inner arrays renewalUIs first and then it takes the first element for sorting the outer array.
var array = [{ transRef: "IX1000013", renewalProgress: 30, renewalUIs: [{ patentUI: { business: null, clientRef: "P9101011" }, renewalAttemptsMade: 1, renewalDueDate: 1514764740000 }] }, { transRef: "IX100333", renewalProgress: 55, renewalUIs: [{ patentUI: { business: null, clientRef: "P7101011" }, renewalAttemptsMade: 1, renewalDueDate: 1514764740000 }, { patentUI: { business: null, clientRef: "P5551011" }, renewalAttemptsMade: 3, renewalDueDate: 174834740000 }] }];
array.forEach(function (o) {
o.renewalUIs.sort(function (a, b) {
return a.patentUI.clientRef.localeCompare(b.patentUI.clientRef);
});
});
array.sort(function (a, b) {
return a.renewalUIs[0].patentUI.clientRef.localeCompare(b.renewalUIs[0].patentUI.clientRef);
});
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
IF my interpretation is correct, it'd be way easier to first put everything in an single array and then sort it as such just use loops for the first task and a normal sort for the second, nested sorts are really just a bad idea.
// let's assume your data is in unorderedData
var clientUIs = [];
for (var i = 0; i < unorderedData.length; i++){
for (var j = 0 ; j < unorderedData[i]["renewalUIs"].length; j++){
// add each UI to your list individually.
clientUIs.push(unorderedData[i]["renewalUIs"][j]["patentUI"]);
}
}
clientUIs.sort() //just sort everything
tableData = clientUIs;
If "renewalUIs" or "patentUIs" arent constant you can iterate over the keys of the dictionary.
I have to sort the arrays of the "Key" in ascending order and here's what I am doing .
Surprsingly the first array is getting sorted in descending order and the next two in ascending order . Whats the possible reason for this?
var MainObject4 = [{ "mainarray": [{ "Key": [9,768,78] },
{ "Key": [9,4,1] },{ "Key": [49,89,54] }]
}];
var first = MainObject4[0];
Object.keys(MainObject4[0]).forEach(function (k) {
first[k].forEach(function (j) {
Object.keys(j).forEach(function (g) {
j[g].sort();
},this);
},this);
},this);
alert(JSON.stringify(MainObject4, 0, 4));
Expected output:
[9,78,768]
[1,4,9]
[49,54,89]
Output I am getting now:
[768,78,9]
[1,4,9]
[49,54,89]
See the doc of Array.sort()
The default sort order is according to string Unicode code points.
If you want to compare numbers, you need to provide the comparison function, as said in the doc :
To compare numbers instead of strings, the compare function can simply subtract b from a.
You need to add a function to compare sort parameters:
var MainObject4 = [{
"mainarray": [{
"Key": [9, 768, 78]
}, {
"Key": [9, 4, 1]
}, {
"Key": [49, 89, 54]
}]
}];
var first = MainObject4[0];
Object.keys(MainObject4[0]).forEach(function(k) {
first[k].forEach(function(j) {
Object.keys(j).forEach(function(g) {
j[g].sort(function(a, b) {
return a - b;
});
}, this);
}, this);
}, this);
alert(JSON.stringify(MainObject4, 0, 4));
var MainObject4 = [{ "mainarray": [
{ "Key": [9,768,78] },
{ "Key": [9,4,1] },
{ "Key": [49,89,54] }
]
}];
MainObject4[0].mainarray.forEach(function (j) {
Object.keys(j).forEach(function (g) {
j[g].sort(function (a, b) {
return a - b;
});
},this);
},this);
alert(JSON.stringify(MainObject4, 0, 4));
It's happening because of JavaScript's sort method takes by default unicode to compare the elements. For sorting numbers, you have to explicitly write a call back function to compare elements. For ex
var input = [1,20,2,12];
console.log(input.sort());// prints 1,12,2,20
console.log(input.sort(function(a,b){
return a-b;
}));// prints 1,2,12,20
So, You just need to add the compare function to your sort. That's all
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I have an array of data containing objects like below
[
{
"name":"Q1'2016",
"y":0
},
{
"name":"Q2'2016",
"y":0
},
{
"name":"Q3'2016",
"y":0
},
{
"name":"Q4'2015",
"y":0
}
]
I want to sort them based on quarterly, so Q4'2015 should come first, then Q1'2016 and so on.
How can this be acheived?
You can use the sort method and give it a callback to sort your object based on a predicate; in your case, you want to inspect the objects' name property containing your quarter-year information. Since you'll likely have data with different quarters and years, you'll want to map the quarters to month values so you can convert them to year/month dates and compare them that way.
var data = [{
"name": "Q1'2016",
"y": 0
}, {
"name": "Q2'2016",
"y": 0
}, {
"name": "Q3'2016",
"y": 0
}, {
"name": "Q4'2015",
"y": 0
}];
var quarterToMonthMap = {
"Q1": 0,
"Q2": 3,
"Q3": 6,
"Q4": 9
}
function sortByQuarterYear(lhs, rhs) {
var lhsQuarterYear = lhs.name.split("'");
var rhsQuarterYear = rhs.name.split("'");
var lhsDate = new Date(lhsQuarterYear[1], quarterToMonthMap[lhsQuarterYear[0]]);
var rhsDate = new Date(rhsQuarterYear[1], quarterToMonthMap[rhsQuarterYear[0]]);
return lhsDate.getTime() - rhsDate.getTime();
}
document.write(JSON.stringify(data.sort(sortByQuarterYear)));
To sort by year and quarter you can use the following ES6 snippet (you can do the exact same thing using ES5, but I like writing ES6)
input.map((obj, index) => obj.name.split("'").reverse().concat(index)).sort().map(arr => input[arr[2]]);
Let's break this down a bit
input.map((obj, index) => obj.name.split("'").reverse().concat(index))
The map() method creates a new array with the results of calling a provided function on every element in this array.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Our "provided function" splits the "name" property of each object by the ' character, reverses the resulting array, and adds the current index into the resulting array.
This results in an array that looks like this:
[
["2016", "Q1", 0],
["2016", "Q2", 1],
["2016", "Q3", 2],
["2015", "Q4", 3]
]
Then we call .sort,
The sort() method sorts the elements of an array in place and returns the array. The sort is not necessarily stable. The default sort order is according to string Unicode code points.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
We then end up with an array like this:
[
["2015", "Q4", 3],
["2016", "Q1", 0],
["2016", "Q2", 1],
["2016", "Q3", 2]
]
So we now have an appendix of sorts, notice that the indexes that we threw in there aren't in order. We can use this lack of order to create order.
All we have to do is call map again, this time returning the original object related to the index that we have stored in our temporary arrays.
.map(arr => input[arr[2]]);
When we put it all together and call it, we end up with an array like this:
[
{
name: "Q4'2015",
y: 0
}, {
name: "Q1'2016",
y: 0
}, {
name: "Q2'2016",
y: 0
}, {
name: "Q3'2016",
y: 0
}
]
Here's a demo:
let input = [
{
"name":"Q1'2016",
"y":0
},
{
"name":"Q2'2016",
"y":0
},
{
"name":"Q3'2016",
"y":0
},
{
"name":"Q4'2015",
"y":0
}
];
input = input.map((obj, index) => obj.name.split("'").reverse().concat(index)).sort().map(arr => input[arr[2]]);
console.log(input);
To do the exact same thing in ES5:
input.map(function(obj, index){
return obj.name.split("'").reverse().concat(index);
}).sort().map(function(arr){
return input[arr[2]];
});
Using .sort() with a comparator function, you can easily re-format each value from "Q1'2016" to "2016Q1", allowing a simple alphanumeric comparison because "2016Q1" > "2015Q4".
function sortQuarters(arr) {
function reformat(v) {
return v.replace(/(Q\d)'(\d{4})/, "$2$1");
}
return arr.sort(function(a, b) {
return reformat(a.name).localeCompare(reformat(b.name));
});
}
console.log(sortQuarters([
{ "name":"Q1'2016", "y":0 },
{ "name":"Q3'2016", "y":0 },
{ "name":"Q2'2016", "y":0 },
{ "name":"Q4'2015", "y":0 }
]));
(Note that the reformatting I'm talking about is only within temporary variables used while sorting, it doesn't change the values in the array. Also note that the function I've shown modifies the order of the array passed to it and returns back the same array, it doesn't create a new array.)