How to group data with similar keys? - javascript

I have this data from CSV:
Group Profession Status Count
6 Fisherman Offer Accepted 1
6 Fisherman All 1
7 Fisherman Offer Accepted 1
7 Fisherman All 1
8 Banker Onboard 2
8 Banker All 2
8 Cook Onboard 4
8 Cook All 4
8 Developer Onboard 2
8 Developer All 2
9 Banker Onboard 2
9 Banker Offer Accepted 1
9 Banker All 3
Which I need to return as a JSON array:
"Fisherman" : {
6 : {
"Offer Accepted" : 1,
"All" : 1
},
7 : {
"Offer Accepted" : 1,
"All" : 1
}
},
"Banker" : {
8 : {
"Onboard" : 2,
"All" : 2
},
9 : {
"Onboard" : 2,
"Offer Accepted" : 1,
"All" : 3
}
},
....so on
So far, what I did was I got all the unique Profession and Group.
Then I looped through all the data and compared if there's a match for Profession AND Group.
for(var d in data) {
var json = [];
for(var p in profession) {
for(var g in group) {
if(data[d]["Profession"] == profession[p] && data[d]["Group"] == group[g]) {
json.push({data[d]["Status"] : data[d]["Count"]});
// put 'json' variable in JSON array with key group?
}
}
}
}
If there is a match, I created an array wherein I pushed the Status and Count.
But I really don't know how to proceed from there.
Thank you for your help!

Suppose data is an array with objects like,
{ Group: 6, Profession: 'Fisherman', Status: 'Offer Accepted', Count: 1 }
then you could use the following
var order = ['Profession', 'Group', 'Status'],
object = {};
data.forEach(function (d) {
order.reduce(function (r, a) {
r[d[a]] = r[d[a]] || {};
return r[d[a]];
}, object).Count = d.Count;
});
How it works:
d is an object with the structure like above. oder is an array with keys in the wanted order for the result object. (I renamed json to object, because JSON is a string with a special formatting and not an object, like here necessary.)
For an assignment of count, it is necessary to know the path to the property. This is granted with iterating over the order for the keys of the actual object d.
r[d[a]] = r[d[a]] || {};
This d[a] is taken for a check if the property exits and if not to assign an empty object.
At the end of the callback, the reference to the last object r[d[a]] is returned.
At last, a new property Count is assinged with the value of d.Count
object a d[a] return value
---------------------------- ---------- -------------- ------------
{} Profession Fisherman {}
/--------------------------------------/ (same reference)
{ "Fisherman": {} } Group 6 {}
/-------------------------------/ (same reference)
{ "Fisherman": { "6": {} } } Status Offer Accepted {}
object after first loop of data
{
"Fisherman": {
"6": {
"Offer Accepted": {
"Count": 1
}
}
}
}
Roundup: reduce returns something, which is highly controllable.

Try this:
var result = new Array();
for(var d in data){
if(result[d.profession] == 'undefined'){
result[d.profession] = new Array();
}
if(result[d.profession][d.group] == 'undefined'){
result[d.profession][d.group] = new Array();
}
result[d.profession][d.group][d.status] = d.count;
}
result = JSON.stringify(result);//convert array to json
console.log(result);
I didn't test it and I supposed your JSON data are in data variable

You can do it quite easy with reduce:
const result = data.reduce((result, row) => {
ensureKeyExists(result, row.Profession);
ensureKeyExists(result[row.Profession], row.Group);
result[row.Profession][row.Group][row.Status] = row.Count;
return result;
}, {});
function ensureKeyExists(object, key, defaultVal = {}) {
if (!object[key]) {
object[key] = defaultVal;
}
}
Full example: https://jsfiddle.net/k1zk3ses/

Assuming your data is in this shape, one solution can be produced by using Array.prototype.reduce() as follows;
var str = 'Group,Profession,Status,Count\n6,Fisherman,OfferAccepted,1\n6,Fisherman,All,1\n7,Fisherman,OfferAccepted,1\n7,Fisherman,All,1\n8,Banker,On Board,2\n8,Banker,All,2\n8,Cook,On Board,4\n8,Cook,All,4\n8,Developer,On Board,2\n8,Developer,All,2\n9,Banker,On Board,2\n9,Banker,OfferAccepted,1\n9,Banker,All,3',
data = str.split("\n").map(s => s.split(",")).slice(1),
jdata = data.reduce((p,c) => (p[c[1]] ? p[c[1]][c[0]] ? p[c[1]][c[0]][c[2]] = c[3]
: p[c[1]][c[0]] = {[c[2]]:c[3]}
: p[c[1]] = {[c[0]]:{[c[2]]:c[3]}},p),{});
document.write("<pre>" + JSON.stringify(jdata,null,2) + "</pre>");

Related

Sort array of objects and return array of specific values from the object

I have an array of objects(product list), and I am implementing the sorting logic to it. In order to make it a single source of truth, I made a id-product map, which looks something like this:
const prodsMap = {
1 : {
id : 1,
name : 'abc',
price : 4
},
2 : {
id : 2,
name : 'aac',
price : 3
}
}
Now in order to sort products I am doing this:
function sortString(data, propName) {
return data.sort((obj1, obj2) => {
const val1 = obj1[propName]
const val2 = obj2[propName]
if (val1 < val2) {
return -1
}
if (val1 > val2) {
return 1
}
return 0
})
}
Calling the function like this:
const prods = sortString(Object.values(prodsMap), 'name')
Everything works fine here, the result of sorting will be an array of objects, in order to get the id's I am using map function.
Now the problem is that I've to iterate thrice(first to get object values, second to sort and third time to map id's), I was wondering if there is a better way to get only ID's when the array gets sorted.
You could order the keys of the handed over object, to get an array of id.
If you need the ìd property of the objects, you could map the values of the outer object with id property.
const prodsMap = {
1 : {
id : 1,
name : 'abc',
price : 4
},
2 : {
id : 2,
name : 'aac',
price : 3
}
}
function sortString(data, propName) {
return Object.keys(data).sort((a, b) => {
const val1 = data[a][propName];
const val2 = data[b][propName];
if (val1 < val2) {
return -1;
}
if (val1 > val2) {
return 1;
}
return 0;
});
}
const prods = sortString(prodsMap, 'name');
console.log(prods);

Parse data into JavaScript arrays

I have an array data format coming from back-end which looks like:
Array
[{"ckey::"C1","date":"0506","rows":17},
{"ckey::"C1","date":"0706","rows":7},
{"ckey::"C2","date":"0706","rows":13},
{"ckey::"C2","date":"0806","rows":11}]
So for few days C1 data is there and few days C2 data.
Only one day has C1 and C2 data both.
I want to build an array like for C1 and C2
[[17,7,0],[0,13,11]]
First nested array for C1 where third value is 0 because for 0806 date the value was not present.
Second nested array for C2 where first value is 0 because for 0506 date the value was not present.
Please help. I cannot form the array effectively.
I think it would be O(n^3) solution. But please help with the same.
UPDATE
Here was my approach, I could not post the code here but it looks something like this.
I was getting date values in separate array like and I filter for unique days.
angular.forEach(data, function(obj){
if(timeData.indexOf(obj.date) === -1)
timeData.push(obj.date);
});
Then
ckey array _distinctckeyArray also were there containing values ["C1","C2"].
angular.forEach(_distinctckeyArray,function(_ckey){
var _formattedDataArrItem = [];
angular.forEach(timeData,function(_dateTimeString) {
var _tempDataVolume = [];
angular.forEach(_data,function(_dataObj) {
if(_dataObj.date === _dateTimeString) {
if(_dataObj.ckey === _ckey) {
_tempDataVolume.push(_dataObj.rows);
}else {
_tempDataVolume.push(0);
}
}
});
});
You can make an object dates that will have date properties. Initialize the values to 0
You reduce to group the array. Use Object.values to convert the object into an array.
let arr = [{ckey:"C1","date":"0506","rows":17},{ckey:"C1","date":"0706","rows":7},{ckey:"C2","date":"0706","rows":13},{ckey:"C2","date":"0806","rows":11}];
//Make an object with property dates. assign all property to 0 as initial value.
//Expected output:
//{"0506":0,"0706":0,"0806":0}
let dates = arr.reduce((c, v) => Object.assign(c, {[v.date]: 0}), {});
//Loop thru the array using `reduce`.
//This is to group the array to object using the ckey as the key
//After grouping, use `Object.values` to convert the object into array
let result = Object.values(arr.reduce((c, {ckey,date,rows}) => {
c[ckey] = c[ckey] || { ...dates }; //Check if c[ckey] exist. If it does not, "clone" the dates object.
c[ckey][date] = rows; //Overide the initial value 0 to the rows value
return c;
}, {})).map(o => Object.values(o));
console.log(result);
I think this is what you are looking for. Let me know.
let data = [{
'ckey': 'C1',
'date': '0506',
'rows': 17
}, {
'ckey': 'C1',
'date': '0706',
'rows': 7
}, {
'ckey': 'C2',
'date': '0706',
'rows': 13
}, {
'ckey': 'C2',
'date': '0806',
'rows': 11
}]
function nested_arrays(array) {
const result = []
const obj = {
c1: [],
c2: []
}
for (let i = 0; i < array.length; i++) {
if (array[i].ckey === 'C1') {
obj.c1.push(array[i].rows)
}
if (array[i].ckey === 'C2') {
obj.c2.push(array[i].rows)
}
}
obj.c1.push(0) // set last value to 0
obj.c2.unshift(0) // set first value to 0
result.push(obj.c1, obj.c2)
return result
}
let _tempDataVolume = nested_arrays(data)
console.log(_tempDataVolume) //=> [ [ 17, 7, 0 ], [ 0, 13, 11 ] ]
let arr = [{"ckey::"C1","date":"0506","rows":17},
{"ckey::"C1","date":"0706","rows":7},
{"ckey::"C2","date":"0706","rows":13},
{"ckey::"C2","date":"0806","rows":11}]
arr.map(res =>{
let c1arr = [],
let c2arr = [],
if(res.ckey== 'C1'){
c1arr.push(res.rows)
}else{ c2arr.push(res.rows) }
})
let newArrr = []
newArr.push(c1arr);
newArr.push(c2arr);
console.log('arr is',newArr)

Loop through JS object to return matching result of value

I have an object, full of salmon. I want to loop through and check whether salmon is fresh or frozen. If frozen, put in no pile, if fresh, put in yes pile. If ounces is less than 2 and more than 6, no pile, and if it matches 2-6, yes pile.
Then, with the yes pile, I want to loop through those keys and values to find the cheapest one, and print that out.
Here is what I have so far, but I seem to be stuck on looping through the big object in order to run the checks.
How do I do this?
function findSalmon(salmonInStore){
var noPile,
yesPile;
var salmon = Object.keys(salmonInStore));
for(var key in salmonInStore){
if(salmonInStore.key.freshness !== 'fresh'){
noPile.push(salmonInStore.key);
} else {
yesPile.push(salmonInStore.key);
}
if(salmonInStore.key.ounces < 2 && salmonInStore.key.ounces > 6){
noPile.push(key);
}else {
yesPile.push(salmonInStore.key);
}
}
return yesPile;
}
var salmonInStore = {
salmon1 : {
ounces: 1,
freshness: 'frozen',
price: 16
},
salmon2 : {
ounces: 6,
freshness: 'fresh',
price: 10
},
salmon3 : {
ounces: 2,
freshness: 'frozen',
price: 20
},
salmon4 : {
ounces: 5,
freshness: 'fresh',
price: 1
},
salmon5 : {
ounces: 3,
freshness: 'frozen',
price: 12
}
}
findSalmon(salmonInStore);
First we can sort the salmon list by price.
const salmonList = Object.keys(salmonInStore)
.map(key => salmonInStore[key])
.sort((a,b) => {
if (a.price < b.price)
return -1;
if (a.price > b.price)
return 1;
return 0;
});
Then we can filter out the yes fish.
const yesPile = salmonList.filter((salmon) => {
return salmon.ounces >= 2 && salmon.ounces <= 6 && salmon.freshness === 'fresh';
});
yesPile[0] // cheapest fish
Check out the fiddle for a working example.
https://jsfiddle.net/y1jg01wy/
function findSalmon(salmonInStore){
var yesPile = [];
var noPile = [];
Object.keys(salmonInStore).forEach( (key)=> {
var salmon = salmonInStore[key]
if (salmon.freshness !== 'fresh' || salmon.ounces < 2 && salmon.ounces > 6){
noPile.push(key);
} else {
yesPile.push(key);
}
})
return yesPile;
}
I seem to be stuck on looping through the big object in order to run the checks.
Objects are also known as key/value pairs.
You can use Object.keys to get the keys of an object. From then on you need nested loops and dynamic value access using bracket notation to get the value of a k/v pair based on the key you got when you used Object.keys.
Here's an example:
var salmonInStore = {
salmon1 : {
ounces: 1,
freshness: 'frozen',
price: 16
},
salmon2 : {
ounces: 6,
freshness: 'fresh',
price: 10
}
}
Object.keys(salmonInStore).forEach(storeKey => {
var salmon = salmonInStore[storeKey]
// put per salmon logic here...
Object.keys(salmon).forEach(salmonKey => {
var value = salmon[salmonKey] // value for each key
console.log(value)
// put per salmon value logic here...
})
})
However if you plan on having arbitrary nesting levels - not just 1 level, you would need a recursive function to walk your salmons.
The above can be made more 'functional' by using reduce instead of forEach but that's another story.
The gist of the above is that you need Object.keys and someObj[key]. I've substituted your for.. loops with forEach just for brevity.

lodash / js: Filtering values within an object based on regular expressions and getting the highest by comparison

For the following json
[
{
"index": "xyz",
...
},
{
"index": "abc1234",
...
},
{
"index": "xyz",
...
},
{
"index": "abc5678",
...
}
...
I want to filter out abc values and xyz values separately.
I tried the following to get values
var x = _.filter(jsonData, function (o) {
return /abc/i.test(o.index);
});
and it worked to give the filtered outputs.
Now i want to get the highest of abc values that is if there values abc123, abc444, abc999 then the code should return abc999.
I can loop over again using lodash but could this be done in a single call - within the same one that filters out?
You can use Array.prototype.reduce(), String.prototype.replace() with RegExp /\D+/ to match and remove characters that are not digits. Check if previous number portion of string is less than current number portion of string
var jsonData = [
{
"index": "xyz",
},
{
"index": "abc1234",
},
{
"index": "xyz",
},
{
"index": "abc5678",
},
{
"index": "abc1",
}];
var x = jsonData.reduce(function (o, prop) {
return /abc/i.test(prop.index)
? !o || +prop.index.replace(/\D+/, "") > +o.replace(/\D+/, "")
? prop.index
: o
: o
}, 0);
console.log(x);
Following is a crude and unsatisfactory implementation:
//filter out matching objects for possible future use
var latest = "";
var matches = _.filter(jsonData, function (o) {
var ret = /abc/i.test(o.index);
if (ret) {
var digits = o.index.replace(/\D/g,'')
if (parseInt(digits) > latest) {
latest = digits;
latestIndex = o.index
console.log(latest+">>>latestIndex")
}
return true;
}
return false;
});
console.log("latestIndex->"+latest);
}
If you want to find the highest abc{SOME_NUMBER} value and filter at the same time, you can just use regular iteration instead of _.filter:
let jsonData = [{"index": "xyz"},{"index": "abc1234"}, {"index": "xyz"},{"index": "abc5678"}];
let max = Number.MIN_SAFE_INTEGER; // value for the max number at the end of "abc"
let item; // item containing the max abc${NUMBER} value
let filtered = []; // filtered array containing abc strings
jsonData.forEach((curr) => {
// filter test
if (/abc/i.test(curr.index)) {
filtered.push(curr);
// max value test
const [digits] = curr.index.match(/\d+/);
const test = parseInt(digits);
if (test > max) {
max = test;
item = curr;
}
}
});
console.log('Item:\n', item, '\n\n----\nFiltered:\n', filtered);
One way to do it using lodash is by replacing filter with maxBy in your code.
Of course, this has the downside that if no valid elements exist in the collection, it'll arbitrarily return an invalid one. So, after getting the result, an extra validity check is needed.
This is why I have extracted the validation/filter code in a separate function:
var jsonData = [{
"index": "xyz",
}, {
"index": "abc1234",
}, {
"index": "xyz",
}, {
"index": "abc5678",
}];
var isValid = function(o) {
return /abc/i.test(o.index);
};
var highest = _.maxBy(jsonData, isValid);
if (isValid(highest)) {
console.log('The max value is: ' + highest.index);
} else {
console.log('No valid value found!');
}
<script src="https://cdn.jsdelivr.net/lodash/4/lodash.min.js"></script>
And here's how it works if there are no valid elements:
var jsonDataWithoutValidValues = [{
"index": "xyz",
}, {
"index": "xyz",
}];
var isValid = function(o) {
return /abc/i.test(o.index);
};
var highest = _.maxBy(jsonDataWithoutValidValues , isValid);
if (isValid(highest)) {
console.log('The max value is: ' + highest.index);
} else {
console.log('No valid value found!');
}
<script src="https://cdn.jsdelivr.net/lodash/4/lodash.min.js"></script>
This is a probably a bit weird to use in production, but I thought it was interesting enough to share.
Well in this case you can just use Array prototype sort after you have filtered out the 'abc' to sort them the way you want
var x = _.filter(jsonData, function (o) {
return /abc/i.test(o.index);
}).sort(function (a, b) {
if(a.index > b.index) return -1;
if(a.index < b.index) return 1;
return 0;
});
if you do the sorting correct you can get the highest value like
console.log(x[0].index)
You could use a single loop with Array#reduce and check the number, if exists.
var data = [{ index: "xyz" }, { index: "abc1234" }, { index: "xyz" }, { index: "abc5678" }],
getNumber = function (s) { return s.match(/^abc(\d+)/i)[1]; },
result = data.reduce(function (r, a) {
return a.index.match(/^abc\d/i) && (!r || getNumber(r.index) < getNumber(a.index)) ? a : r;
}, undefined);
console.log(result);
Here's a lodash chaining approach:
_(data)
.map('index')
.filter(_.method('match', /abc/))
.maxBy(_.flow(_.bindKey(/\d+/, 'exec'), _.first, _.toNumber));
The map() and filter() calls get you a list of stings with abc in them. The maxBy() call finds the max, but we have to compose a function to tell it that we want to compare it numerically. The flow() function is really handy for this. Here, we're telling it to execute the regular expression, find the first element of the result, and turn that into a number.

How to extract a sub-array from two arrays with high performances?

I have two arrays of JSON objects :
one contains about 60.000 elements, which represents my reference dataset. Each JSON inside owns a key and some other attributes. Please note that the key might be not uniq in the array.
another one contains a various number of elements (at least a few thousands). Each JSON inside owns a key (that is also defined in the first array), and some other attributes.
e.g. :
let refarray = [{key : 1, attr1 : 'aze', ...}, {key : 1, attr1 : 'zer', ...},{key : 2, attr1 : 'ert'},...]
let otherarray = [{key : 1, attr2 : 'wxc', ...}, {key : 3, attr2 : 'xcv'},...]
I simply need to extract from refarray all elements whose key exists in otherarray.
For the moment I'm using loadash as following :
let newarray = _.filter(refarray , function(d) { return _.findIndex(otherarray , function(s) { return s.key=== d.key;}) >= 0});
But it takes between 3 and 15 seconds, which is far too long. Any quickest solution is welcome. Thanks.
You may try caching the keys of otherarray and then filter refarray. I tried a small sample (although I tried on node and not browser) and it was taking a little over 100 ms:
let refarray = []
let otherarray = []
for(let i of Array(60 * 1000).keys())
refarray.push({ key: 1 + (i % 1200) })
for(let i of Array(1000).keys())
otherarray.push({ key: i + 1 })
console.time('cache')
let cache = _.uniq(_.map(otherarray, n => n.key))
const inCache = n => cache.indexOf(n.key) !== -1
let newArray = _.filter(refarray, inCache)
console.timeEnd('cache')
console.log(refarray.length, otherarray.length, newArray.length);
Depending on the amount of duplicate keys, solution by Emil S. Jørgensen might not be optimal enough. I would go with iterating over distinct values of 1st array:
d2 = Date.now();
var distinct = [];
refarray.forEach(function(item) {
if (distinct.indexOf(item.key) < 0) {
distinct.push(item.key);
}
});
console.log('Results:',otherarray.filter(function(item) {
return distinct.indexOf(item.key) > -1;
}));
console.log('Milliseconds to filter:', Date.now() - d2);
Array.prototype.filter with Array.prototype.some should be the fastest approach.
//Default ref
var refarray = [{
key: 1,
attr1: 'aze'
}, {
key: 2,
attr1: 'zer'
}];
//Default other
var otherarray = [{
key: 1,
attr2: 'wxc'
}, {
key: 3,
attr2: 'xcv'
}];
//Padding ref
while (refarray.length < 10 * 1000) {
refarray.push({
key: 5,
attr1: 'aze'
})
}
//Padding other
while (otherarray.length < 60 * 1000) {
otherarray.push({
key: 6,
attr2: 'aze'
})
}
console.log('Size of refarray:', refarray.length);
console.log('Size of otherarray:', otherarray.length);
var d = Date.now();
console.log('Results:',refarray.filter(function(a) {
return otherarray.some(function(b) {
return b.key == a.key
})
}));
console.log('Milliseconds to filter:', Date.now() - d);

Categories