Aggregate values in a javascript grouped object based on conditions - javascript

I am using IE 11.
I have an object array that is grouped using the lodash library. I want to be able to query the object and based on certain conditions come up with sums/counts. So for example, I have this object array.
I would like to have the result seen below but in an object like the image above
As you can see, each company in the group should have certain values based on the following criteria
How many times does 'company x' have a Total Count >3?
How many times does 'company x' have expectingFunding eq ‘Yes’>
How many times does 'company x' have fundedOnIKNS eq ‘No’?
I've tried quite a bit in the last couple of days but not success. I first declared 2 arrays so I can capture the unique values of company name and program. I also created an object to update when conditions were met. The only successful thing I was able to get was to keep it in an grouped object. All the values in the new object were wrong.
Here's an excerpt of the code:
const companiesSummary = {};
for (const company of Object.keys(myData)) {
companiesSummary[company] = {
totalCount: 0,
expectedFunding: 0,
IKNSFunding: 0,
};
for (const { TotalCount, expectedFunding, fundedOnIKNS } of myData[company]) {
companiesSummary[company].totalCount += TotalCount;
companiesSummary[company].expectedFunding += expectedFunding === "Yes";
companiesSummary[company].fundedOnIKNS += fundedOnIKNS === "Yes";
}
}
I get the error,
TypeError: myData[company] is not iterable
Here's a link to the pen
I would still like the result to be in an object array, so I can create an html table later. Any help would be much appreciated. Thank you!

Your code isn't working because you're taking myData, an array, accessing myData[company], an object (company is 0, 1, ...), and you can't iterate through an object with for...of. myData is definitely not the same object in your screenshot.
Your code excerpt might work if your myData object were the object in your screenshot.

Related

Use multiple key-value filters on an object of objects?

Bit of a lengthy one so those of you who like a challenge (or I'm simply not knowledgeable enough - hopefully it's an easy solution!) read on!
(skip to the actual question part to skip the explanation and what I've tried)
Problem
I have a site that has a dataset that contains an object with multiple objects inside. Each of those objects contains an array, and within that array there are multiple objects. (yes this is painful but its from an API and I need to use this dataset without changing or modifying it.) I am trying to filter the dataset based of the key-value pairs in the final object. However, I have multiple filters being executed at once.
Example of Path before looping which retrieves the key-value pair needed for one hall.
["Hamilton Hall"]["Hire Options"][2].Commercial
After Looping Path of required key-value pair for all halls, not just one (the hall identifier is stored):
[0]["Hire Options"][2].Commercial
Looping allows me to check each hall for a specific key-value pair (kind of like map or forEach, but for an object).
After getting that out of the way back to the question.
How would I go about filtering which of the looped objects are displayed?
What I have Tried
(userInput is defined elsewhere - this happens on a btn click btw)
let results = Object.keys(halls);
for (key of results) {
let weekend = [halls[ `${key}` ][ 'Hire Options' ][4][ 'Weekend function' ]];
if(userInput == weekend) {
outputAll([halls[ `${key}` ]]);
}
}
That filters it fine. However, I run into an issue here. I want to filter by multiple queries, and naturally adding an AND into the if statement doesn't work. I also dont want to have 10 if statements (I have 10+ filters of various data types I need to sort by).
I have recently heard of ternary operators, but do not know enough about them to know if that is the correct thing to do? If so, how? Also had a brief loook at switches, but doesnt seem to look like what I want (correct me if I am wrong.)
Actual Question minus the babble/explanation
Is there a way for me to dynamically modify an if statements conditions? Such as adding or removing conditions of an if statement? Such as if the filter for 'a' is set to off, remove the AND condition for 'a' in the if statement? This would mean that the results would only filter with the active filters.
Any help, comments or 'why haven't you tried this' remark are greatly appreciated!
Thanks!
Just for extra reference, here is the code for retrieving each of the objects from the first object as it loops through them:
(Looping Code)
halls = data[ 'Halls' ];
let results = Object.keys(halls);
for (key of results) {
let arr = [halls[ `${key}` ]];
outputAll(arr);
}
You can use Array.filter on the keys array - you can structure the logic for a match how you like - just make it return true if a match is found and the element needs to be displayed.
let results = Object.keys(halls);
results.filter(key => {
if (userInput == halls[key]['Hire Options'][4]['Weekend function']) {
return true;
}
if (some other condition you want to match) {
return true;
}
return false;
}).forEach(key => outputAll([halls[key]]));

How does manipulating multiple checkbox selection code work?

I am following ag-grid's official tutorial:
https://www.ag-grid.com/angular-getting-started/?utm_source=ag-grid-readme&utm_medium=repository&utm_campaign=github
I reached the part where I have to manipulate the information regarding the selected checkboxes. However, the documentation is not detailed; It does not explain how the code actually works. Perhaps, this makes sense since it is not their job to explain in detail. However, for someone like me who doesn't have solid experience with working with angular technology and who wants to truly understand how things work, this is troublesome!
In the html file, I have been asked to add this:
<button (click)="getSelectedRows()">Get Selected Rows</button>
And in app.component.ts, I have been asked to add this:
getSelectedRows() {
const selectedNodes = this.agGrid.api.getSelectedNodes();
const selectedData = selectedNodes.map(node => node.data);
const selectedDataStringPresentation = selectedData.map( node => node.make + ' ' + node.model).join(', ');
alert('Selected nodes: ${selectedDataStringPresentation}');
}
If someone could explain what the typescript code is doing exactly, that would be very generous.
Thank you!
I guess agGrid is the service storing your mock values, this simply gets an array of data from somwhere.
selectedData is another array that is created by transforming (transforms the array while providing a new reference, thus not modifying the original array) the selectedNodes array and only selecting the data property of each node.
selectedDataStringPresentation is the same, but this time it provides an array of formatted strings by merging the properties make and model of each object from selectedData.
What you probably fail to grasp is the usage of the ES6 (JavaScript standard) functions that are used here, and especially the map() function.
Here is the official documentation of the map() function for arrays : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
Simply explained, it is a function that iterates over an array, and transforming each object by applying the function declared in the map, returning the results in a new array.
If you take the example of selectedData, you can translate the operation into this :
Loop over each object of selectedNodes, and return only the property data of the current object. Push the results in a new array.
This is especially useful when you want to work on different arrays that serve different purposes. For example, imagine you have a service that contains a list of objects. A backend service will provide you an array of numbers representing the IDs of the objects you have in your service.
You can then use the map() function to transform the array of IDs into an array of your Objects stored in your service easily.
Darn #Alex Beugnet(upvote) beat me to the punch! I'm going to post anyway as I was in the middle of writing it all.
Firstly I'm not sure how much of TypeScript you already know, I apologize if much of these becomes trivial, the purpose is only to ensure maximum clarification to the question in understanding the logic.
In the Enable Selection portion of the guide, you are essentially enabling multiple row selection in the grid and having the button return the data from those selected rows.
In order to see what's happening with the getMultipleRows() function, it would be best to visualize it via the Debugger provided in browsers, I'm using Chrome Developer Tools (hit F12), I would highly recommend it for understanding what is happening in the logic.
const selectedNodes
Let's start by selecting say 2 rows, I have selected the Porsche Boxster 72000 and Ford Mondeo 32000. After selecting them I click on the 'Get Selected Rows' button which triggers the getSelectedRows() function:
const selectedNodes = this.agGrid.api.getSelectedNodes();
The above line is assigning the constant variable 'selectedNodes' the RowNodes from AgGrid. Here you are using the AgGridNg2 method getSelectedNodes() to return the selected node data, which you would be returned an array in the form of:
[RowNode, RowNode] //(each for the row we have selected)
Looking into a RowNode we get:
These are all the RowNode properties provided by the AgGrid framework, you can ignore all of these object properties as you are only concerned with the 'data' property as you'll see in the next line of code!
const SelectedData
const selectedData = selectedNodes.map(node => node.data);
Here we are setting 'selectedData' as an array of RowNode.data, basically trying to get the data property from the RowNodes into an array.
The above line can basically be assumed as:
let selectedData = [];
for (let i = 0; i <= selectedNodes.length - 1; i++){
selectedData[i] = selectedNodes[i].data;
}
In which we are just trying to get the data property into a new constant variable 'selectedData'. Look at the documentation in order to better understand this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
const selectedData would be returned as:
[
{
make: "Porsche",
model: "Boxster",
price: 72000,
},
{
make: "Ford",
model: "Mondeo",
price: 32000
}
]
const selectedDataStringPresentation
const selectedDataStringPresentation = selectedData.map( node => node.make + ' ' + node.model).join(', ');
We take the selectedData array and concatenate the Make and Model as a single element for the array and adding a comma in the end. We would get "Porsche Boxter, Ford Mondeo".
let selectedData = [
{
make: "Porsche",
model: "Boxster",
price: 72000,
},
{
make: "Ford",
model: "Mondeo",
price: 32000
}
]
let selectedDataStringPresentation = [];
for (let i = 0; i <= selectedData.length - 1; i++){
selectedDataStringPresentation[i] = selectedData[i].make + ' ' + selectedData[i].model;
}
selectedDataStringPresentation = selectedDataStringPresentation.join(', ');
console.log(selectedDataStringPresentation);
alert()
And the last line,
alert('Selected nodes: ${selectedDataStringPresentation}');
You are going to send an alert in the browser that will display the selectedDataStringPresentation array.

Inserting values into multi-dimentional javascript array

I need to dynamically create a multidimensional javascript array that matches this layout:
array_answers[0][1]:"yes"
array_answers[1][2]:"no"
array_answers[2][2-subquestion]:"text input"
array_answers[3][8]:"yes"
array_answers[4][8-subquestion]:"text input"
The first "[ ]" defines what question it is on the page (out of totalInputs)
The second "[ ]" defines what question from the database this is (questions already in order to match the corresponding input)
and the information following is the input I am trying to add
I have attempted to the following with no luck.
for(var i = 0; i < totalInputs; i++) {
array_answers.push([i]);
array_answers[i].push([questions[i]]);
array_answers[i][0] = "yes, no, or other text";
}
The last line is where it falls apart. It would make sense to me that I should be able to use [0] to indicate that I want the first array to be given this value but with no avail.
I have also tried:
for(var i = 0; i < totalInputs; i++) {
array_answers.push([i]);
array_answers[i].push([questions[i]]);
array_answers[i][questions[i]] = "yes, no, or other text";
}
but this gives me lots of empty arrays for all the numbers from 0 to whatever the value of questions[i] is.
What am I missing or is there a simpler way to do this in jQuery while still conforming to the target layout?
If I understand you correctly, you are trying to store questions and prompts (maybe?) together in a multi dimensional array. let me suggest a different way that should work.
const array_answers = questions.map(q => [q, "yes, no, other text"]);
This may be what you want
I am still not 100% sure of what you need, but i thought I would write an answer with my assumptions, that I can update as the information improves.
The first "[ ]" defines what question it is on the page (out of
totalInputs)
This part looks like you are correct, and need to use an array. But an array is just a list of "things", in your case, questions. So where you have this line:
array_answers.push([i]);
I'm not sure it is doing what you are expecting. This is adding a new array item, which is in itself an array, that contains a single number. So if totalInputs is 3, then that first line will result in this structure:
array_answers= [[0],[1],[2]]
I think what you actually might intend here is to simply house an array of question details. Now by the complexity of your keys listed for the second dimension, it looks like an object would be more appropriate.
The second "[ ]" defines what question from the database this is
(questions already in order to match the corresponding input)
So lets go ahead and create a single question.
var question = {
anythingYouWant: 'here',
2: 'even numeric keys'
}
// you can access the values in these ways
console.log(question.anythingYouWant)
console.log(question[2])
console.log(question['anythingYouWant'])
Now once you have a question object, you can then add it to your array_answers array with push.
array_answers.push(question).
If you have two identical questions like the one above, your array will look like this:
array_answers = [{
anythingYouWant: 'here',
2: 'even numeric keys'
},{
anythingYouWant: 'here',
2: 'even numeric keys'
}]
In order to access the questions within the array, you can simply use their index:
// access second question
var secondQuestion = array_answers[1]
You can read these links to learn more about objects & arrays

Can't figure out MYSQL query to get the result I want

I have a table called subcategories with columns 'id' and 'name' and a table called goals with columns 'id', 'name' and foreign key 'subcategory_id'.
I want a query that results in an array of subcategory objects, which has a property 'goals' which is an array of goal objects.
Too give an example of how the result would look In JS code:
result = [
{id: 1, name: "name", goals: [{id: 1, name: "goalName"}, {...}, {...}]},
{...},
{...}
]
But (with a different syntax) the result would be the same for other languages..
Thusfar I tried to do this with left-join, like this:
SELECT sc.ID as subcatId, sc.name as subcatName, g.ID as ID, g.name as name
FROM needs_subcategories as sc
LEFT JOIN needs_goals as g
ON sc.ID=g.subcategory_id
But the goals aren't grouped under a single subcategory.. I feel like it should be possible to do with a query, but I can't figure out/google how to do it because I wouldn't know how to phrase the question due to my lack of SQL knowledge..
Hope you guys can help me!
Thanks in advance.
You won't be able to acheive that with a query. MySQL can't do that.
You are currently fetching all goals, each one with their subcategory (subcategories will repeat).
You can convert it to the desired array with some code (example in php, you can translate this to any other language).
$result=array();
$lastSubcatId=null;
$goals=array();
while($row=$query->fetch_object()) { //assuming $query is the resultset
if($lastSubcatId&&$lastSubcatId!=$row->subcatId) {
$row->goals=$goals;
$result[]=$row; //or you could assign each desired property
$goals=array();
}
$goals[]=$row; //or you could assign each desired property
}
//surely, there are items left in $goals
if($lastSubcatId) {
$row->goals=$goals;
$result[]=$row; //or you could assign each desired property
}
But a more efficient way would be, I think, with multiple queries:
$result=array();
$subcats=$db->query("SELECT * FROM needs_subcategories");
while($subcat=$subcats->fetch_object()) {
//you might want to use prepared statements, I'm just simplifying
//it will not only be safer, but reusing the prepared statement will increase the performance considerably
$goals=$db->query("select * from needs_goals where subcategory_id=".$subcat->ID);
$temp=array();
while($goal=$goals->fetch_object()) $temp[]=$goal;
$subcat->goals=$temp;
$result[]=$subcat;
}
In the end I solved this using groupBy as #tadman suggested in his comment.
I created a function (based on the information in this answer) that looks like this:
function processResults(collection, groupKey) {
var result = _.chain(collection)
.groupBy(groupKey)
.toPairs()
.map(function (currentItem) {
// 'text' and 'children' are the keys I want in my resulting object
// children being the property that contains the array of goal objects
return _.zipObject(['text', 'children'], currentItem);
})
.value();
return result;
}
Which results in the array of objects with grouped goals! As I structured the function now (with hard-coded key names) it only works for my specific case, if you want to generalize the function you could add parameters amd replace the hard-coded key names with those.

meteor minimongo getting inconsistent collection.findOne() results

I've been trying to debug a chunk of code for some hours now, banging my head against the wall, and finally pinpointed my issues to a place in the code where assigning the results of a collection.findOne() call to a variable is giving me different data than what I see with a console.log() of the same findOne() on the previous line.
prePostState = function(thisStID) {
console.log(Students.findOne({_id:thisStID}));
var stTemp = Students.findOne({_id:thisStID});
console.log(stTmp);
var testsTemp = stTmp.tests;
The collection object has a 'tests' array. In this instance, the array contains 3 objects as its elements.
While both the console.log() lines return something like this
Object {_id: "eXf9dqQbaemKS24Ti", name: "Student,Name", group: "none", site: "SiteName", tests: Array[3]}
Expanding each shows different data. The first one shows the correct tests: Array[3], the second one shows tests: Array[1], and the single element in that array also has data that is different from the matching element in the full array.
----Update----
Doing some further testing, I've changed the code a bit.
prePostState = function(thisStID) {
console.log(Students.find({_id:thisStID}).fetch()); //1
var stTmp = Students.find({_id:thisStID}).fetch();
console.log(stTmp); //2
console.log(stTmp[0].tests.length); //3
for(var i = 0; i < stTmp[0].tests.length; i++) {
console.log(stTmp[0].tests[i]); //4
}
1 Returns:
[Object]
0: Object
_id: "AqLHB8hT8GxzQ7zyD"
group: "none"
name: "Student,Name"
site: "SiteName"
tests: Array[3]
2 Returns:
[Object]
0: Object
_id: "AqLHB8hT8GxzQ7zyD"
group: "none"
name: "Student,Name"
site: "SiteName"
tests: Array[1]
3 Returns:
3
The for loop at 4 repeats three times and prints out each of the three objects in the tests array.
Obviously this means I can access the data I need. Instead of
var testArray = stTmp.tests;
Which leaves me with an array with only a single element, I will just have to get the length of stTmp.tests, and then use a for loop to access each element by index and insert them into the testArray variable.
So I can continue on, but I still don't understand the behavior I'm seeing. I'm on a bit of a timeline to keep making progress at this point, but when I have some time I may revisit this and try and replicate it in a meteorpad or other form that I can share the full code with.
1) If you modify a return value from Minimongo, don't expect it to persist. Minimongo was specifically written this way, so you are forced to use update operators to update the values.
2) The correct projection API is Coll.find({..selector..}, {fields:{..projection..}})

Categories