obtaining a count after using reduce in Javascript - javascript

I'm trying to find a way to count the amount of times the direction is 'IN' or 'Out' after grouping them.
for example in the below data I would like Person and Item tag to be the group and then output how many IN's or OUT's show up based on their unique groups.
const data = [
{
Person: 'Jake',
Item_tag: '124',
date: 'Mon May 11 2020',
direction: 'IN',
},
{
Person: 'Jake',
Item_tag: '123',
date: 'Tue May 12 2020',
direction: 'IN',
},
{
Person: 'Jake',
Item_tag: '123',
date: 'Tue May 12 2020',
direction: 'OUT',
},
]
I would expect an output like
Person: Jake Item_tag:123 Incoming: 1 outgoing:1
Person: Jake Item_tag:124 Incoming:1 outgoing:0
Below is what I've tried using reduce, but I couldn't figure out how to get the proper count inside the reduce.
import React from 'react'
type Props = {}
//Data
const data = [
{
Person: 'Jake',
Item_tag: '123',
date: 'Mon May 11 2020',
direction: 'IN',
},
{
Person: 'Mary',
Item_tag: '123',
date: 'Mon May 11 2020',
direction: 'OUT',
},
{
Person: 'Jake',
Item_tag: '123',
date: 'Tue May 12 2020',
direction: 'IN',
},
{
Person: 'Jake',
Item_tag: '123',
date: 'Tue May 12 2020',
direction: 'OUT',
},
]
const result = data.reduce((res, current) => {
// build the grouping (Person + Item_tag)
const key = `${current.Person}_${current.Item_tag}`
res[key] = res[key] || {
Person: current.Person,
Item_tag: current.Item_tag,
Count_of_incoming: '0',
Count_of_outgoing: '0',
return res
}, {})
// print values
console.log(Object.values(result))
Any help is appreciated!

You just need one more increment instruction for res[key].Count_of_xxxx, where xxxx depends on current.direction.
A tiny issue: don't inialise these counters as strings, but as numbers.
See the added if..else:
const data = [
{Person: 'Jake',Item_tag: '123',date: 'Mon May 11 2020',direction: 'IN',},
{Person: 'Mary',Item_tag: '123',date: 'Mon May 11 2020',direction: 'OUT',},
{Person: 'Jake',Item_tag: '123',date: 'Tue May 12 2020',direction: 'IN',},
{Person: 'Jake',Item_tag: '123',date: 'Tue May 12 2020',direction: 'OUT',},
];
const result = data.reduce((res, current) => {
// build the grouping (Person + Item_tag)
const key = `${current.Person}_${current.Item_tag}`;
res[key] = res[key] || {
Person: current.Person,
Item_tag: current.Item_tag,
Count_of_incoming: 0,
Count_of_outgoing: 0,
};
if (current.direction === "OUT") {
res[key].Count_of_outgoing++;
} else {
res[key].Count_of_incoming++;
}
return res;
}, {});
// print values
console.log(Object.values(result));

Let's try to group by Person and Item_tag using a separator first. Then using object of objects. Seems appropriate for a more generic solution but for now this works:
const data = [{
Person: 'Jake',
Item_tag: '124',
date: 'Mon May 11 2020',
direction: 'IN',
}, {
Person: 'Jake',
Item_tag: '123',
date: 'Tue May 12 2020',
direction: 'IN',
}, {
Person: 'Jake',
Item_tag: '123',
date: 'Tue May 12 2020',
direction: 'OUT',
},
];
var step1 = data.reduce(function(acc, item) {
var key1 = item.Person;
var key2 = item.Item_tag;
acc[key1] = acc[key1] || {};
acc[key1][key2] = acc[key1][key2] || {IN: 0, OUT: 0}
acc[key1][key2][item.direction] = acc[key1][key2][item.direction] + 1
return acc;
}, {})
for (var key1 in step1) {
for (var key2 in step1[key1]) {
console.log(`Person: ${key1} Item_tag:${key2} Incoming: ${step1[key1][key2].IN} outgoing:${step1[key1][key2].OUT}`)
}
}

Related

Group array of objects by date and get newest in each day in Javascript

I'm using Express JS and Mongo DB to build an API. I have entries in a collection to store various documents with data every day. My query pulls out data in the following format using toArray()
[
{
name: 'John',
events: '600',
created_at: '2021-01-20T14:56:42.368+00:00' // date object
},
{
name: 'Edward',
events: '900',
created_at: '2021-01-20T20:56:42.368+00:00' // date object
},
{
name: 'Jane',
events: '100',
created_at: '2021-01-19T13:56:42.368+00:00' // date object
},
{
name: 'Robert',
events: '700',
created_at: '2021-01-19T15:56:42.368+00:00' // date object
}
]
I need to get each object for the given date (grouped) and then get the newest one in that day, for example:
[
{
name: 'Edward',
events: '900',
created_at: '2021-01-20T20:56:42.368+00:00' // date object
},
{
name: 'Robert',
events: '700',
created_at: '2021-01-19T15:56:42.368+00:00' // date object
}
]
I've written a function to try this, but it's giving me the following type of format...
{
'Tue Jan 19 2021 15:56:42 GMT+0000 (Greenwich Mean Time)': [
{
name: 'Robert',
events: '700',
created_at: '2021-01-19T15:56:42.368+00:00'
}
]
}
How can I modify my function to achieve the desired results. Where am I going wrong?
function groupDataBeyondDaily (data, key = 'created_at') {
const group = data.reduce((r, a) => {
r[a.created_at] = [...r[a.created_at] || [], a]
return r
}, {})
return group;
}
You are currently grouping them by the date strings stored in the created_at property, but you need to be grouping them by date and then comparing time of day separately.
The example snippet below creates a new Date() from the date string and calculates the epoch day to use as a key in the accumulator. It then compares the previously stored date against the current iteration and updates it if it is later. Finally, it returns the Object.values() of the object returned by the reduce() call.
const input = [{ name: 'John', events: '600', created_at: '2021-01-20T14:56:42.368+00:00' }, { name: 'Edward', events: '900', created_at: '2021-01-20T20:56:42.368+00:00' }, { name: 'Jane', events: '100', created_at: '2021-01-19T13:56:42.368+00:00' }, { name: 'Robert', events: '700', created_at: '2021-01-19T15:56:42.368+00:00' }];
function groupDataBeyondDaily(data) {
const group = data.reduce((a, o) => {
const
date = new Date(o['created_at']),
day = Math.floor(date / 8.64e7); // 86,400,000ms per day
//let entry = (a[day] ??= { ...o }); using logical nullish assignment
let entry = (a[day] = a[day] || { ...o });
if (new Date(entry['created_at']) < date) {
a[day] = { ...o };
}
return a
}, {})
return Object.values(group);
}
console.log(groupDataBeyondDaily(input))

How to dynamically build nested objects

I'm getting data from an api and want to format the data in a way that the client side can read.
I've tried the following code to build up the object, but it feels hacky and doesn't take care of edge cases.
function myHackyFunction(data){
result = {}
data.forEach(el => {
const timeStamp = el['time']
result[timeStamp] = { teacher: null, student: null }
})
data.forEach(el => {
const role = el['role']
const timeStamp = el['time']
const age = el['age']
if (role.includes('teacher')) {
result[timeStamp].teacher = age
}
if (role.includes('student')) {
result[timeStamp].student = age
}
})
return result
}
myHackyFunction(data)
The data variable will have different length, but always the same setup. Sometimes it includes both the student and teacher role, sometimes just one of them.
this data..
const data = [
{
time: 2019,
role: 'student',
age: 22
},
{
time: 2019,
role: 'teacher',
age: 37
},
{
time: 2020,
role: 'teacher',
age: 45
}
]
..should look like:
const desiredData = {
2019: {
student: 22,
teacher: 37
},
2020: {
student: null,
teacher: 45
}
}
Whenever you see data that looks like your grouping etc, Array.reduce would normally fit the bill.
eg.
const data = [
{
time: 2019,
role: 'student',
age: 22
},
{
time: 2019,
role: 'teacher',
age: 37
},
{
time: 2020,
role: 'teacher',
age: 45
}
];
//..should look like:
const desiredData = data.reduce((a, v) => {
a[v.time] = a[v.time] || {student: null, teacher: null};
a[v.time][v.role] = v.age;
return a;
}, {});
console.log(desiredData);
Find the solution below for the problem:
console.clear()
const data = [
{
time: 2019,
role: 'student',
age: 22
},
{
time: 2019,
role: 'teacher',
age: 37
},
{
time: 2020,
role: 'teacher',
age: 45
}
]
const m = {}
const len = data.length
for (let i = 0; i < len; ++i) {
if (!m[data[i].time]) m[data[i].time] = {student: null, teacher: null}
if (data[i].role === 'student')
m[data[i].time].student = data[i].age
else if (data[i].role === 'teacher')
m[data[i].time].teacher = data[i].age
}
console.log(m)
Note: This will only work if there is one student/teacher in a given year, otherwise the value will be overridden.

ES5 Javascript map through objects in array, then change the object key by group

I have an array of object, I tried to add one more word the object key, but having trouble with it, please check my data and expect output and the code I tried below:
Trying to get output like this:
const data = [
{
'name_A': Joe,
'year_A': 2019,
'saving_A': 0,
'member_A': false,
'group:_A': "A"
},
{
'name_B': Amy,
'year_B': 2019,
'saving_B': 0,
'member_B': false,
'group_B': "B"
},
{
'name_C': Bob,
'year_C': 2019,
'saving_C': 0,
'member_C': true,
'group_C': "C"
}
];
Here's my code:
const data = [{
name: 'Joe',
year: 2019,
saving: 0,
member: false,
group: "A"
},
{
name: 'Amy',
year: 2019,
saving: 0,
member: false,
group: "B"
},
{
name: 'Bob',
year: 2019,
saving: 0,
member: true,
group: "C"
}
];
const filter = data.reduce((acc, key) => {
const groupName = key.group;
const obj = {};
if (key.member === false) {
const nodeId = key.nodeId + groupName;
obj.nodeId = key.nodeId;
obj.year = key.year;
acc.push(obj);
}
return acc;
}, []);
console.log(filter)
Thanks for the help!
You can do it by using reduce:
const data = [
{
name: 'Joe',
year: 2019,
saving: 0,
member: false,
group: "A"
},
{
name: 'Joe',
year: 2019,
saving: 0,
member: false,
group: "B"
},
{
name: 'Joe',
year: 2019,
saving: 0,
member: true,
group: "C"
}
];
let result = data.map(el => {
const group = el.group
return Object.keys(el).reduce((a, i) => {
a[`${i}_${group}`] = el[i]
return a
}, {})
})
console.log(result)
You can take advantage of Array.prototype.map and Array.prototype.reduce to resolve this issue:
const data = [{
name: 'Joe',
year: 2019,
saving: 0,
member: false,
group: "A"
},
{
name: 'Amy',
year: 2019,
saving: 0,
member: false,
group: "B"
},
{
name: 'Bob',
year: 2019,
saving: 0,
member: true,
group: "C"
}
];
const format = arr => arr.map(el => {
return Object.keys(el).reduce((accum, key) => {
accum[`${key}_${el.group}`] = el[key];
return accum;
}, {});
});
console.log(format(data));
An alternative is a mix between the function map and function reduce.
The function map for converting the current objects to the desired structure, and the function reduce to concatenate the group with the current keys.
const data = [ { name:'Joe', year: 2019, saving: 0, member: false, group:"A" }, { name: 'Amy', year: 2019, saving:0, member: false, group: "B" }, { name:'Bob', year: 2019, saving: 0, member: true, group:"C" } ];
let result = data.map(({group, ...rest}) => Object.entries(rest).reduce((a, [k, v]) => Object.assign(a, {[`${k}_${group}`]: v}), {[`group_${group}`]: group}));
console.log(result);

Join two select statements containing a where clause using alasql in node.js

I am using alasql in node.js, and I cannot get a join to work.
Here you have dummy data:
x = [
{ date: 20180501, price: 23, product: 'x' },
{ date: 20180501, price: 46, product: 'y' },
{ date: 20180502, price: 29, product: 'x' },
{ date: 20180502, price: 50, product: 'y' },
{ date: 20180503, price: 22, product: 'x' },
{ date: 20180503, price: 43, product: 'y' },
{ date: 20180504, price: 21, product: 'x' },
{ date: 20180504, price: 43, product: 'y' },
{ date: 20180505, price: 26, product: 'x' },
{ date: 20180505, price: 48, product: 'y' }]
I would like to get, for each day, the ratio between the price of product y and product y. So, my desired output is:
desiredOutput = [
{ date: 20180501, price_ratio: 46/23},
{ date: 20180502, price_ratio: 50/29},
{ date: 20180503, price_ratio: 43/22},
{ date: 20180504, price_ratio: 43/21},
{ date: 20180505, price_ratio: 48/26}]
I am attempting to get this with the following query:
alasql("select date, price_y/price_x as price_ratio from (select date, price as price_y from ? where product='y') as y join (select date, price as price_x from ? where product='x') as x on x.date=y.date", [x,x])
But I can't get it to work. It doesn't crash or anything, but I only get price_x, not the ratio. This is what I get:
[
{ date: 20180501, price_x: 23 },
{ date: 20180502, price_x: 29 },
{ date: 20180503, price_x: 22 },
{ date: 20180504, price_x: 21 },
{ date: 20180505, price_x: 26 } ]
I can get the desired result by running and storing each subquery separately, and then performing the join using those objects, but I would like to know how to do it with just nested subqueries, in a single call.
Any help would be appreciated!
/* to get produt x and y prices for each date */
const res = alasql("select date, sum(case when product='x' then price else null end) as price_x, sum(case when product='y' then price else null end) as price_y from ? group by date", [x])
/* to get ratio */
const res1 = alasql("select date, concat(price_y, '/', price_x) price_ratio from (select date, sum(case when product='x' then price else null end) as price_x, sum(case when product='y' then price else null end) as price_y from ? group by date)", [x])
console.log(res)
console.log(res1)
EDIT: added DEMO

How to process data in object collection?

Now, in my object collection,obj:
{date:2015/9/11,name:Ann,apple:1},
{date:2015/9/12,name:Ann,apple:0},
{date:2015/9/13,name:Ann,apple:1},
{date:2015/9/11,name:Bob,apple:1},.....
and I print it out like this:
2015/9/11 Ann 1
2015/9/12 Ann 0
2015/9/13 Ann 1
2015/9/11 Bob 1
2015/9/12 Bob 1
2015/9/13 Bob 1
Here is my code
var index, objLen
for (index = 0, objLen = obj.length; index<objLen; ++index){
console.log(obj[index].date +'\t'+ obj[index].name +'\t'+ result[index].apple)
}
I wish to output:
2015/9/13 Ann 2
2015/9/13 Bob 3
This shows how many apples Ann and Bob have eaten in this three days
I want to know how to get the output.
You can use a Array.prorotype.reduce to merge the items with the same name, storing the results in an object.
var obj = [
{ date: '2015/9/11', name: 'Ann', apple: 1 },
{ date: '2015/9/12', name: 'Ann', apple: 0 },
{ date: '2015/9/13', name: 'Ann', apple: 1 },
{ date: '2015/9/11', name: 'Bob', apple: 1 },
{ date: '2015/9/12', name: 'Bob', apple: 2 },
{ date: '2015/9/13', name: 'Bob', apple: 1 }
];
var merged = obj.reduce(function(merged, item) {
if (!merged.hasOwnProperty(item.name)) {
merged[item.name] = { date: item.date, name: item.name, apple: 0 };
}
merged[item.name].date = item.date;
merged[item.name].apple += item.apple;
return merged;
}, {});
// merged == {
// Ann: { date: '2015/9/13', name: 'Ann', apple: 2 },
// Bob: { date: '2015/9/13', name: 'Bob', apple: 4 }
// }
But this one results into an object with the person's name as the key. If you want to convert this back to an array of merged items, you can can make an array out of the values of the merged object using a combination of Object.keys and Array.prototype.map:
merged = Object.keys(merged).map(function(item) {
return merged[item];
});
// merged == [
// { date: '2015/9/13', name: 'Ann', apple: 2 },
// { date: '2015/9/13', name: 'Bob', apple: 4 }
// ]
You can then now use your code to print out the elements of this array.
Refer to the following links to read more about the different methods used in this solution:
Array.prototype.reduce
Array.prototype.map
Object.keys
Some functional approach to group and count element by passed key:
var obj = [{
date: '2015/9/11',
name: 'Ann',
apple: 1
}, {
date: '2015/9/12',
name: 'Ann',
apple: 0
}, {
date: '2015/9/13',
name: 'Ann',
apple: 1
}, {
date: '2015/9/11',
name: 'Bob',
apple: 1
}, {
date: '2015/9/12',
name: 'Bob',
apple: 2
}, {
date: '2015/9/13',
name: 'Bob',
apple: 1
}];
function groupBy(key, collenction) {
return collenction.reduce(function(group, element) {
if (!group[element[key]]) {
group[element[key]] = [element]
} else {
group[element[key]].push(element)
}
return group
}, {})
}
function countCollectionBy(key, collenction) {
return Object.keys(collenction).reduce(function(memo, element) {
memo[element] = countBy(key, collenction[element])
return memo;
}, {})
}
function countBy(key, collenction) {
return collenction.reduce(function(memo, element) {
return memo + element[key]
}, 0)
}
alert(JSON.stringify(countCollectionBy('apple', groupBy('name', obj))))

Categories

Resources