I've spent all morning messing with this now and reading on here, but have found myself going round in circles!
I am trying to draw a chart using the excellent AmCharts Javascript Charts, to show me stock holding as a bar chart and stock turn as a line chart.
I cannot get both sets of data from one query to my database, and cannot use AmCharts StockChart as it is not time based data... therefore, I have two sets of data which need combining with Javascript.
The data is being pulled from a database and returned successfully as JSON arrays similar to this:
SALES DATA:
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}]
STOCK DATA:
[{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}]
Obviously the actual figures are made up in that example!
Now, what I need to do is to combine those to create this:
COMBINED DATA
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55","stockValue":"975"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43","stockValue":"1234"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13","stockValue":"834"}]
What we have there is the Sales Dataset combined with Stock Dataset to add the additional data of stockValue added to the corresponding brandName record.
I have tried using $.extend but I can't figure out how to use it in this situation.
It is perhaps important to note that the data pairs might not necessarily be in the right order, and it is possible, though unlikely, that there might not be a match, so some kind of zeroing error catching must be implemented.
What you'll need to do first is transform the data into two objects, whose properties are the values you want to merge together:
{
"Fender" : {"gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
"Gibson" : {"gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
"Epiphone" : {"gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}
}
and
{
"Gibson": {"stockValue":"1234"},
"Fender": { "stockValue":"975"},
"Epiphone": { "stockValue":"834"}
}
Once the transformation is done, you'll have two objects that you can merge using $.extend or other functions.
Update
For large sets, this gives results in nearly linear time:
var salesa = {}, stocka = {};
$.each(sales, function(i, e) {
salesa[e.brandName] = e;
});
$.each(stock, function(i, e) {
stocka[e.brandName] = e;
});
var combine = {};
$.extend(true, combine, salesa, stocka)
More speed can be tweaked if the merging happened during the second transformation callback ($each(stock...) instead of a separate call to $.extend() but it loses some of its obviousness.
I think what's he's trying to do is join the two datasets as if they were tables, joining by the brandName. From what I've been testing jQuery's $.extend() function does not take care of that, but merges objects according to their index in the Object arrays that it receives.
I think the matching of the key would need to be done manually.
stock = [{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}];
value = [{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}];
var results = [];
$(stock).each(function(){
datum1 = this;
$(value).each(function() {
datum2 = this;
if(datum1.brandName == datum2.brandName)
results.push($.extend({}, datum1, datum2));
});
});
Which would result in:
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55","stockValue":"975"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43","stockValue":"1234"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13","stockValue":"834"}]
Instead of what the use of $.extend() returns:
[{"brandName":"Gibson","gearShiftedPerMonth":"35","retailSalesPerMonth":"55","stockValue":"1234"},
{"brandName":"Fender","gearShiftedPerMonth":"23","retailSalesPerMonth":"43","stockValue":"975"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13","stockValue":"834"}]
If your example code reflects reality, then jQuery's $.extend will be the wrong tool for this.
It blindly copies data from one object to another. Notice that the order of your data is not consistent. The SALES DATA has Fender first, while the STOCK DATA has gibson first.
So jQuery's $.extend is mixing the two results. The "gearShifted" and "retailSales" for Fender is ending up with the "brandName" and "stockValue" for Gibson.
What you'll need is to iterate one array, and look up the "brandName" in the other, and then copy over the data you want. You could use $.extend for that part of it if you like...
var sales_data =
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}]
var stock_data =
[{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}]
var combined = $.map(sales_data, function(obj, i) {
return $.extend({}, obj, $.grep(stock_data, function(stock_obj) {
return obj.brandName === stock_obj.brandName
})[0]);
});
Note that this is not terribly efficient, but unless the data set is enormous, it shouldn't be an issue.
DEMO: http://jsfiddle.net/sDyKx/
RESULT:
[
{
"brandName": "Fender",
"gearShiftedPerMonth": "35",
"retailSalesPerMonth": "55",
"stockValue": "975"
},
{
"brandName": "Gibson",
"gearShiftedPerMonth": "23",
"retailSalesPerMonth": "43",
"stockValue": "1234"
},
{
"brandName": "Epiphone",
"gearShiftedPerMonth": "10",
"retailSalesPerMonth": "13",
"stockValue": "834"
}
]
In vanilla javascript you can do:
var sales = [{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}];
var stock = [{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}];
var combined = stock.slice(0);
for (var i = 0; i < stock.length; i++) {
for (var j = 0; j < sales.length; j++) {
if (stock[i].brandName === sales[j].brandName) {
for (var attrname in sales[j]) { combined[i][attrname] = sales[j][attrname]; }
}
}
}
JSON.stringify(combined)
produces
[
{"brandName":"Gibson","stockValue":"1234","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Fender","stockValue":"975","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Epiphone","stockValue":"834","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}
]
Related
I would like to store product information in a key, value array, with the key being the unique product url. Then I would also like to store the visit frequency of each of these products. I will store these objects as window.localStorage items, but that's not very important.
The thing I had in mind was two key value arrays:
//product information
prods["url"] = ["name:product_x,type:category_x,price:50"]
//product visits frequency
freq["url"] = [6]
Then I would like to sort these prods based on the frequency.
Is that possible?
Hope you guys can help! Thanks a lot
Well you seem to have made several strange choices for your data format/structure. But assuming the format of the "prod" is beyond your control but you can choose your data structure, here's one way to do it.
Rather than two objects both using url as a key and having one value field each I've made a single object still keyed on url but with the product and frequency information from each in a field.
Objects don't have any inherent order so rather than sorting the table object I sort the keys, your "url"s ordered by ascending frequency.
To show that it's sorted that way I print it out (not in the same format).
For descending frequency, change data[a].freq - data[b].freq to data[b].freq - data[a].freq
var data = {
"url": {
prod: "name:product_x,type:category_x,price:50",
freq: 6
},
"url2": {
prod: "name:product_y,type:category_y,price:25",
freq: 3
}
};
var sorted = Object.keys(data).sort((a, b) => data[a].freq - data[b].freq);
console.log(sorted.map(k => [data[k].freq, k, data[k].prod]));
There's more than one way to format the data, which would change the shape of the code here.
maybe something like this:
var prods = [
{url:1, val:[{name:'a',type:'x',price:60}]},
{url:2, val:[{name:'b',type:'x',price:30}]},
{url:3, val:[{name:'c',type:'x',price:50}]},
{url:4, val:[{name:'c',type:'x',price:20}]},
{url:5, val:[{name:'c',type:'x',price:10}]},
{url:6, val:[{name:'c',type:'x',price:40}]}
];
var freq = [
{url:1, freq:6},
{url:2, freq:3},
{url:3, freq:5},
{url:4, freq:2},
{url:5, freq:1},
{url:6, freq:4}
];
prods.sort(function (a, b) {
var aU = freq.filter(function(obj) {
return obj.url === a.url;
});
var bU = freq.filter(function(obj) {
return obj.url === b.url;
});
if (aU[0].freq > bU[0].freq) {
return 1;
}
if (aU[0].freq < bU[0].freq) {
return -1;
}
return 0;
});
In localstorage I have key 'results' with this values:
[{"id":"item-1","href":"google.com","icon":"google.com"},
{"id":"item-2","href":"youtube.com","icon":"youtube.com"},
{"id":"item-3","href":"google.com","icon":"google.com"},
{"id":"item-4","href":"google.com","icon":"google.com"},
{"id":"item-5","href":"youtube.com","icon":"youtube.com"},
{"id":"item-6","href":"asos.com","icon":"asos.com"},
{"id":"item-7","href":"google.com","icon":"google.com"},
{"id":"item-8","href":"mcdonalds.com","icon":"mcdonalds.com"}]
To get the last item I use this:
// this is how I parse the arrays
var result = JSON.parse(localStorage.getItem("result"));
for(var i=0;i<result.length;i++) {
var item = result[i];
$('element').val(item.href);
}
How can I get the href for item-3 or for a specific ID?
Using native Array.filter
If you are targeting only modern browsers (IE9+ or a recent version of any other major browser) you can use the JavaScript 1.6 array method filter.
var testItem,
data = [{"id":"item-1","href":"google.com","icon":"google.com"},
{"id":"item-2","href":"youtube.com","icon":"youtube.com"},
{"id":"item-3","href":"google.com","icon":"google.com"},
{"id":"item-4","href":"google.com","icon":"google.com"},
{"id":"item-5","href":"youtube.com","icon":"youtube.com"},
{"id":"item-6","href":"asos.com","icon":"asos.com"},
{"id":"item-7","href":"google.com","icon":"google.com"},
{"id":"item-8","href":"mcdonalds.com","icon":"mcdonalds.com"}];
function getItemById(data, id) {
// filter array down to only the item that has the id
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
var ret = data.filter(function (item) {
return item.id === id;
});
// Return the first item from the filtered array
// returns undefined if item was not found
return ret[0];
}
testItem = getItemById(data, 'item-3');
Working example
Manually looping over the data
If you can't use filter you are probably stuck with just using a loop:
var testItem,
data = [{"id":"item-1","href":"google.com","icon":"google.com"},
{"id":"item-2","href":"youtube.com","icon":"youtube.com"},
{"id":"item-3","href":"google.com","icon":"google.com"},
{"id":"item-4","href":"google.com","icon":"google.com"},
{"id":"item-5","href":"youtube.com","icon":"youtube.com"},
{"id":"item-6","href":"asos.com","icon":"asos.com"},
{"id":"item-7","href":"google.com","icon":"google.com"},
{"id":"item-8","href":"mcdonalds.com","icon":"mcdonalds.com"}];
function getItemById(data, id) {
var i, len;
for (i = 0, len = data.length; i < len; i += 1) {
if(id === data[i].id) {
return data[i];
}
}
return undefined;
}
testItem = getItemById(data, 'item-3');
Working example
Even though brute-forcing it with a loop might seem less elegant than using Array.filter, it turns out that in most cases the loop is faster than Array.filter.
Refactoring to an object instead of an array
The best solution, assuming that the id of each of your items is unique, would be refactoring the way you are storing the data. Instead of an array of objects, use an object that uses the id as a key to store an object containing the href and icon key/property values.
var data = {
"item-1": {"href": "google.com", "icon": "google.com"},
"item-2": {"href": "youtube.com", "icon": "youtube.com"},
"item-3": {"href": "google.com", "icon": "google.com"},
"item-4": {"href": "google.com", "icon": "google.com"},
"item-5": {"href": "youtube.com", "icon": "youtube.com"},
"item-6": {"href": "asos.com", "icon": "asos.com"},
"item-7": {"href": "google.com", "icon": "google.com"},
"item-8": {"href": "mcdonalds.com", "icon": "mcdonalds.com"}
};
This would make accessing items even easier and faster:
var data = JSON.parse(localStorage.getItem("result"));
data["item-3"].href;
jQuery has filter helper for that:
$(result).filter(function(){return this.id == "item-3";})[0]
Function for href of item with specific id would be:
function getItemHrefById(json, itemId){
return json.filter(function(testItem){return testItem.id == itemId;})[0].href;
}
And sample usage is:
var href = getItemHrefById(result, "item-3");
You can see working example on http://jsfiddle.net/LXvLB/
UPDATE
If you cannot read item from local storage, maybe you forgot to call JSON.stringify when setting value:
localStorage["results"] = JSON.stringify([{"id":"item-1","href":"google.com","icon":"google.com"},
{"id":"item-2","href":"youtube.com","icon":"youtube.com"},
{"id":"item-3","href":"google.com","icon":"google.com"},
{"id":"item-4","href":"google.com","icon":"google.com"},
{"id":"item-5","href":"youtube.com","icon":"youtube.com"},
{"id":"item-6","href":"asos.com","icon":"asos.com"},
{"id":"item-7","href":"google.com","icon":"google.com"},
{"id":"item-8","href":"mcdonalds.com","icon":"mcdonalds.com"}])
You need to convert json to string to be properly serialized (and to use JSON.parse to get JSON back)
This is final example.
EDIT
As Useless Code pointed out, this metod is substantially slower than native filter function (and custom loop but I think that introducing few new lines of code to save 20-30ms is overkill unless performance is a issue), so I'm updating my example to not use jquery filter. +1 please for his answer for that.
Also, what is important to point out here, if this array would have hundreds instead of 8 bookmarks, for loop would probably be statistically about twice faster (as it does not have to iterate through rest of the array). But, in that case it would probably be a good idea to put for loop into function which returns first found item which satisfies condition, and with prototypejs it probably can even be hooked up to array.
for the jquery filter method, I think using a callback function, and bind the search parameter is more elegant and readable:
function filterById(id, i, obj) {
return obj.id === id;
}
function getItemHrefById(json, itemId) {
return $(json).filter(filterById.bind(null, itemId))[0].href;
}
da usual fiddle
(however, i prefer the "for loop" approach" for this!!)
Description and Goal:
Essentially data is constantly generated every 2 minutes into JSON data. What I need to do is retrieve the information from the supplied JSON data. The data will changed constantly. Once the information is parsed it needs to be captured into variables that can be used in other functions.
What I am stuck in is trying to figure out how to create a function with a loop that reassigns all of the data to stored variables that can later be used in functions.
Example information:
var json = {"data":
{"shop":[
{
"carID":"7",
"Garage":"7",
"Mechanic":"Michael Jamison",
"notificationsType":"repair",
"notificationsDesc":"Blown Head gasket and two rail mounts",
"notificationsDate":07/22/2011,
"notificationsTime":"00:02:18"
},
{
"CarID":"8",
"Garage":"7",
"Mechanic":"Tom Bennett",
"notificationsType":"event",
"notifications":"blown engine, 2 tires, and safety inspection",
"notificationsDate":"16 April 2008",
"notificationsTime":"08:26:24"
}
]
}};
function GetInformationToReassign(){
var i;
for(i=0; i<json.data.shop.length; i++)
{
//Then the data is looped, stored into multi-dimensional arrays that can be indexed.
}
}
So the ending result needs to be like this:
shop[0]={7,7,"Michael Jamison",repair,"Blown Head gasket and two rail mounts", 07/22/2011,00:02:18 }
shop[1]={}
You can loop through your JSON string using the following code,
var JSONstring=[{"key1":"value1","key2":"value2"},{"key3":"value3"}];
for(var i=0;i<JSONstring.length;i++){
var obj = JSONstring[i];
for(var key in obj){
var attrName = key;
var attrValue = obj[key];
//based on the result create as you need
}
}
Hope this helps...
It sounds to me like you want to extract the data in the "shop" property of the JSON object so that you can easily reference all of the shop's items. Here is an example:
var json =
{
"data":
{"shop":
[
{"itemName":"car", "price":30000},
{"itemName":"wheel", "price":500}
]
}
},
inventory = [];
// Map the shop's inventory to our inventory array.
for (var i = 0, j = json.data.shop.length; i < j; i += 1) {
inventory[i] = json.data.shop[i];
}
// Example of using our inventory array
console.log( inventory[0].itemName + " has a price of $" + inventory[0].price);
Well, your output example is not possible. You have what is a list of things, but you're using object syntax.
What would instead make sense if you really want those items in a list format instead of key-value pairs would be this:
shop[0]=[7,7,"Michael Jamison",repair,"Blown Head gasket and two rail mounts", 07/22/2011,00:02:18]
For looping through properties in an object you can use something like this:
var properties = Array();
for (var propertyName in theObject) {
// Check if it’s NOT a function
if (!(theObject[propertyName] instanceof Function)) {
properties.push(propertyName);
}
}
Honestly though, I'm not really sure why you'd want to put it in a different format. The json data already is about as good as it gets, you can do shop[0]["carID"] to get the data in that field.
I have an array like this (with just over 3000 objects instead of the 3 here):
items = [{name:'charlie', age:'16'}, {name:'ben', age:'18'}, {name:'steve', age:'18'}]
What's the best way to return an array with just the objects of people who are 18? So I want:
items = [{name:'ben', age:'18'}, {name:'steve', age:'18'}]
The best I can think of is this (using jQuery):
newArray = []
$.each(items, function(index, item) {
if(item.age=='18') {
newArray.push(item)
}
})
Considering that there's 3000 thousand objects, and also that I'll be doing that comparison up to fifty times in one go, that's a lot of looping. Is there a better way?
You can use pure javascript
var wanted = items.filter( function(item){return (item.age==18);} );
And if your browser does not support the 1.6 version of javascript you can find an implementation of the filter method at https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
Update
Speedwise there is a huge varying (had an error in the test) difference from a normal loop (depending on browser).. Have a look at this little test i made at http://jsperf.com/array-filter-vs-loop/3
Get matched item and items using find() and filter() method
If you want first matched single item, use find() method which returns single object.
If you want all matched , use filter() method which returns array of objects.
let items = [{name:'charlie', age:'16'},
{name:'ben', age:'18'},
{name:'steve', age:'18'}]
let all = items.filter(item=> item.age==='18')
console.log(all);
let single = items.find(item=> item.age==='18')
console.log(single);
If you're going to do the search often it may be best to keep a version of your data in a form that is quick to access.
I've used underscore.js (http://documentcloud.github.com/underscore/) to make it easy for myself, but this code here will create an object that holds your data indexed by the age field.
You end up with something that looks like this:
{
"16": [
{
"name": "charlie",
"age": "16"
}
],
"18": [
{
"name": "ben",
"age": "18"
},
{
"name": "steve",
"age": "18"
}
]
}
The code:
var itemsByAge = _(items).reduce(function(memo, item) {
memo[item.age] = memo[item.age] || [];
memo[item.age].push(item);
return memo;
}, {});
alert(JSON.stringify(itemsByAge["18"]));
No matter which method you choose (items.filter or any "query language" for json), a for loop is inevitable.
If performance is a concern, I would recommend you to use pure javascript instead of libraries like jQuery which will add overheads to the whole processing as is evident here.
Thus, your code would look like:
var newArray = [];
for(var i=0;i<items.length;i++) {
var item = items[i];
if(item.age == '18') {
newArray.push(item);
}
});
making use of javascript magnificent function eval() which evaluates string as code at runtime, we can define a prototype method for Array type
Array.prototype.where = function (query) {
var newArray = [];
for(var i=0; i<this.length; i++) {
var item = this[i];
if(eval( "item" + query )) {
newArray.push(item);
}
}
return newArray;
};
and use it with any array, passing the query as string
var newArray= items.where('.age >= 18');
Use the filter method of the array, it calls the provided callbackfunction once for each element in an array.
array.filter(<callbackfucntion>[, <Object to use>])
once i had such problem and i solved it like this
1- create an array of array
2- each index create an Index record
e.g.
var pAry=[];
var cAry=[{name:'ben', age:'18'}, {name:'steve', age:'18'}]
pAry[17]=cAry;
This way when u require person with age 18, you will get on index 17.
All of the MongoDB MapReduce examples I have seen have dealt with counting/adding numbers. I need to combine strings, and it looks like MapReduce is the best tool for the job. I have a large MongoDB collection in this format:
{name: userone, type: typeone}
{name: usertwo, type: typetwo}
{name: userthree, type: typeone}
Each name only has one type, but names are not necessarily unique. I want to end up with a collection that lists all names for a particular type, either in a comma separated list or an array, like this:
{type: typeone, names: userone, usertwo}
{type: typetwo, names: userthree}
I was trying to use MapReduce to accomplish this. My function works correctly when there is only one user for a type. However, when there is more than one user, 'undefined' is stored in the names field.
I'm not very good at Javascript, and I'm still learning MongoDB so it's probably a simple data type or scope error.
Here are my map and reduce functions. What's wrong with them?
map = function() {
emit(this.user,{type:this.type});
}
reduce = function(key, values) {
var all="";
for(var i in values) {
all+=values[i]['type']+",";
}
return all;
}
It looks to me like you're trying to do a group-by via type. If so, you should be emitting type first. From there, its pretty much the same as your code, but I took the liberty of cleaning it up a bit.
Beware, the reduce function could get called multiple times on smaller groups. Therefore, if you used your code in a sharded environment, you may get extra trailing commas. See Reduce Function for more information.
Map:
m = function(){ emit(this.type, {names:this.name}); }
Reduce:
r = function(key, values){
var all = [];
values.forEach(function(x){
all.push(x.names)
})
return {"names": all.join(", ")};
}
Usage:
res = db.users.mapReduce(m,r); db[res.result].find()
Alternate:
Per OP request, here is a version that returns an array for names instead of a comma separated list string:
m = function () {
emit(this.type, {names:this.name});
}
r = function (key, values) {
var all = [];
values.forEach(function (x) {all.push(x.names);});
return {type:key, names:all};
}
f = function (w, r) {
r.names = r.names[0];
return r
}
res = db.users.mapReduce(m,r, {finalize:f}); db[res.result].find()
Cheers!