format json data in javascript like a pivot table - javascript

I have the the following data being returned by my api.
[{"category":"Amazon","month":"Feb","total":9.75},
{"category":"Amazon","month":"Mar","total":169.44},
{"category":"Amazon","month":"Apr","total":10.69},
{"category":"Amazon","month":"May","total":867.0600000000001},
{"category":"Amazon","month":"Jun","total":394.43999999999994},
{"category":"Amazon","month":"Jul","total":787.2400000000001},
{"category":"Amazon","month":"Aug","total":1112.4400000000003},
{"category":"Amazon","month":"Sep","total":232.86999999999998},
{"category":"Amazon","month":"Oct","total":222.26999999999998},
{"category":"Amazon","month":"Nov","total":306.09999999999997},
{"category":"Amazon","month":"Dec","total":1096.2599999999998}]
I want to format it so that the months are all grouped under each category like this:
[{"category":"Amazon","month":{"Jan":9.75,"Feb":9.75,"Mar":9.75,"Apr":9.75,etc...}]
How can I do this with javascript?
What I'm ultimately trying to do is to display some pivoted data in a table. I'm not sure what the best design is to accomplish this.
Right now, I'm just setting up a table dynamically and adding in the data corresponding to each row. Are there better design patterns for doing this?

You can reduce the array of objects to an object using the categories as keys, and adding the months, and then map it back to an array again
var arr = [{"category":"Amazon","month":"Feb","total":9.75},
{"category":"Amazon","month":"Mar","total":169.44},
{"category":"Amazon","month":"Apr","total":10.69},
{"category":"Amazon","month":"May","total":867.0600000000001},
{"category":"Amazon","month":"Jun","total":394.43999999999994},
{"category":"Amazon","month":"Jul","total":787.2400000000001},
{"category":"Amazon","month":"Aug","total":1112.4400000000003},
{"category":"Amazon","month":"Sep","total":232.86999999999998},
{"category":"Amazon","month":"Oct","total":222.26999999999998},
{"category":"Amazon","month":"Nov","total":306.09999999999997},
{"category":"Amazon","month":"Dec","total":1096.2599999999998}];
var o = arr.reduce( (a,b) => {
a[b.category] = a[b.category] || [];
a[b.category].push({[b.month]:b.total});
return a;
}, {});
var a = Object.keys(o).map(function(k) {
return {category : k, month : Object.assign.apply({},o[k])};
});
console.log(a);

I would take the following approach:
Write down on a piece of paper how to solve the problem (the "algorithm").
Flesh out this algorithm with more details. Sometimes this is called "pseudo-code".
Convert the pseudo-code into JavaScript.
For instance, your algorithm might look like this:
Create a new thing to hold the results.
Loop through the elements in the input.
For each element in the input, update the results thing.
Return the result.
Sometimes it helps to read out the algorithm aloud to yourself. Or animate the algorithm on a blackboard. Or talk through the algorithm with someone nearby.
The pseudo-code might be:
Create a new array containing a new object to hold the results, with a category property set to "Amazon", and a months property set to an empty object.
Loop through the elements in the input array.
For each element, add a new property to the months property of the results object, whose key is the value of the month property from the element, and whose value is the value of the total property from the element.
Return the result.
If you have specific questions about any of those steps, you can research it further, or ask, such as:
How do I create a new array, with an object inside?
How do I loop through the elements of an array?
How do I retrieve a property from an object?
How do I add a new key/value pair to an object?
How do I return the result?
If you are unfamiliar with any of the terms used above--such as array, object, property, key, value, or element--research them and make sure you know what they mean. Knowing and using correct terminology is the first step to successful programming!
When converting your algorithm into JS, write it step by step, and test it at each phase. For instance, start with a version which doesn't loop over the input at all, and make sure it produces a correct output of [{category: "Amazon", month: {}}]. Walk though your code in the debugger at this and each following step--if you don't know how to use the debugger, learning that should be your first priority! If you want to check a little bit of syntax, to make sure it does what you think, just try it out by typing it into the console. If you don't know what the console is, learning that should be another top priority.
All the above assumes that you've got a single Amazon category. If you are going to have multiple categories, and want multiple objects (one for each) in your output array, then start over from the top and write the algorithm and pseudo-code which can handle that.

Related

Is there a way to have a percentage of uniqueness in an array of objects?

this is the first time I write in this site.
So I need to generate a set of random data with a function that returns an object.
This object picks some properties (on really nested levels) randomly from other arrays of objects. So the function returns the same object in structure, but different values in its properties.
Is there a way to calculate a uniqueness ratio or something like that? Like if there's one generated object exactly equal to other in the set, it will return a uniqueness of 0, if there are no shared properties with any other, return a 100, and if some are shared, and others not, some percentage in between?
My goal with this is to generate a set of 100 for example and pick the top 20 most unique generated objects.
Thanks in advance for your ideas.
EDIT:
Let's assume I already generated the set of data.
All objects have the same structure but different values.
Something like this:
{
name: 'Some Name',
propA: (picked randomly from set A),
propB: (picked randomly from a different set B),
sections: [
{
propC: (another random from another set C)
},
{...},
...
]
}
I spawned an array of these objects with some utilities I wrote with ramda, like pick random from a list, and R.times to do it.
The main issue is that I need this:
{
...generatedObject,
uniqueness: 79
}
On each object, the uniqueness is a percentage.
So far I used deep-diff To get a difference between to objects and wrote a function to extract a percentage based on the number of props that were changed in the object.
This is that fn:
// changes is a Number
const measureUniquenessBetweenTwoChildObjects = R.curry((changes, objA, objB) =>
R.compose(
R.multiply(100),
R.divide(R.__, changes),
R.length,
diff)(objA, objB)
);
What this does is that if there is the same changes as there are generated props, then the difference is 100%.
Then I did pick every object in the list, and map this function with every other object except itself, reduce that array of differences with an average and that's what I thought the final number is. Then I attached that number to the object with R.assoc.
Inspecting the array of percentage differences gives me something like this:
[
73.02, 73.02, 72.79, 72.56,
72.56, 72.34, 72.34, 72.11,
71.66, 71.66, 71.2, 70.98,
70.98, 70.98, 70.75, 70.52,
70.29, 70.29, 70.07, 69.84
]
Each of these are the uniqueness ratio I attach to the objects.
However I think my solution is flawed, I sense something is odd here. This was my logic to solve this problem.
What I am asking you is how would you solve this? In the end the issue is to write an algorithm that calculates a uniqueness value of each object within a set of objects of the same structure, but different values.
I'm not asking for code, just some ideas to make this work in a proper way. I'm not a data scientist or a mathematician, so I went with my naive way of achieving this.
Hope this makes it more clear.
Thanks.
Several other questions demonstrate that if you're looking for an optimized solution, this question is NP-hard. I don't know if there's any algorithm better than brute force for that. And that is out of the question as (100 choose 20) is rather large (535983370403809682970) -- unless you have some hardware I'd really like to know about!
But I think you can find a local optimum which likely wouldn't be a bad guess. That would involve
Calculating the difference matrix
Sum the rows
Choose the maximum value
Add that value to your list
If you still need more items, remove that index from the row and column and go back to step 2.
Or of course you could use some simulated annealing technique to find an even better local maximum.
As to the differences, as I suggested in a comment, deep-diff might be more than you need. You might be able to use a function like this:
const findLeafPaths = (o, path = [[]]) =>
typeof o == 'object'
? Object .entries (o) .flatMap (
([k, v]) => findLeafPaths (v, path) .map (p => [k, ...p])
)
: path
to find all the paths in a sample object, and then for each object, reduce it to an array of values by mapping R.path on these. To find a numerical difference between them should be fairly simple (I'd start with R.zipWith (R.equals) or some such.) But if deep-diff is working well for you, there's no reason to change; it simply is testing for things that, as I understand your requirements, are not going to be there.

Return Array of Data using Google Assistant from Firebase

The structure I have for my firebase database is like this:
fruits:
apple,5
banana,6
I want to put apple and banana in an array so that when i give a command to Google Assistant, it would give me apple, 5 and banana, 6. The code I have is like the one below:
function handleCommand(agent) {
return admin.database().ref('Fruits').child().once("value").then((snapshot) =>{
var i;
var fruitlist=[];
//puts each snapshot child of 'Fruit' in an array
snapshot.forEach(function(item) {
var itemVal = item.val();
fruitlist.push(itemVal);
});
//outputs command in google assistant
for (i=0; i < fruitlist.length; i++) {
agent.add(fruitlist[i]);
}
})
The default response is "not available".
I get the following in the execution logs:
Firebase.child failed. Was called 0 aruguments. expects at least 1.
I do not know which argument to put inside the Firebase.child. if i want all fruits to be "spoken" by Google Assistant. Below is a picture of my firebase structure.
The error looks like the one below:
What I am currently doing now to just output the fruits are manually entering each child in the code like this and removed the ".child" in the return statement:
Which gives me the output below which is also what I want to see but using arrays as the solution I am using now is very much hardcoded:
As the error message suggests, and as you surmise, the child() call expects a parameter - in particular, the name of the child node you want to get information from. However, since you want all the children of the "Fruits" node - you don't need to specify it at all. The child() call just navigates down through the hierarchy, but you don't need to navigate at all if you don't want to.
The snapshot you get back will have a value of the entire object. In some cases, this can be pretty large, so it isn't a good idea to get it all at once. In your case, it is fairly small, so not as big a deal.
On the JavaScript side, you can now handle that value as an object with attributes and values. Your original code didn't quite do what you said you want it to, however - you're getting the value, but ignoring the name (which is the attribute name or key). You can iterate over the attributes of an object in a number of ways, but I like getting the keys of the object, looping over this, getting the value associated with the key, and then "doing something" with it.
While I haven't tested the code, it might look something like this:
function handleCommand(agent) {
return admin.database().ref('Fruits').once("value").then((snapshot) =>{
// Get an object with all the fruits and values
var fruits = snapshot.val();
// Get the keys for the attributes of this object as an array
var keys = Object.keys( fruits );
// Iterate over the keys, get the associated value, and do something with it
for( var i=0; i<keys.length; i++ ){
var key = keys[i];
var val = fruits[key];
agent.add( `The number of ${key} you have are: ${val}` );
}
})
While this is (or should be) working Firebase and JavaScript, there are a couple of problems with this on the Actions on Google side.
First, the message returned might have some grammar problems, so using your example, you may see a message such as "The number of Apple you have are: 1". There are ways to resolve this, but keep in mind my sample code is just a starter sample.
More significantly, however, the call to agent.add() with a string creates a "SimpleResponse". You're only allowed two simple responses per reply in an Action. So while this will work for your example, it will have problems if you have more fruit. You can solve this by concatenating the strings together so you're only calling agent.add() once.
Finally, you may wish to actually look at some of the other response options for different surfaces. So while you might read out this list on a speaker, you may read a shorter list on a device with a screen and show a table with the information. Details about these might be better addressed as a new StackOverflow question, however.

update, instead of replace, list used for ng-repeat

How it is
I have an array of objects called vm.queued_messages (vm is set to this in my controller), and vm.queued_messages is used in ng-repeat to display a list of div's.
When I make an API call which changes the underlying model in the database, I have the API call return a fresh list of queued messages, and in my controller I set the variable vm.queued_messages to that new value, that fresh list of queued messages.
vm.queued_messages = data; // data is the full list of new message objects
The problem
This "full replacement" of vm.queued_messages worked exactly as I wanted, at first. But what I didn't think about was the fact that even objects in that list which had no changes to any properties were leaving and new objects were taking their place. This made no different to the display because the new objects had identical keys and values, they were technically different objects, and thus the div's were secretly leaving and entering every time. THIS MEANS THERE ARE MANY UNWANTED .ng-enter's AND .ng-leave's OCCURRING, which came to my attention when I tried to apply an animation to these div's when they entered or left. I would expect a single div to do the .ng-leave animation on some click, but suddenly a bunch of them did!
My solution attempt
I made a function softRefreshObjectList which updates the keys and values (as well as any entirely new objects, or now absent objects) of an existing list to match those of a new list, WITHOUT REPLACING THE OBJECTS, AS TO MAINTAIN THEIR IDENTITY. I matched objects between the new list and old list by their _id field.
softRefreshObjectList: function(oldObjs, newObjs) {
var resultingObjList = [];
var oldObjsIdMap = {};
_.each(oldObjs, function(obj) {
oldObjsIdMap[obj._id] = obj;
});
_.each(newObjs, function(newObj) {
var correspondingOldObj = oldObjsIdMap[newObj._id];
if (correspondingOldObj) {
// clear out the old obj and put in the keys/values from the new obj
for (var key in correspondingOldObj) delete correspondingOldObj[key];
for (var key in newObj) correspondingOldObj[key] = newObj[key];
resultingObjList.push(correspondingOldObj);
} else {
resultingObjList.push(newObj);
};
});
return resultingObjList;
}
which works for certain things, but with other ng-repeat lists I get odd behavior, I believe because of the delete's and values of the objects being references to other controller variables. Before continuing down this rabbit hole, I want to make this post in case I'm thinking about this wrong, or there's something I'm missing.
My question
Is there a more appropriate way to handle this case, which would either make it easier to handle, or bypass my issue altogether?
Perhaps a way to signal to Angular that these objects are identified by their _id instead of their reference, so that it doesn't make them leave and enter as long as the _id doesn't change.
Or perhaps a better softRefreshObjectList function which iterates through the objects differently, if there's something fishy about how I'm doing it.
Thanks to Petr's comment, I now know about track by for ng-repeat. It's where you can specify a field in your elements that "identifies" that element, so that angular can know when that element really is leaving or entering. In my case, that field was _id, and adding track by message._id to my ng-repeat (ng-repeat="message in ctrl.queued_messages track by message._id") solved my issue perfectly.
Docs here. Search for track by.

Use _.filter on object created by _.indexBy

I started off with an array of objects and used _.filter to filter down on some search criteria and _.findWhere to select out asingle object based on ID.
Unfortunately the amount of data has increased so much so that it's much more efficient to use _.indexBy to index by ID so I can just do data[ID] = id for the _.findWhere's.
However I am stumped on how to replace the _.filter method without looping through all the keys in data.
Is there a better way?!
Edit
The IDs are always unique.
I can't show any real data as it is sensitive but the structure is
data = {
1: {id: 1, name: 'data1', date: 20/1/2016}
2: {id: 2, name: 'data2', date: 21/1/2016},
3: {....
}
and I need to something like:
var recentData = _.filter(data, function(d){d.date > 1/1/2016});
To get an array of data or ids.
(n.b. the dates are all in epoch times)
This is really an optimization question, rather than simply which function to use.
One thing to go about this would be if we could rely on sort order of the whole collection. If it's already sorted, you go with something like binary search to find the border elements of your date range and then splice everything from this point. (side note: array would probably work better for this).
If the array is not sorted you could also consider sorting it first on your end - but that makes sense only if you need to retrieve such information several times from the same dataset.
But if all you got is just the data, unsorted and you need to pick all elements starting from a certain date - no other way that iterate through it all with something like _.filter().
Alternatively you could revert back to the source of your data and check whether you can improve the results that way - if you're using some kind of API, maybe there are extra params for sorting or narrowing down the date selection (generally speaking database engines are really efficient at what you're trying to do)? Or if you're using a huge static JSON as the source - maybe consider improving that source object with sort order?
Just some ideas. Hard to give you the best resolution without knowing all the story behind what you're trying to do.

JSON Weirdness Needs More Elegant Approach

Basically, I'm working on a page that includes four different JSON "thingies" (objetcs,arrays). Forgive my lack of proper terminology.
When I get the JSON, it comes in as an object with a bunch of sub-objects, and each "sub-object" looks like this:
"token":"government",
"title":"Government",
"isSelected":false,
"type":"CATEGORY",
"subtype":"INDUSTRY",
"count":12
So the first task is to loop through each JSON and populate a box full of checkboxes, using the title as the label and the isSelected to indicate the checked status. So far, so good.
BTW, somewhere aslong the way, I picked up a JS script that checks whether an object is JSON or an array, and according to that "quick & dirty" test, my object is an array. Or an array object (you know, the one is created with [ ] and the other with { })?
Anyway, when the end user checks and un-checks checkboxes, I need to keep track of it all and immediately send back changes to the server (when the user clicks a DONE button). The crazy thing is that by looping through the objects, I was able to change the isSelected value to true . . . just not back to false.
for(var i = 0; i < $array.length; i++){
$array[z].isSelected = true;
}
Perhaps I was up too late when I worked on all of this, but using the same approach, I could not change $array[z].isSelected to false when the checkbox got de-selected.
In the end, I converted the JSON "thingy" to a string, search and replaced the corresponding values, and then converted the string back into an object. This is all working now, but I feel as though I've just used up a roll of duct tape on something that could have been put together by snapping the pieces together nicely.
Question: Did I miss the boat totally and is there a simple way to change values of JSON objects?
If so, could you point me in the right direction?
That JSON thingy is just a string representation of a javascript object.
One way of creating an object is
var myObject = {
"myName": "AName",
"myType": "AType"
};
This object can be referenced as myObject, with the properties myObject.myName and myObject.myType containing values AName and AType.
You should be able to just reference the object by name as objName.token objName.title etc.
If you have trouble try parsing the json with javascript then reference the result as above. This should make it easier for you to access, manipulate or delete data in the objects properties as well.
The nesting of these as below can be referenced as myObject.moreProperties.prop1 etc
var myObject = {
"myName": "AName",
"myType": "AType",
"moreProperties": {
"prop1": "vaue1",
"prop2": "vaue2",
}
};

Categories