Reducing Array of Objects to a single output - javascript

Hi I am trying to figure out the best way to achieve something. I essentially receive a lot of data like so
[
{
name: 'email.delivered',
programme: 'Email One',
timestamp: 2022-03-24T18:06:02.000Z,
"payload":{
"id":"bcabca7c-a5d5-4e02-b247-2292240ffc77",
},
},
{
name: 'interaction.click',
programme: 'Email One',
timestamp: 2022-03-24T18:06:02.000Z,
"payload":{
"correlation":{
"channel":"email",
"messageId":"bcabca7c-a5d5-4e02-b247-2292240ffc77",
},
"metadata":{
"customerId":"9999999111",
"programName":"Email One"
}
},
},
...
]
The name is the event, can be delivered, failed, expired or click. The programme is what I want things categorised by. So I am doing some checks beforehand, and my code (removed a bit for simplicity) at the moment is like so
emails.forEach((record) => {
const data = JSON.parse(record.data);
if (data?.name === 'email.delivered') {
const id = data?.payload?.id;
if (!deliverDates[id]) deliverDates[id] = record.timestamp;
deliveredMap.set(programme, (deliveredMap.get(programme) || 0) + 1);
return;
}
if (data?.name === 'email.click') {
const id = data?.payload?.correlation?.messageId;
if (id) {
const deliveryDate = new Date(deliverDates[id]);
if (deliveryDate.getTime() > Date.now() - 1209600000) {
const programme = record?.programme;
clicksMap.set(programme, (clicksMap.get(programme) || 0) + 1);
}
}
}
});
The problem with the above is now I now have two Maps, when really I want just one Object returned with the programme as the key. For each programme I want to count all the different event types. So what I would like to see is something more like this
{
'Email One': {
delivered: 315,
failed: 18,
expired: 14,
click: 27,
},
'Email Two': {
delivered: 542,
failed: 322,
expired: 33,
click: 22,
}
...
}
How can I achieve this?
Thanks

Use a helper function to record the event occurence:
const eventCountsByProgramme = {};
function recordOccurence(programme, event) {
const eventCounts = (eventCountsByProgramme[programme] ??= {});
eventCounts[event] = (eventCounts[event] ?? 0) + 1;
}
Then use
recordOccurence(programme, 'delivered');
instead of
deliveredMap.set(programme, (deliveredMap.get(programme) || 0) + 1);
and
recordOccurence(programme, 'click');
instead of
clicksMap.set(programme, (clicksMap.get(programme) || 0) + 1);

Related

how to print all students name which have percentage more than 70% in javascript?

I am using json-rule-engine .
https://www.npmjs.com/package/json-rules-engine
I am having a student list which have name and their percentage, Also I have business rule the percentage should be greater thank or equal to than 70 . so I want to print all students name those have percentage more than 70
here is my code
https://repl.it/repls/AlienatedLostEntropy#index.js
student list
const students = [
{
name:"naveen",
percentage:70
},
{
name:"rajat",
percentage:50
},
{
name:"ravi",
percentage:75
},
{
name:"kaushal",
percentage:64
},
{
name:"piush",
percentage:89
}
]
rule
engine.addRule({
conditions: {
all: [
{
fact: "percentage",
operator: "greaterThanInclusive",
value: 70
}
]
},
onSuccess(){
console.log('on success called')
},
onFailure(){
console.log('on failure called')
},
event: {
type: "message",
params: {
data: "hello-world!"
}
}
});
code
https://repl.it/repls/AlienatedLostEntropy#index.js
any update
The json-rules-engine module takes data in a different format. In your Repl.it you have not defined any facts.
Facts should be:
let facts = [
{
name:"naveen",
percentage:70
},
[...]
Also, the module itself doesn't seem to process an array of facts. You have to adapt it to achieve this. This can be done with:
facts.forEach((fact) => {
engine
.run(fact)
[...]
Finally, the student data is found inside the almanac. You can get these values with: results.almanac.factMap.get('[name|percentage|age|school|etc]').value
Here is the updated Repl.it: https://repl.it/#adelriosantiago/json-rules-example
I might have submitted a completely unrelated answer, but here goes. Since the students object is an array, you could just loop through it and then use an if else statement.
for (let i = 0; i < students.length; i++) {
if (students[i].percentage >= 70) {
console.log(students[i].name);
}
}
Sorry if this is incorrect!
Here is a working example.
Counting success and failed cases
const { Engine } = require("json-rules-engine");
let engine = new Engine();
const students = [
{
name:"naveen",
percentage:70
},
{
name:"rajat",
percentage:50
},
{
name:"ravi",
percentage:75
},
{
name:"kaushal",
percentage:64
},
{
name:"piush",
percentage:89
}
]
engine.addRule({
conditions: {
all: [{
fact: 'percentage',
operator: 'greaterThanInclusive',
value: 70
}]
},
event: { type: 'procedure_result'}
})
let result = {success_count : 0 , failed_count : 0}
engine.on('success', () => result.success_count++)
.on('failure', () => result.failed_count++)
const getResults = function(){
return new Promise((resolve, reject) => {
students.forEach(fact => {
return engine.run(fact)
.then(() => resolve())
})
})
}
getResults().then(() => console.log(result));

Deleting JSON object based on attribute

I am working with React, I have got a variable named newtreeData, which looks like:
var newTreeData = {
name: submitted_title,
resource_link: submitted_resource_link,
details: submitted_details,
uuid: submitted_uuid,
website_image: submitted_website_img,
children: [
{
name: "Edit and save",
resource_link: "uh",
uuid: uuid.v4(),
details: "hi",
website_image:
"https://cdn3.iconfinder.com/data/icons/harmonicons-06/64/plus-circle-512.png",
children: [{...}, {}, ...]
},
{
name: "Edit and save",
resource_link: "uh",
uuid: uuid.v4(),
details: "hi",
website_image:
"https://cdn3.iconfinder.com/data/icons/harmonicons-06/64/plus-circle-512.png"
}
]
};
The line children: [{...}, {}] is just representing that newTreeData's children can have children which can have children...
Anyways, I wrote a method name findUUIDthenDelete which should do in pseudocode: if(object.uuid == toFindUUID) then delete object, and here's the full code for findUUIDthenDelete:
findUUIDthenDelete = (orig_data, to_delete_uuid) => {
var targetIsFound = false;
if (orig_data.uuid == to_delete_uuid) {
targetIsFound = true;
}
if (targetIsFound == false) {
if (orig_data.children === undefined) {
} else {
//if target not found, run recursion
orig_data.children.map(eachChildren =>
this.findUUIDthenDelete(eachChildren, to_delete_uuid)
);
}
} else {
console.log(orig_data, "this is the child ");
console.log(orig_data.parent, "is found, deleting its parent");
delete orig_data
}
};
As you can see this method is two parts: first I locate the object which has the uuid that we are trying to seek (potentially with some recursions), then delete the object. However, right now I am getting the "delete in local variable strict mode blah blah" error because of doing delete orig_data. Any insights to any workarounds to that error or some totally new way of tackling this? Also sincere apologies if there is an obvious solution I am out of mental energy and unable to think of anything algorithmic at the moment.
This should do it:
function findUUIDthenDelete(tree, uuid) {
if (!tree.children) return;
tree.children = tree.children.filter(c => c.uuid !== uuid);
tree.children.forEach(c => findUUIDthenDelete(c, uuid));
}
Should be pretty self-explanatory.
First, if the current node has no children, exit right away.
Next, potentially remove a child from the children array if the uuid matches using filter().
Finally, recursion.
Ok I'll admit this turned out to be more complicated than I thought, but the solution below will work if you can use Immutable. Essentially it walks your objects and collects the path to find the object that has the uuid and then once it has done that, it removes it.
const testMap = Immutable.fromJS({
uuid: 1,
children: [{
uuid: 2,
children: [{
uuid: 3,
children:[{
uuid: 8
}]
},
{
uuid: 4
},
{
uuid: 5
},
]
},
{
uuid: 7
}]
});
function findPath(checkMap, uuid, pathMap, currentIndex) {
if (checkMap.has('uuid') && checkMap.get('uuid') === uuid) {
const updatePathMap = pathMap.get('path').push(currentIndex);
return new Immutable.Map({
found: true,
path: pathMap.get('path').push(currentIndex)
});
} else {
if (checkMap.has('children') && checkMap.get('children').size > 0) {
for (let i = 0; i < checkMap.get('children').size; i++) {
const child = checkMap.get('children').get(i);
const checkChildPath = findPath(child, uuid, pathMap, i);
if (checkChildPath.get('found') === true) {
let updatePath = checkChildPath.get('path').push('children');
updatePath = updatePath.push(currentIndex);
return new Immutable.Map({
found: true,
path: updatePath
});
}
}
}
return pathMap;
}
}
const testPath = findPath(testMap, 7, new Immutable.Map({
found: false,
path: new Immutable.List()
}), 0);
console.info(testPath);
const testPath2 = findPath(testMap, 8, new Immutable.Map({
found: false,
path: new Immutable.List()
}), 0);
console.info(testPath2);
if (testPath2.get('found') === true) {
const path = testPath2.get('path');
if (path.size === 1 && path.get(0) === 0) {
// Your highlest level map has the uuid
} else {
const truePath = path.shift();
const cleanedUpMap = testMap.removeIn(truePath);
console.info(cleanedUpMap);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.js"></script>

JSON property not updating the value for new key on third function call

I apologise in advance for the title, I haven't found what I have been looking for while debugging but I may not know the correct terms to search for.
I have the following object:
const Game = {
user: {
tool: {
displayName: "shovel",
level: 0,
max: 1,
},
backpack: {
level: 0,
max: 10,
contents: {
ice: 5,
}
}
},
locations: {
lifePod: {
displayName: "Life Pod",
loseOxygen: false
},
icyPlain: {
displayName: "Ice Plain",
loseOxygen: true,
materials: {
type: "ice",
}
},
metalPlain: {
displayName: "Metal Plain",
loseOxygen: true,
materials: {
type: "metal",
}
}
}
};
I would like to use the following function to increase the count of the item in ice by 1. This works 100% of the time correctly, however when I try to use "metal" instead, it only allows a maximum of 2.
function mineResource(locationName) {
let newLocation = Game.locations[locationName];
if (Game.user.currentLocation != "lifePod" && newLocation.materials != undefined && backpackNotFull()) {
var alreadyInserted = false;
materialType = newLocation.materials.type; //ice
materialAmount = Game.user.tool.max; //1
let {backpack} = Game.user;
if (backpack.contents != null || backpack.contents != undefined) {
for (item in backpack.contents) {
if (item == materialType) {
Game.user.backpack.contents[materialType] += Game.user.tool.max;
alreadyInserted = true;
refreshValues();
} else if (alreadyInserted) {
null;
} else {
Game.user.backpack.contents[materialType] = materialAmount;
refreshValues();
}
};
};
};
}
I am confused by the fact that this function works fine with Ice but not Metal. As a test I changed:
contents: {
ice: 5,
}
to:
contents: {
ice: 5,
metal: 5,
}
And call Game.user.backpack.contents showed only {ice: 5} and calling contents.metal was undefined. I had definitely saved and refreshed. Unfortunately as I am a beginner I don't know what I don't know and it's hard to search for this. I have a put console logs underneath the "if (item == materialType) {" line and they were outputting but not increasing the counter.
If you want to see the whole code it's on http://oxygen.meddleso.me/main.js
EDIT: I did just remove Ice, and made metal the default with 5 and now Metal goes up by 1 normally, but if I add Ice like I was adding metal, Ice only goes up to 2 now.
When you add metal, but have ice, before adding metal you do
} else {
Game.user.backpack.contents[materialType] = materialAmount; // remove this line
refreshValues();
}
So when mining metal you do contents['metal'] = 1 (on ice loop) and later contents['metal'] += 1 (on metal loop)... Since you are looping over item set contents[item]. But I´m sure you would not want to set contents['ice'] = 1 either when mining metalPlain, so just remove this line.

Fastest way to see if object repeats in nested object

I have the following nested Object
const ERR_CODES = {
INVALID_CONSUMER_ID: {
code: 1000,
message: 'Invalid Consumer ID',
},
INVALID_MOBILE: {
code: 1001,
message: 'Invalid Mobile Number',
},
INVALID_ZIPCODE: {
code: 1002,
message: 'Invalid Zipcode',
},
INVALID_FIRST_NAME: {
code: 1000,
message: 'First Name',
},
}
I want to throw an error when two objects have the same code, like in the example consumer id and first name both have 1000 for error codes. What is the fastest way to go through my ERR_CODES obj and see if any code repeats?
You can use a map to keep track of the codes and compare with it, if the code is already part of the map, you could throw an error.
const ERR_CODES = {
INVALID_CONSUMER_ID: {
code: 1000,
message: 'Invalid Consumer ID',
},
INVALID_MOBILE: {
code: 1001,
message: 'Invalid Mobile Number',
},
INVALID_ZIPCODE: {
code: 1002,
message: 'Invalid Zipcode',
},
INVALID_FIRST_NAME: {
code: 1000,
message: 'First Name',
}
};
let keys = Object.keys(ERR_CODES);
let codesMap = {};
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let obj = ERR_CODES[key];
let code = obj.code;
if (!codesMap[code]) {
codesMap[code] = true
} else {
console.error('Duplicate');
}
}
Create an array of all of the codes present, then get all of the unique codes. If they are of equal length, there are no repeats. If the unique codes array is shorter, then there is a repeat.
const ERR_CODES = {
INVALID_CONSUMER_ID: {
code: 1000,
message: 'Invalid Consumer ID',
},
INVALID_MOBILE: {
code: 1001,
message: 'Invalid Mobile Number',
},
INVALID_ZIPCODE: {
code: 1002,
message: 'Invalid Zipcode',
},
INVALID_FIRST_NAME: {
code: 1000,
message: 'First Name',
},
};
const codes = Object.keys(ERR_CODES).map(err => ERR_CODES[err].code)
const uniq_codes = codes.reduce((p, c) => {
if (p.indexOf(c) < 0) p.push(c);
return p;
}, []);
console.log(codes.length == uniq_codes.length);
Since you only need to check if any code is repeated, I would choose a Set to store all of your codes:
let codes_num = Object.keys(ERR_CODES).map(elem => {
return ERR_CODES[elem].code;
});
let codes = new Set(codes_num);
Thanks to the properties of a Set, duplicated elements are just discarded. For this reason a simple check like this one:
Object.keys(ERR_CODES).length == codes.size
is going to tell you if a duplicated code has been found.
May not be the fastest way but a way to do that
const ERR_CODES = {
INVALID_CONSUMER_ID: {
code: 1000,
message: 'Invalid Consumer ID',
},
INVALID_MOBILE: {
code: 1001,
message: 'Invalid Mobile Number',
},
INVALID_ZIPCODE: {
code: 1002,
message: 'Invalid Zipcode',
},
INVALID_FIRST_NAME: {
code: 1000,
message: 'First Name',
},
};
function isValid(data) {
try{
Object.keys(data).reduce((obj, key) => {
if (data[key].code in obj) {
throw new Error(`Duplicate code: ${data[key].code}`);
} else {
obj[data[key].code] = 'found';
}
return obj;
}, {});
} catch(e) {
console.log(e.message);
return false;
}
return true;
}
console.log(isValid(ERR_CODES));
I will take the filter approach to check if the key exists more than one time.
With filter we can get a list of duplicate items by using 2 filters and checking the size of the the returned array. If the length is larger than 1, that means we found duplicates.
const ERR_CODES = {
INVALID_CONSUMER_ID: {
code: 1000,
message: 'Invalid Consumer ID',
},
INVALID_MOBILE: {
code: 1001,
message: 'Invalid Mobile Number',
},
INVALID_ZIPCODE: {
code: 1002,
message: 'Invalid Zipcode',
},
INVALID_FIRST_NAME: {
code: 1000,
message: 'First Name',
},
}
let codes = Object.values(ERR_CODES)
let result = codes.filter(item => codes.filter(i => i.code == item.code).length > 1)
console.log(result)
if(result.length > 0) {
// We have duplicates throw error here
console.error('Duplicate codes')
}
My take on the fastest solution would iterate over ERR_CODES only once and break once a duplicate is found:
const codes = new Set();
for (err in ERR_CODES) {
const {code} = ERR_CODES[err];
if (codes.has(code)) {
console.error(`Duplicate code: ${code}`);
break;
}
codes.add(code);
}

Angular - Only push to array if unique

I have an Angular application that collects values of items for an invoice, I want to make sure only unique items are being added to this collection but am having no luck.
I am pushing 3 pieces of information to this collection: id, price, and type. I want to make sure there is nothing in the collection currently matching those 3 points.
// My container
$scope.invoice = {
items: [{
}]
}
$scope.addPhoto = function() {
console.log('Withdrawing Photo: '+ $scope.item.id);
if ($scope.invoice.items.indexOf(item.id) != $scope.item.id)
{
$scope.invoice.items.push({
id: $scope.item.id,
price: $scope.item.price,
type: 'photo'
});
}
}
// Trying to avoid collections like this
invoice: {
items:
[ { } , {
id: 25
price: 0
type: photo
} , {
id: 25
price: 0
type: photo
} ]
}
.filter is pretty much what you need.
$scope.addPhoto = function() {
console.log('Withdrawing Photo: '+ $scope.item.id);
var matches = $scope.invoice.items.filter(function(datum) {
return datum.id === $scope.item.id &&
datum.price === $scope.item.price &&
datum.type === $scope.item.type;
});
if (!matches.length)
{
$scope.invoice.items.push({
id: $scope.item.id,
price: $scope.item.price,
type: 'photo'
});
}
}
Semi-contrived JSFiddle
This is the solution I came up with to solve my problem, hopefully it helps someone else.
$scope.addPhoto = function () {
console.log('Withdrawing Photo: ' + $scope.item.id);
var newItemId = $scope.item.id;
var newItemPrice = $scope.item.price;
var newItemType = 'photo';
var matches = true;
// Make sure user hasnt already added this item
angular.forEach($scope.invoice.items, function(item) {
if (newItemId === item.id && newItemPrice === item.price && newItemType === item.type) {
matches = false;
$scope.message = 'You have already selected to withdraw this item!';
}
});
// add item to collection
if (matches != false) {
$scope.invoice.items.push({
id: $scope.item.id,
price: $scope.item.price,
type: 'photo'
});
$scope.total += $scope.item.price;
$scope.message = 'Total Amount Selected';
}
};
YOu can simple pop opposite of push
array.splice(array.pop(item));

Categories