I'm querying Firebase to get some data to throw into Chart.js. Here's how I've laid out my data:
{
"20160428": {
"follow": 13,
"host": 6,
"raid": 1,
"substreak": 1,
"tip": 1
},
"20160429": {
"follow": 15,
"host": 21,
"raid": 2,
"substreak": 4
},
"20160430": {
"follow": 4
},
"20160501": {
"follow": 11,
"host": 15,
"subscription": 4,
"substreak": 5
},
"20160502": {
"follow": 2,
"host": 6,
"subscription": 1,
"substreak": 4
},
"20160503": {
"follow": 2
}
}
As you can see, each object is keyed off by a timestamp and events don't always appear in every object (but there are a finite number of events). Here's how I'd like the data to look so I can feed it into Chart.js:
labels: ["20160428", "20160429", "20160430", ...]
{
"follow": [13, 15, 4, 11, 2, 2],
"host": [6, 21, 0, 15, 6, 0],
"raid": [1, 2, 0, 0, 0, 0],
"subscription": [0, 0, 0, 4, 1, 0]
"substreak": [1, 4, 0, 5, 4, 0]
"tip": [1, 0, 0, 0, 0, 0]
}
I've played with Lodash's groupBy and similar functions, but I'm not sure if I'm on the right track. I wouldn't mind doing this x times either per event, but at this point I can't change the schema.
If you have a defined set of keys that you want to group then you have to:
Use map() to pluck values with compact() to get non-null values from the object collection from the defined set of keys.
Build the result using zipObject() from the defined keys and values obtained from the first step.
var keys = ["follow", "host", "raid", "substreak", "tip", "subscription"];
var values = _.map(keys, key => _(data).map(key).compact().value());
var result = _.zipObject(keys, values);
var data = {
"20160428": { "follow": 13, "host": 6, "raid": 1, "substreak": 1, "tip": 1 },
"20160429": { "follow": 15, "host": 21, "raid": 2, "substreak": 4 },
"20160430": { "follow": 4 },
"20160501": { "follow": 11, "host": 15, "subscription": 4, "substreak": 5 },
"20160502": { "follow": 2, "host": 6, "subscription": 1, "substreak": 4 },
"20160503": { "follow": 2 }
};
var keys = ["follow", "host", "raid", "substreak", "tip", "subscription"];
var values = _.map(keys, key => _(data).map(key).compact().value());
var result = _.zipObject(keys, values);
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.js"></script>
If you want to group them from all the keys present in the object collection then you can:
Get all the unique keys by:
map() each data object inside the object collection
flatten() the resulting array
Use uniq() to get all unique keys from the flattened array.
Use the methodology in the first example to get the values and build the object.
var keys = _(data).map(_.keys).flatten().uniq().value();
var values = _.map(keys, key => _(data).map(key).compact().value());
var result = _.zipObject(keys, values);
var data = {
"20160428": { "follow": 13, "host": 6, "raid": 1, "substreak": 1, "tip": 1 },
"20160429": { "follow": 15, "host": 21, "raid": 2, "substreak": 4 },
"20160430": { "follow": 4 },
"20160501": { "follow": 11, "host": 15, "subscription": 4, "substreak": 5 },
"20160502": { "follow": 2, "host": 6, "subscription": 1, "substreak": 4 },
"20160503": { "follow": 2 }
};
var keys = _(data).map(_.keys).flatten().uniq().value();
var values = _.map(keys, key => _(data).map(key).compact().value());
var result = _.zipObject(keys, values);
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.js"></script>
You could use plain javascript and some loops.
var data = { "20160428": { "follow": 13, "host": 6, "raid": 1, "substreak": 1, "tip": 1 }, "20160429": { "follow": 15, "host": 21, "raid": 2, "substreak": 4 }, "20160430": { "follow": 4 }, "20160501": { "follow": 11, "host": 15, "subscription": 4, "substreak": 5 }, "20160502": { "follow": 2, "host": 6, "subscription": 1, "substreak": 4 }, "20160503": { "follow": 2 } },
grouped = {}
Object.keys(data).forEach(function (k) {
["follow", "host", "raid", "substreak", "tip"].forEach(function (kk) {
grouped[kk] = grouped[kk] || [];
grouped[kk].push(data[k][kk] || 0);
});
});
document.write('<pre>' + JSON.stringify(grouped, 0, 4) + '</pre>');
Don't try this at home.
var fields = {
follow: 0,
host: 0,
raid: 0,
substreak: 0,
tip: 0,
subscription: 0
};
_(data)
.values()
.map(x => _.assign({}, fields, x))
.map(_.toPairs)
.flatten()
.groupBy(0)
.mapValues(x => _.map(x, 1))
.value();
Related
i have a json and need to extract data to array.
const data = [{
"week": 1,
"lost": 10,
"recovery_timespan": [{
"week": 2,
"count": 1
}, {
"week": 3,
"count": 0
}],
"netLost": 10,
"netReturned": 20
}, {
"week": 2,
"lost": 7,
"recovery_timespan": [{
"week": 3,
"count": 1
}],
"netLost": 30,
"netReturned": 200
}, {
"week": 3,
"lost": 8,
"recovery_timespan":"",
"netLost": 50,
"netReturned": 40
}];
Expected output: lost,count in recovery_timespan,netLost , netReturned.
[ [ 10, 1, 0, 10, 20 ], [ 7, 1, 30, 200 ], [ 8, 50, 40 ] ]
As you can see expected output, last recovery_timespan does not contain any data and it just shows as "".so i need to ignore it.
My approach:
const result = data.map(({lost, recovery_timespan,netLost,netReturned}) => [
lost,
...recovery_timespan.map(({count}) => count),
netLost,netReturned
]);
My code breaks when "recovery_timespan" is "". How can i add a filter along with map to filter that part and make my code work?
It's just a matter of checking if it's string or not, but you can short circuit
const result = data.map(({lost, recovery_timespan,netLost,netReturned}) => [
lost,
...(recovery_timespan || []).map(({count}) => count),
netLost,netReturned
]);
I have an array of nested regions that look like this:
Egypt
Zone 1
Tagamo3
Giza
Helwan
Fayoum
Zone 2
Gesr ElSuis
test
Delta
Mohandeseen
Down Town
The array itself:
[
{
"key": 10,
"title": "Egypt",
"parent_key": null,
"children": [
{
"key": 1,
"title": "Zone 1",
"parent_key": 10,
"children": [
{
"key": 3,
"title": "Tagamo3",
"parent_key": 1,
"children": []
},
{
"key": 7,
"title": "Giza",
"parent_key": 1,
"children": []
},
{
"key": 8,
"title": "Helwan",
"parent_key": 1,
"children": []
},
{
"key": 11,
"title": "Fayoum",
"parent_key": 1,
"children": []
}
]
},
{
"key": 2,
"title": "Zone 2",
"parent_key": 10,
"children": [
{
"key": 4,
"title": "Gesr ElSuis",
"parent_key": 2,
"children": [
{
"key": 12,
"title": "test",
"parent_key": 4,
"children": []
}
]
},
{
"key": 5,
"title": "Delta",
"parent_key": 2,
"children": []
},
{
"key": 6,
"title": "Mohandeseen",
"parent_key": 2,
"children": []
},
{
"key": 9,
"title": "Down Town",
"parent_key": 2,
"children": []
}
]
}
]
}
]
I want to return to the highest region in a given input
Examples:
input [7, 1, 10] should return [10] since 10 is Egypt parent of 1 and 7
input [1, 2] should return both [1, 2] since they are on the same level both Zone 1 and zone 2 located under Egypt
input [2, 3, 1] should return [2, 1] since they are on the same level and 3 removed because it's a child of 1
input [1, 4] should return [1, 4] since they are on different levels and no one parent to the other
First it helps to turn your tree structure into a map of descendant ids, recursively:
const descendantsMap = new Map<number, Set<number>>();
function walk(tree: Tree) {
const s: Set<number> = new Set();
descendantsMap.set(tree.key, s);
for (const t of tree.children) {
walk(t);
s.add(t.key);
descendantsMap.get(t.key)?.forEach(v => s.add(v));
}
}
arr.forEach(walk);
We are building up a Map from each key in your tree structure to a Set of the keys of its descendants. The walk() function is recursive, and we merge the descendants for the children of each node into the descendants for the current node.
Let's make sure it looks right:
console.log(descendantsMap);
/* Map (12) {
10 => Set (11) {1, 3, 7, 8, 11, 2, 4, 12, 5, 6, 9},
1 => Set (4) {3, 7, 8, 11},
3 => Set (0) {},
7 => Set (0) {},
8 => Set (0) {},
11 => Set (0) {},
2 => Set (5) {4, 12, 5, 6, 9},
4 => Set (1) {12},
12 => Set (0) {},
5 => Set (0) {},
6 => Set (0) {},
9 => Set (0) {}
} */
Yes. You can see how now we have a quick mapping from each key to the set of keys in its descendant subtree.
Now to get the "highest" entries in an array (I would call these the "shallowest" since they are closest to the root), we find all the descendants of all the elements in the array and then filter these out of the array:
const shallowest = (x: number[]): number[] => {
const descendants = new Set<number>();
for (const v of x) {
descendantsMap.get(v)?.forEach(i => descendants.add(i));
}
console.log(descendants); // just to understand what's happening
return x.filter(v => !descendants.has(v));
}
Let's test it:
console.log(shallowest([7, 1, 10]));
// descendants are {3, 7, 8, 11, 1, 2, 4, 12, 5, 6, 9}
// output [10]
console.log(shallowest([1, 2]));
// descendants are {3, 7, 8, 11, 4, 12, 5, 6, 9};
// output [1, 2]
console.log(shallowest([2, 3, 1]));
// descendants are {4, 12, 5, 6, 9, 3, 7, 8, 11};
// output [2, 1]
console.log(shallowest([1, 4]));
// descendants are {3, 7, 8, 11, 12};
// output [1, 4]
Looks good. You can see that shallowest([7, 1, 10]) first finds all the descendants of 7, 1, and 10, which is {3, 7, 8, 11, 1, 2, 4, 12, 5, 6, 9}, or everything except 10. So when we filter those out of [7, 1, 10] we are left with just 10. Similarly, shallowest([1, 2]) and shallowest([1, 4]) produce sets of descendants that don't overlap at all with the input, so the output is identical to the input. And with shallowest([2, 3, 1]), the list of descendants contains 3 but not 2 or 1, so the output is [2, 1].
Playground link to code
This is my 2nd attempt, thanks to jcalz for pointing out the error and his solution is neater than mine.
The function buildArray builds an array of objects in to the variable keyArray, the key is the element in the array to be searched and another array that's the path to that element (so key 7 will have a path of [10, 1, 7]).
We then filter keyArray to remove any elements that have a parent in the original search array.
Anyway, reading jcalz's solution, I've learnt about maps so my time's not been entirely wasted. Hope this helps in some way though.
console.log(search2([7, 1, 10], obj)); //returns [10]
console.log(search2([1,2], obj)); //returns [1,2]
console.log(search2([2,3,1], obj)); //returns [1,2]
console.log(search2([1,4], obj)); //returns [1,4]
function search2(search, obj) {
keyArray=[];
buildArray(obj);
return keyArray.filter((element)=> !element.path.some(e => search.includes(e))).map((e)=> e.key);
function buildArray(obj, path=[]) {
obj.forEach((element) =>{
if(search.includes(element.key)) {
keyArray.push({"key":element.key,"path":path});
}
buildArray(element.children,[...path,element.key]);
});
}
}
Stocks: [{
PRN: 1,
PID: 1,
Qty: 3,
SlNos: [1, 2, 3]
}, {
PRN: 1,
PID: 2,
Qty: 4,
SlNos: [10, 11, 12, 13]
}, {
PRN: 2,
PID: 1,
Qty: 3,
SlNos: [4, 5, 6]
}, {
PRN: 2,
PID: 2,
Qty: 4,
SlNos: [14, 15, 16, 17]
}]
I want this array as bellow with Lodash
Stocks: [{
PID: 1,
Qty: 6,
SlNos: [1, 2, 3, 4, 5, 6]
}, {
PID: 2,
Qty: 4,
SlNos: [10, 11, 12, 13, 14, 15, 16, 17]
}]
Here is a solution with _lodash:
var stocks = [{ PRN: 1, PID: 1, Qty: 3, SlNos: [1, 2, 3] }, { PRN: 1, PID: 2, Qty: 4, SlNos: [10, 11, 12, 13] }, { PRN: 2, PID: 1, Qty: 3, SlNos: [4, 5, 6] }, { PRN: 2, PID: 2, Qty: 4, SlNos: [14, 15, 16, 17] }]
const result = _.reduce(stocks, (r, {PRN, ...c}) => {
let _c = _.find(r, {'PID': c.PID})
if(_c)
_c = _.mergeWith(_c, c, (ov, sv, k) => _.includes(['Qty','SlNos'], k) ? _.isArray(sv) ? (ov || []).concat(sv) : _.isNumber(sv) ? sv + (ov || 0) : sv : ov)
else
r.push(c)
return r
}, [])
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
We are using reduce with mergeWith (which is the main thing here really) plus a little bit of includes.
Here is a solution without lodash that will do the trick:
var stocks = [{ PRN: 1, PID: 1, Qty: 3, SlNos: [1, 2, 3] }, { PRN: 1, PID: 2, Qty: 4, SlNos: [10, 11, 12, 13] }, { PRN: 2, PID: 1, Qty: 3, SlNos: [4, 5, 6] }, { PRN: 2, PID: 2, Qty: 4, SlNos: [14, 15, 16, 17] }]
const result = stocks.reduce((r, c) => {
_c = r.find(x => x.PID === c.PID)
if (_c) {
_c.PID = c.PID
_c.Qty = _c.Qty + c.Qty
_c.SlNos = _c.SlNos ? _c.SlNos.concat(c.SlNos) : c.SlNos
} else {
r.push(!delete(c.PRN) || c)
}
return r
}, [])
console.log(result)
The idea is to use reduce and first find if we had already that object by PID if so sum the values if not insert to the final array. Since we are going through each record that logic does the trick in one loop.
Let's say we have an Array which contains arrays inside:
[
["2000-01-01", "xyz1#gmail.com", 1, 9, 338],
["2000-01-01", "xyz2#yahoo.com", 1, 2, 159],
["2000-01-01", "xyz3#yahoo.com", 1, 5, 462],
["2000-01-01", "xyz4#yahoo.com", 1, 6, 417],
["2000-01-01", "xyz5#gmail.com", 1, 3, 156],
["2000-01-01", "xyz6#gmail.com", 1, 8, 414],
]
I want to get top 2 based on the last column i.e.
["2000-01-01", "xyz3#yahoo.com", 1, 8, 462],
["2000-01-01", "xyz4#yahoo.com", 1, 6, 417],
We can use Array.filter but not really sure how to in this situation.
You could sort descending by the element at index 4 and take the first two elements.
This propposal features a destructuring assignment, where an array is taken for destructuring and the property 4 is taken and renamed to a rsp. to b.
Example:
vvv
{ 4: a } = ["2000-01-01", "xyz1#gmail.com", 1, 9, 338]
^
Result
a = 338
var array = [["2000-01-01", "xyz1#gmail.com", 1, 9, 338], ["2000-01-01", "xyz2#yahoo.com", 1, 2, 159], ["2000-01-01", "xyz3#yahoo.com", 1, 5, 462], ["2000-01-01", "xyz4#yahoo.com", 1, 6, 417], ["2000-01-01", "xyz5#gmail.com", 1, 3, 156], ["2000-01-01", "xyz6#gmail.com", 1, 8, 414]],
top2 = array.sort(({ 4: a }, { 4: b }) => b - a).slice(0, 2);
console.log(top2);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Without sorting:
function getTopTwo(array){
let first = {4: - Infinity}, second = { 4: - Infinity};
for(const el of array){
if(el[4] > first[4]){
second = first;
first = el;
} else if(el[4] > second[4]){
second = el;
}
}
return [first, second];
}
I've got an object like so...
var oInfoModal = "{"modals":[{"myID":17,"warningIdx":[0]},{"myID":12,"warningIdx":[1,2]},{"myID":11,"warningIdx":[3]},{"myID":10,"warningIdx":[4]},{"myID":9,"warningIdx":[5,6,7]},{"myID":8,"warningIdx":[8]},{"myID":7,"warningIdx":[9,10]},{"myID":6,"warningIdx":[11,12]},{"myID":5,"warningIdx":[13,14]},{"myID":4,"warningIdx":[15,16]},{"myID":3,"warningIdx":[17]},{"myID":1,"warningIdx":[18,19]},{"myID":0,"warningIdx":[20,21]}]}";
I'd like to filter the object based on a warnIdx value passed to a function but I'm getting empty object as the result. I know the issue is with oInfoModal.modals.warningIdx being an array, itself. But I'm not sure how to return the filtered oInfoModal.modals object.
function filterInfoModals(warnIdx) {
return oInfoModal.modals.filter(function (item) {
return item.warningIdx == warnIdx;
});
}
I've also tried
function filterInfoModals(warnIdx) {
return oInfoModal.modals.filter(function (item) {
return item.warningIdx.filter(function (idx,val) {
return val.warningIdx == warnIdx;
});
});
}
Which gives me back the original, unfiltered, object.
Just going in circles now. Any guidance would be appreciated!
You don't have a valid object. You have it wrapped in " " quotes, so therefore it is a string. Thus, your filter is not going to work properly. There are other syntax errors in your object as well that are going to prevent it from working properly. Here is the complete fix:
var oInfoModal = {
"modals": [
{ "myID": 17, "warningIdx": [0] },
{ "myID": 12, "warningIdx": [1, 2] },
{ "myID": 11, "warningIdx": [3] },
{ "myID": 10, "warningIdx": [4] },
{ "myID": 9, "warningIdx": [5, 6, 7] },
{ "myID": 8, "warningIdx": [8] },
{ "myID": 7, "warningIdx": [9, 10] },
{ "myID": 6, "warningIdx": [11, 12] },
{ "myID": 5, "warningIdx": [13, 14] },
{ "myID": 4, "warningIdx": [15, 16] },
{ "myID": 3, "warningIdx": [17] },
{ "myID": 1, "warningIdx": [18, 19] },
{ "myID": 0, "warningIdx": [20, 21] }
]
};
function filterInfoModals(warnIdx) {
return oInfoModal.modals.filter(function (item) {
return item.warningIdx.indexOf(warnIdx) > -1;
});
}
console.log(filterInfoModals(1,2));