I have an array of Google Analytics data:
[
["2016-01-01","google","335"],
["2016-01-01","bing","135"],
["2016-01-01","yahoo","35"],
["2016-01-02","google","145"],
["2016-01-02","bing","115"],
...........
]
It can be large. What is the simplest and fastest way to get the following two arrays?
An array of the unique date values:
["2016-01-01","2016-01-02","2016-01-03","2016-01-04"] - this is uniqe sorting date
An array of objects that group the data by source:
[{
source: 'google',
data: [335, 145,.....] // lenght array=count unique date
}, {
source: 'bing',
data: [135, 115,.....]
}, ...
]
A solution with an intermediate object.
var array = [["2016-01-01", "google", "335"], ["2016-01-01", "bing", "135"], ["2016-01-01", "yahoo", "35"], ["2016-01-02", "google", "145"], ["2016-01-02", "bing", "115"]],
object = {},
keys = {},
uniqueData,
grouped = [];
array.forEach(function (a) {
keys[a[0]] = true;
object[a[1]] = object[a[1]] || {};
object[a[1]][a[0]] = a[2];
});
uniqueData = Object.keys(keys).sort();
grouped = Object.keys(object).map(function (k) {
return {
source: k,
data: uniqueData.map(function (a) {
return object[k][a] || 0;
})
};
});
document.write('<pre>' + JSON.stringify(uniqueData, 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(grouped, 0, 4) + '</pre>');
a naive implementation:
var arr = [
["2016-01-01","google","335"],
["2016-01-01","bing","135"],
["2016-01-01","yahoo","35"],
["2016-01-02","google","145"],
["2016-01-02","bing","115"],
];
var new_arr = [];
var sources = {};
for(var i = 0; i < arr.length; i++){
if ( sources[arr[i][1]] ){
sources[arr[i][1]].data.push(arr[i][2])
} else {
sources[arr[i][1]] = {
source: arr[i][1],
data: [ arr[i][2] ],
}
}
}
for (var key in sources) {
if (sources.hasOwnProperty(key)) {
new_arr.push(sources[key]);
}
}
console.log(new_arr);
The code above gets you the second array.
The first array should be much simpler to obtain and would be a good exercise for you :)
If you can use lodash, then it is pretty simple:
var data = [
["2016-01-01","google","335"],
["2016-01-01","bing","135"],
["2016-01-01","yahoo","35"],
["2016-01-02","google","145"],
["2016-01-02","bing","115"]
];
var first = _.sortedUniq(data.map(x => x[0]).sort());
var second = _.entries(_.groupBy(data, x => x[1])).map(
e => ({ source: e[0], data: e[1].map(d => d[2]) }) );
logs.innerText = JSON.stringify(first, null, 2) + "\n" + JSON.stringify(second, null, 2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.1/lodash.min.js"></script>
<pre id="logs"></pre>
Related
var arr = [
{level:0,name:"greg"},
{level:0,name:"Math"},
{level:0,name:"greg"}
];
I have tried the following:
function removeDuplicates:(dataObject){
self.dataObjectArr = Object.keys(dataObject).map(function(key){
return dataObject[key];
});
for(var i= 0; i < self.dataObjectArr.length; i++ ){
self.dataObjectArr[i]['name'] = self.dataObjectArr[i];
self.uniqArr = new Array();
for(var key in self.dataObjectArr){
self.uniqArr.push(self.dataObjectArr[key]);
}
}
self.uniqObject = DataMixin.toObject(self.uniqArr);
return self.uniqObject;
}
But I get error saying: Uncaught TypeError: Converting circular structure to JSON.
You should push the name to an array or a set and check the same in the following..
var arr = [{
level: 0,
name: "greg"
}, {
level: 0,
name: "Math"
}, {
level: 0,
name: "greg"
}]
function removeDuplicates(arr) {
var temp = []
return arr.filter(function(el) {
if (temp.indexOf(el.name) < 0) {
temp.push(el.name)
return true
}
})
}
console.log(removeDuplicates(arr))
Here's a generic "uniquify" function:
function uniqBy(a, key) {
var seen = new Set();
return a.filter(item => {
var k = key(item);
return !seen.has(k) && seen.add(k)
});
}
///
var arr = [
{level:0,name:"greg"},
{level:0,name:"greg"},
{level:0,name:"joe"},
{level:0,name:Math},
{level:0,name:"greg"},
{level:0,name:"greg"},
{level:0,name:Math},
{level:0,name:"greg"}
];
uniq = uniqBy(arr, x => x.name);
console.log(uniq);
See here for the in-depth discussion.
I believe you have a syntax error " removeDuplicates:(dataObject){ ..."
should be without the ":" >> " removeDuplicates(dataObject){ ... "
"
You can try this :
function removeDuplicates(arr){
var match={}, newArr=[];
for(var i in arr){ if(!match[arr[i].name]){ match[arr[i].name]=1; var newArr=i; } }
return newArr;
}
arr = removeDuplicates(arr);
You can use $.unique(), $.map(), $.grep()
var arr = [
{level:0,name:"greg"},
{level:0,name:"Math"},
{level:0,name:"greg"}
];
var res = $.map($.unique($.map(arr, el => el.name)), name =>
$.grep(arr, el => el.name === name)[0]);
jsfiddle https://jsfiddle.net/4tex8xhy/3
Or you can use such libraries as underscore or lodash (https://lodash.com/docs/4.16.2). Lodash example:
var arr = [
{level:0,name:"greg"},
{level:0,name:"Math"},
{level:0,name:"greg"}
];
var result = _.map(_.keyBy(arr,'name'));
//result will contain
//[
// {
// "level": 0,
// "name": "greg"
// },
// {
// "level": 0,
// "name": "Math"
// }
//]
Ofc. one thing to always consider in these tasks, what do you want exactly are you going to do: modify an existing array, or get a new one back. This example returns you a new array.
I have an array of objects like this:
[
{
x_issue: 'Cost, Taste, Other',
y_a_issue: 'Spillover'
},
{
x_issue: 'Cost, Taste',
y_a_issue: 'Spillover'
},
{
x_issue: 'Taste, Other',
y_a_issue: 'Packaging'
}
]
I need the result array to be like this:
{
"x": {
"response": {
"Cost": 2,
"Taste": 3,
"Other": 2
}
},
"y_a": {
"response": {
"Spillover": 2,
"Packaging": 1
}
}
}
Also, I have an array of parameters
['x', 'y_a', 'z']
Here there could many more parameters like x, y. the last string issue remains constant in every parameter. And it is grouped by the occurrence.
Cost has been entered twice, Taste entered thrice.
How can I do that in javascript? I am using lodash.
This is what I was trying:
Here data is the array of object which is a mongodb object. And parameters is the array of parameters that I mentioned above.
let obj = {};
_.forEach(data, (v, k) => {
obj.parameters = [];
_.forIn(v.toJSON(), (val, key) => {
// let count = 0;
var bucket = _.find(parameters, k => _.startsWith(key, k));
if (bucket) {
if (key === `${bucket}_issue`) {
obj[bucket] = obj[bucket] || {};
obj[bucket]["response"] = obj[bucket]["response"] || {};
obj[bucket]["response"]["all"] = obj[bucket]["response"]["all"] || [];
obj[bucket]["response"]["all"].push(_.words(val));
}
}
});
});
In pure javascript you could do it like this using forEach() loop
var data = [{
x_issue: 'Cost, Taste, Other',
y_a_issue: 'Spillover'
}, {
x_issue: 'Cost, Taste',
y_a_issue: 'Spillover'
}, {
x_issue: 'Taste, Other',
y_a_issue: 'Packaging'
}]
var o = {}
data.forEach(function(e) {
Object.keys(e).forEach(function(k) {
var p = e[k].split(', ');
var re = /\_(?:.(?!\_))+$/
var key = k.split(re)[0];
if (!o[key]) o[key] = {response: {}};
p.forEach(function(a) {
o[key].response[a] = (o[key].response[a] || 0) + 1;
})
})
})
document.body.innerHTML = '<pre>' + JSON.stringify(o, 0, 4) + '</pre>';
You can use _.mergeWith() with a customizer function to achieve the merge you want, and then loop the result with _.transform() to remove the `_issue from each key:
var arr = [{
x_issue: 'Cost, Taste, Other',
y_a_issue: 'Spillover'
}, {
x_issue: 'Cost, Taste',
y_a_issue: 'Spillover'
}, {
x_issue: 'Taste, Other',
y_a_issue: 'Packaging'
}];
/** Create the mergeWithResponse method **/
var mergeWithResponse = _.partialRight(_.mergeWith, function(ov, sv) {
var oValue = ov ? ov : { // if the original value is undefined initialize it with a response property
response: {}
};
return sv.split(',').reduce(function(final, word) { // split the words of the source value and iterate them
var w = word.trim(); // remove space before and after the words
final.response[w] = (final.response[w] || 0) + 1; // add the word to the response and / or increment the counter
return final; // return the final value with the response object
}, oValue);
});
var result = _(mergeWithResponse.apply(_, [{}].concat(arr))) // add an empty object to the beginning of the array, and apply the new array as paramaters for mergeWithResponse
.transform(function(result, value, key) { // remove the "_issue" string an from each key, and create an object with the new keys
var k = key.replace('_issue', '');
result[k] = value;
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.2/lodash.min.js"></script>
I am trying to find the index of an object within an array. I know there is a way to do this with underscore.js but I am trying to find an efficient way without underscore.js. Here is what I have :
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
function FindIndex(key) {
var rx = /\{.*?\}/; // regex: finds string that starts with { and ends with }
var arr = []; // creates new array
var str = JSON.stringify(arrayOfObjs); // turns array of objects into a string
for (i = 0; i < arrayOfObjs.length; i++) { // loops through array of objects
arr.push(str.match(rx)[0]); // pushes matched string into new array
str = str.replace(rx, ''); // removes matched string from str
}
var Index = arr.indexOf(JSON.stringify(key)); // stringfy key and finds index of key in the new array
alert(Index);
}
FindIndex({"ob2": "test1"});
JSFIDDLE
This works but I am afraid it isn't very efficient. Any alternatives?
Here's one way to do it, somewhat reliably and a little more efficiently, using some() and stopping as soon as the objects don't match etc.
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
function FindIndex(key) {
var index = -1;
arrayOfObjs.some(function(item, i) {
var result = Object.keys(key).some(function(oKey) {
return (oKey in item && item[oKey] === key[oKey]);
});
if (result) index = i;
return result;
});
return index;
}
var index = FindIndex({"ob2": "test1"});
document.body.innerHTML = "'{\"ob2\": \"test1\"}' is at index : " + index;
A hash table with an example of access.
var arrayOfObjs = [{ "obj1": "test1" }, { "obj2": "test1" }, { "obj1": "test3" }],
hash = {};
arrayOfObjs.forEach(function (a, i) {
Object.keys(a).forEach(function (k) {
hash[k] = hash[k] || {};
hash[k][a[k]] = i;
});
});
document.write('<pre>' + JSON.stringify(hash['obj2']['test1'], 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(hash, 0, 4) + '</pre>');
One way of doing this would be to use every to see if each key in the "filter" has a matching, correct value in an object. every ensures that the loop stops as soon as it finds a mismatched or missing value.
function log(msg) {
document.querySelector('pre').innerHTML += msg + '\n';
}
var arr = [
{
a: 1
},
{
b: 2
},
{
c: 3,
d: 4
},
{
a: 1 // Will never reach this since it finds the first occurrence
}
];
function getIndex(filter) {
var keys = Object.keys(filter);
for (var i = 0, len = arr.length; i < len; i++) {
var obj = arr[i];
var match = keys.every(function(key) {
return filter[key] === obj[key];
});
if (match) {
return i;
}
}
return -1;
}
log(getIndex({ a: 1 }));
log(getIndex({ b: 2 }));
log(getIndex({ c: 3 }));
log(getIndex({ c: 3, d: 4 }));
log(getIndex({ e: 5 })); // Doesn't exist, won't find it
<pre></pre>
For an alternative to your customly built approach, lodash's findIndex method does exactly this for you:
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
_.findIndex(arrayOfObjs, {"ob2": "test1"}); // => 1
Since testing equality on two different objects will always return false you could first test keys and then values ,
using reduce :
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1" , k2:2
}, {
"ob1": "test3"
}];
function getI( obj, arr){
const checkK= Object.keys(obj);
return arr.reduce((ac,x,i) => {
if ( checkK.every(z => x[z] && obj[z] === x[z]) )
ac.push(i);
return ac;
},[])
}
document.write( 'result is :'+ getI({ob2:'test1', k2:2},arrayOfObjs))
findIndex won't work in old browsers, but was designed for this specific purpose.
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
function FindIndex(key) {
return arrayOfObjs.findIndex(
obj => Object.keys(key).every(name => key[name] === obj[name])
);
}
alert(FindIndex({"ob2": "test1"})); // 1
I have an JSON object (hope so?):
[{"label":"label1","value":"value1"}
{"label":"label2","value":"value2"}
{"label":"label3","value":"value3"}]
I want to convert/extract that in 2 arrays like:
var labels = [label1,label2,label3]
var values = [value1,value2,value3]
I have no idea...
Assuming you would like two arrays and there is only ever two properties called label and value in each object, then this would be fine: -
var json_string = '[{"label":"label1", "value":"value1"}, {"label":"label2", "value":"value2"}, {"label":"label3", "value":"value3"}]';
var array = JSON.parse(json_string);
var labels = [];
var values = [];
for (var i = 0; i < array.length; i++) {
labels.push(array[i].label);
values.push(array[i].value);
}
Output: -
console.log(labels); // ["label1", "label2", "label3"]
console.log(values); // ["value1", "value2", "value3"]
You could use the map function to create your results:
var dataObj = [{"label":"label1","value":"value1"},
{"label":"label2","value":"value2"},
{"label":"label3","value":"value3"}];
var labels = dataObj.map(function(obj) {return obj.label;});
var values = dataObj.map(function(obj) {return obj.value;});
I think this solution is more elegant then manually iterating the array.
One other implementation could be;
var dataObj = [{"label":"label1","value":"value1"},
{"label":"label2","value":"value2"},
{"label":"label3","value":"value3"}],
dataStr = JSON.stringify(dataObj),
r = /label":"(\w+).+?value":"(\w+)/g,
m = [],
values = [],
labels = [];
while ((m = r.exec(dataStr)) !== null){
labels.push(m[1]);
values.push(m[2]);
}
console.log(labels, values); // [ 'label1', 'label2', 'label3' ] [ 'value1', 'value2', 'value3' ]
A proposal with one object as result with dynamic keys.
var array = [{ "label": "label1", "value": "value1" }, { "label": "label2", "value": "value2" }, { "label": "label3", "value": "value3" }],
result = {};
array.forEach(function (a) {
['label', 'value'].forEach(function (k) {
result[k] = result[k] || [];
result[k].push(a[k]);
});
});
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
I have two array of objects like:
var A = [{title:"name1",count:5},{title:"name2",count:1},{title:"name3",count:3}];
and:
var B = [{title:"name2",count:7},{title:"name3",count:2},{title:"name4",count:3},{title:"name5",count:8}];
I need to merge this two array in one array and sum the "count" values in returned array when the "title" properties is same:
the last answer must be:
[{title:"name1",count:5},{title:"name2",count:8},{title:"name3",count:5},{title:"name4",count:3},{title:"name5",count:8}]
how can i do this???
You can use Array#forEach and Array#some to achieve a result
var M = A.concat(B)
var C = [];
M.forEach(function(a) {
var index;
if (C.some(function(c, i) { index = i; return a.title == c.title; })) {
C[index].count += a.count;
} else {
C.push(a);
}
});
console.log(C); // as you expect
Solution with Array.concat and Array.map functions:
var merged = A.concat(B), titles = [], result = [];
merged.map(function(obj){
if (titles.indexOf(obj.title) === -1) {
titles.push(obj.title);
result.push(obj);
} else {
result[titles.indexOf(obj.title)]['count'] += obj['count'];
}
});
console.log(result); // will output the expected array of objects
It can be done like this https://jsfiddle.net/menm9xeo/
var noMatch;
var A = [{title:"name1",count:5},{title:"name2",count:1},{title:"name3",count:3}];
var B = [{title:"name2",count:7},{title:"name3",count:2},{title:"name4",count:3},{title:"name5",count:8}];
//for each A, loop through B's. If a match is found combine the Counts in A.
for(var i=0;i<A.length;i++){
for(var j=0;j<B.length;j++){
if(A[i].title == B[j].title){
A[i].count += B[j].count;
}
}
}
//find all B's that were not combined with A in the previous step, and push them into A.
for(var i=0;i<B.length;i++){
noMatch = true;
for(var j=0;j<A.length;j++){
if(B[i].title == A[j].title){
B[i].count += A[j].count;
noMatch = false;
}
}
if(noMatch){A.push(B[i]);}
}
Heres a simple 3 line answer (minus the A/B vars); utilizes the fact that objects must have unique keys
var A = [{title:"name1",count:5},{title:"name2",count:1},{title:"name3",count:3}];
var B = [{title:"name2",count:7},{title:"name3",count:2},{title:"name4",count:3},{title:"name5",count:8}];
var o = {};
A.concat(B).forEach(function(a){o[a.title] = o.hasOwnProperty(a.title)? o[a.title]+a.count: a.count});
var AB = Object.keys(o).map(function(j){ return {title:j,count:o[j]} });
This proposal is merging and counting with a temporary object and Array#forEach()
The forEach() method executes a provided function once per array element.
var arrayA = [{ title: "name1", count: 5 }, { title: "name2", count: 1 }, { title: "name3", count: 3 }],
arrayB = [{ title: "name2", count: 7 }, { title: "name3", count: 2 }, { title: "name4", count: 3 }, { title: "name5", count: 8 }],
result = function (array) {
var o = {}, r = [];
array.forEach(function (a) {
if (!(a.title in o)) {
o[a.title] = { title: a.title, count: 0 };
r.push(o[a.title]);
}
o[a.title].count += a.count;
});
return r;
}(arrayA.concat(arrayB));
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
Using lodash ._concat function :
var result = _.concat(A, B);
Fiddle