Accessing Associative Array/Object in MongoDB MapReduce - javascript

I am trying to aggregate the names of all the users in on a blog who have replied to each other. I have records as follows:
{
"_id" : ObjectId("4ee9ada4edfb941f3400ba63"),
"thread" : "Millenium - Niels Arden Oplev",
"author" : "kilny17",
"parent_count" : 0,
"parents" : [ ],
"child_count" : 2,
"date" : ISODate("2010-04-20T21:14:00Z"),
"message" : "I don't think so...",
"children" : [
{
"date" : ISODate("2010-04-20T21:21:00Z"),
"author" : "Kissoon"
},
{
"date" : ISODate("2010-04-20T21:49:00Z"),
"author" : "Twain"
}
]
}
I am trying to return, for each author, a MapReduced object such as:
{ "_id" : "kilny17",
"value" : {
"author" : "kilny17",
"connections" : {
"Kissoon" : 1,
"Twain" : 1 }
}
}
This code works for each record that has a children element with just 1 child, but not for more:
function mapf()
{
var count = this['child_count'];
if (count > 0){
var m_author = this.author;
this['children'].forEach( function(c){
var connect = {'name':c['author'], 'appears':1};
emit(m_author, {'author':m_author, 'connections':connect});
});
};
}
function reducef(key, values)
{
var connects = new Object();
var r = {'author':key, 'connections':connects, 'weight':0};
values.forEach(function(v)
{
c_name = v['connections'].name;
if (c_name == null)
c_name = 'Null_name';
if (r['connections'][c_name] != null)
r['connections'][c_name] += v['connections']['appears'];
else
r['connections'][c_name] = v['connections']['appears'];
});
return r;
}
For any record (such as the example given) with more than 1 child, the author names are not found and I get a reduced record like so (N.B. There was another post by kilny with child DarkKnight3657):
{ "_id" : "kilny17", "value" : { "author" : "kilny17", "connections" : { "DarkKnight3657" : 1, "Null_name" : null } } }
Anyone have any ideas as to why the author name is not being read from the Object?
Thanks

I think the problem is that you are not defining connections as an array in the mapper- you are defining it as an element. Off the top of my head, it seems like it should read:
var connect = [{'name':c['author'], 'appears':1}];
emit(m_author, {'author':m_author, 'connections':connect});

As Chris has suggested, the solution I used was to change the object into an array:
function mapf()
{
if (this['child_count'] > 0){
var m_author = this.author;
if ( m_author == '')
m_author = 'Unknown_author';
var connect = [];
var weight = 0;
for ( c in this['children'] ){
c_name = this['children'][c]['author'];
found = false;
for (i in connect){
if (connect[i]['name'] == c_name){
connect[i]['appears'] += 1;
found = true;
}
}
if (found == false){
var con = {'name':c_name,'appears':1};
connect.push(con);
}
weight += 1;
};
emit(m_author, {'author':m_author, 'connections':connect, 'weight':weight});
};
}
function reducef(key, values)
{
var r = {'author':key, 'connections':[], 'weight':0};
values.forEach(function(v)
{
for ( c in v['connections'] ){
c_name = v['connections'][c]['name'];
found = false;
for (i in r['connections']){
if (r['connections'][i]['name'] == c_name){
r['connections'][i]['appears'] += 1;
found = true;
}
}
if (found == false){
var con = {'name':c_name,'appears':1};
r['connections'].push(con);
}
};
r.weight += v.weight;
});
return r;
}
This then resulted in records of the type desired:
{
"_id" : "Skaundee",
"value" : {
"author" : "Skaundee",
"connections" : [
{
"name" : "Carnage",
"appears" : 1
},
{
"name" : "Tree",
"appears" : 1
}
],
"weight" : 2
}
}

Related

Pass in structure correctly (js)

Face some problem in passing data into structure correctly
This is the structure
And this is how i pass in
var dates = require('dates');
module.exports.function = function getStart() {
var optionList = [
{option : "Latest headlines"},
{option : "Latest news"},
{option : "Top headlines"},
{option : "Top news"}
]
var currentTimeHour = dates.ZonedDateTime.getHour
var timePeriod = "";
if (currentTimeHour == 0 && currentTimeHour <= 12) {
timePeriod = "M"//Morning
} else if (currentTimeHour >= 13 && currentTimeHour <= 20) {
timePeriod = "A"//Afternoon
} else if (currentTimeHour <=23){
timePeriod = "N"//Night
}else {
timePeriod = null
}
var menu = {};
optionList.option.forEach(function(value,index,array){
menu[index] = {
whatuserwant : optionList[index],
timePeriod : timePeriod
}
});
return menu
}
And error pop out
Where did i gone wrong ,
And for extra question, is my if-else condition for currentTimeHour wrote correctly?
Regards.
forEach is defined on arrays where as optionList.option is not an array.
Try using only optionList
optionList.forEach(function(value,index,array){
menu[index] = {
whatuserwant : value.option,
timePeriod : timePeriod
}
});

Perfomance issues with large number of elements in Mithril.js

My app has a sort- and filterable list and a few inputs and checkboxes so far.
The problem appears if the list has more than 500 items, then every every element with user input (checkboxes, input fields, menus) start to have a lag around half a second increasing with the number of items in the list. The sorting and filtering of the list is done fast enough but the lag on the input elements is too long.
The question is: how can the list and the input elements be decoupled?
Here is the list code:
var list = {}
list.controller = function(args) {
var model = args.model;
var vm = args.vm;
var vmc = args.vmc;
var appCtrl = args.appCtrl;
this.items = vm.filteredList;
this.onContextMenu = vmc.onContextMenu;
this.isSelected = function(guid) {
return utils.getState(vm.listState, guid, "isSelected");
}
this.setSelected = function(guid) {
utils.setState(vm.listState, guid, "isSelected", true);
}
this.toggleSelected = function(guid) {
utils.toggleState(vm.listState, guid, "isSelected");
}
this.selectAll = function() {
utils.setStateBatch(vm.listState, "GUID", "isSelected", true, this.items());
}.bind(this);
this.deselectAll = function() {
utils.setStateBatch(vm.listState, "GUID", "isSelected", false, this.items());
}.bind(this);
this.invertSelection = function() {
utils.toggleStateBatch(vm.listState, "GUID", "isSelected", this.items());
}.bind(this);
this.id = "201505062224";
this.contextMenuId = "201505062225";
this.initRow = function(item, idx) {
if (item.online) {
return {
id : item.guid,
filePath : (item.FilePath + item.FileName).replace(/\\/g, "\\\\"),
class : idx % 2 !== 0 ? "online odd" : "online even",
}
} else {
return {
class : idx % 2 !== 0 ? "odd" : "even"
}
}
};
// sort helper function
this.sorts = function(list) {
return {
onclick : function(e) {
var prop = e.target.getAttribute("data-sort-by")
//console.log("100")
if (prop) {
var first = list[0]
if(prop === "selection") {
list.sort(function(a, b) {
return this.isSelected(b.GUID) - this.isSelected(a.GUID)
}.bind(this));
} else {
list.sort(function(a, b) {
return a[prop] > b[prop] ? 1 : a[prop] < b[prop] ? -1 : 0
})
}
if (first === list[0])
list.reverse()
}
}.bind(this)
}
};
// text inside the table can be selected with the mouse and will be stored for
// later retrieval
this.getSelected = function() {
//console.log(utils.getSelText());
vmc.lastSelectedText(utils.getSelText());
};
};
list.view = function(ctrl) {
var contextMenuSelection = m("div", {
id : ctrl.contextMenuId,
class : "hide"
}, [
m(".menu-item.allow-hover", {
onclick : ctrl.selectAll
}, "Select all"),
m(".menu-item.allow-hover", {
onclick : ctrl.deselectAll
}, "Deselect all"),
m(".menu-item.allow-hover", {
onclick : ctrl.invertSelection
}, "Invert selection") ]);
var table = m("table", ctrl.sorts(ctrl.items()), [
m("tr", [
m("th[data-sort-by=selection]", {
oncontextmenu : ctrl.onContextMenu(ctrl.contextMenuId, "context-menu context-menu-bkg", "hide" )
}, "S"),
m("th[data-sort-by=FileName]", "Name"),
m("th[data-sort-by=FileSize]", "Size"),
m("th[data-sort-by=FilePath]", "Path"),
m("th[data-sort-by=MediumName]", "Media") ]),
ctrl.items().map(function(item, idx) {
return m("tr", ctrl.initRow(item, idx), {
key : item.GUID
},
[ m("td", [m("input[type=checkbox]", {
id : item.GUID,
checked : ctrl.isSelected(item.GUID),
onclick : function(e) {ctrl.toggleSelected(this.id);}
}) ]),
m("td", {
onmouseup: function(e) {ctrl.getSelected();}
}, item.FileName),
m("td", utils.numberWithDots(item.FileSize)),
m("td", item.FilePath),
m("td", item.MediumName) ])
}) ])
return m("div", [contextMenuSelection, table])
}
And this is how the list and all other components are initialized from the apps main view:
// the main view which assembles all components
var mainCompView = function(ctrl, args) {
// TODO do we really need him there?
// add the main controller for this page to the arguments for all
// added components
var myArgs = args;
myArgs.appCtrl = ctrl;
// create all needed components
var filterComp = m.component(filter, myArgs);
var part_filter = m(".row", [ m(".col-md-2", [ filterComp ]) ]);
var listComp = m.component(list, myArgs);
var part_list = m(".col-md-10", [ listComp ]);
var optionsComp = m.component(options, myArgs);
var part_options = m(".col-md-10", [ optionsComp ]);
var menuComp = m.component(menu, myArgs);
var part_menu = m(".menu-0", [ menuComp ]);
var outputComp = m.component(output, myArgs);
var part_output = m(".col-md-10", [ outputComp ]);
var part1 = m("[id='1']", {
class : 'optionsContainer'
}, "", [ part_options ]);
var part2 = m("[id='2']", {
class : 'menuContainer'
}, "", [ part_menu ]);
var part3 = m("[id='3']", {
class : 'commandContainer'
}, "", [ part_filter ]);
var part4 = m("[id='4']", {
class : 'outputContainer'
}, "", [ part_output ]);
var part5 = m("[id='5']", {
class : 'listContainer'
}, "", [ part_list ]);
return [ part1, part2, part3, part4, part5 ];
}
// run
m.mount(document.body, m.component({
controller : MainCompCtrl,
view : mainCompView
}, {
model : modelMain,
vm : modelMain.getVM(),
vmc : viewModelCommon
}));
I started to workaround the problem by adding m.redraw.strategy("none") and m.startComputation/endComputation to click events and this solves the problem but is this the right solution? As an example, if I use a Mithril component from a 3rd party together with my list component, how should I do this for the foreign component without changing its code?
On the other side, could my list component use something like the 'retain' flag? So the list doesn't redraw by default unless it's told to do? But also the problem with a 3rd party component would persist.
I know there are other strategies to solve this problem like pagination for the list but I would like to know what are best practices from the Mithril side.
Thanks in advance,
Stefan
Thanks to the comment from Barney I found a solution: Occlusion culling. The original example can be found here http://jsfiddle.net/7JNUy/1/ .
I adapted the code for my needs, especially there was the need to throttle the scroll events fired so the number of redraws are good enough for smooth scrolling. Look at the function obj.onScroll.
var list = {}
list.controller = function(args) {
var obj = {};
var model = args.model;
var vm = args.vm;
var vmc = args.vmc;
var appCtrl = args.appCtrl;
obj.vm = vm;
obj.items = vm.filteredList;
obj.onContextMenu = vmc.onContextMenu;
obj.isSelected = function(guid) {
return utils.getState(vm.listState, guid, "isSelected");
}
obj.setSelected = function(guid) {
utils.setState(vm.listState, guid, "isSelected", true);
}
obj.toggleSelected = function(guid) {
utils.toggleState(vm.listState, guid, "isSelected");
m.redraw.strategy("none");
}
obj.selectAll = function() {
utils.setStateBatch(vm.listState, "GUID", "isSelected", true, obj.items());
};
obj.deselectAll = function() {
utils.setStateBatch(vm.listState, "GUID", "isSelected", false, obj.items());
};
obj.invertSelection = function() {
utils.toggleStateBatch(vm.listState, "GUID", "isSelected", obj.items());
};
obj.id = "201505062224";
obj.contextMenuId = "201505062225";
obj.initRow = function(item, idx) {
if (item.online) {
return {
id : item.GUID,
filePath : (item.FilePath + item.FileName).replace(/\\/g, "\\\\"),
class : idx % 2 !== 0 ? "online odd" : "online even",
onclick: console.log(item.GUID)
}
} else {
return {
id : item.GUID,
// class : idx % 2 !== 0 ? "odd" : "even",
onclick: function(e) { obj.selectRow(e, this, item.GUID);
m.redraw.strategy("none");
e.stopPropagation();
}
}
}
};
// sort helper function
obj.sorts = function(list) {
return {
onclick : function(e) {
var prop = e.target.getAttribute("data-sort-by")
// console.log("100")
if (prop) {
var first = list[0]
if(prop === "selection") {
list.sort(function(a, b) {
return obj.isSelected(b.GUID) - obj.isSelected(a.GUID)
});
} else {
list.sort(function(a, b) {
return a[prop] > b[prop] ? 1 : a[prop] < b[prop] ? -1 : 0
})
}
if (first === list[0])
list.reverse()
} else {
e.stopPropagation();
m.redraw.strategy("none");
}
}
}
};
// text inside the table can be selected with the mouse and will be stored
// for
// later retrieval
obj.getSelected = function(e) {
// console.log("getSelected");
var sel = utils.getSelText();
if(sel.length != 0) {
vmc.lastSelectedText(utils.getSelText());
e.stopPropagation();
// console.log("1000");
}
m.redraw.strategy("none");
// console.log("1001");
};
var selectedRow, selectedId;
var eventHandlerAdded = false;
// Row callback; reset the previously selected row and select the new one
obj.selectRow = function (e, row, id) {
console.log("selectRow " + id);
unSelectRow();
selectedRow = row;
selectedId = id;
selectedRow.style.background = "#FDFF47";
if(!eventHandlerAdded) {
console.log("eventListener added");
document.addEventListener("click", keyHandler, false);
document.addEventListener("keypress", keyHandler, false);
eventHandlerAdded = true;
}
};
var unSelectRow = function () {
if (selectedRow !== undefined) {
selectedRow.removeAttribute("style");
selectedRow = undefined;
selectedId = undefined;
}
};
var keyHandler = function(e) {
var num = parseInt(utils.getKeyChar(e), 10);
if(constants.RATING_NUMS.indexOf(num) != -1) {
console.log("number typed: " + num);
// TODO replace with the real table name and the real column name
// $___{<request>res:/tables/catalogItem</request>}
model.newValue("item_update_values", selectedId, {"Rating": num});
m.redraw.strategy("diff");
m.redraw();
} else if((e.keyCode && (e.keyCode === constants.ESCAPE_KEY))
|| e.type === "click") {
console.log("eventListener removed");
document.removeEventListener("click", keyHandler, false);
document.removeEventListener("keypress", keyHandler, false);
eventHandlerAdded = false;
unSelectRow();
}
};
// window seizes for adjusting lists, tables etc
vm.state = {
pageY : 0,
pageHeight : 400
};
vm.scrollWatchUpdateStateId = null;
obj.onScroll = function() {
return function(e) {
console.log("scroll event found");
vm.state.pageY = e.target.scrollTop;
m.redraw.strategy("none");
if (!vm.scrollWatchUpdateStateId) {
vm.scrollWatchUpdateStateId = setTimeout(function() {
// update pages
m.redraw();
vm.scrollWatchUpdateStateId = null;
}, 50);
}
}
};
// clean up on unload
obj.onunload = function() {
delete vm.state;
delete vm.scrollWatchUpdateStateId;
};
return obj;
};
list.view = function(ctrl) {
var pageY = ctrl.vm.state.pageY;
var pageHeight = ctrl.vm.state.pageHeight;
var begin = pageY / 41 | 0
// Add 2 so that the top and bottom of the page are filled with
// next/prev item, not just whitespace if item not in full view
var end = begin + (pageHeight / 41 | 0 + 2)
var offset = pageY % 41
var heightCalc = ctrl.items().length * 41;
var contextMenuSelection = m("div", {
id : ctrl.contextMenuId,
class : "hide"
}, [
m(".menu-item.allow-hover", {
onclick : ctrl.selectAll
}, "Select all"),
m(".menu-item.allow-hover", {
onclick : ctrl.deselectAll
}, "Deselect all"),
m(".menu-item.allow-hover", {
onclick : ctrl.invertSelection
}, "Invert selection") ]);
var header = m("table.listHeader", ctrl.sorts(ctrl.items()), m("tr", [
m("th.select_col[data-sort-by=selection]", {
oncontextmenu : ctrl.onContextMenu(ctrl.contextMenuId, "context-menu context-menu-bkg", "hide" )
}, "S"),
m("th.name_col[data-sort-by=FileName]", "Name"),
${ <request>
# add other column headers as configured
<identifier>active:jsPreprocess</identifier>
<argument name="id">list:table01:header</argument>
</request>
} ]), contextMenuSelection);
var table = m("table", ctrl.items().slice(begin, end).map(function(item, idx) {
return m("tr", ctrl.initRow(item, idx), {
key : item.GUID
},
[ m("td.select_col", [m("input[type=checkbox]", {
id : item.GUID,
checked : ctrl.isSelected(item.GUID),
onclick : function(e) {ctrl.toggleSelected(this.id);}
}) ]),
m("td.nameT_col", {
onmouseup: function(e) {ctrl.getSelected(e);}
}, item.FileName),
${ <request>
# add other columns as configured
<identifier>active:jsPreprocess</identifier>
<argument name="id">list:table01:row</argument>
</request>
} ])
}) );
var table_container = m("div[id=l04]",
{style: {position: "relative", top: pageY + "px"}}, table);
var scrollable = m("div[id=l03]",
{style: {height: heightCalc + "px", position: "relative",
top: -offset + "px"}}, table_container);
var scrollable_container = m("div.scrollableContainer[id=l02]",
{onscroll: ctrl.onScroll()}, scrollable );
var list = m("div[id=l01]", [header, scrollable_container]);
return list;
}
Thanks for the comments!
There are some good examples of when to change redraw strategy in the docs: http://mithril.js.org/mithril.redraw.html#changing-redraw-strategy
But in general, changing redraw strategy is rarely used if the application state is stored somewhere so Mithril can access and calculate the diff without touching DOM. It seems like your data is elsewhere, so could it be that your sorts method is getting expensive to run after a certain size?
You could sort the list only after events that modifies it. Otherwise it will be sorted on every redraw Mithril does, which can be quite often.
m.start/endComputation is useful for 3rd party code, especially if it operates on DOM. If the library stores some state, you should use that for the application state as well, so there aren't any redundant and possibly mismatching data.

Transform an object recursively in JavaScript

I've been struggling with that problem for a few days. I have to transform an object into another object with a different structure.
from this:
{
"a1" : "key",
"a2" : {
"b1" : "key",
"b2" : {
"c1" : "key"
}
}
}
to this:
{
"key" : {
"a1" : null,
"a2" : [
{
"b1" : null,
},
{
"b2" : [
"c1" : null
]
}
]
}
}
For better understanding, a1, a2, b1, b3 etc. would represent css selectors. null will be the value applied to the node. But for now, this shouldn't matter.
My current (non working) function looks like this:
var json = {};
var obj = {};
function build(objA){
for(var a in objA){
var key = objA[a];
if(key instanceof Object){
obj = {}; obj[a] = key;
build(key);
} else {
json[key] = json[key] || {};
if(obj == undefined){
json[key][a] = null;
} else {
var p = firstKeyOf(obj);
var key = obj[p][a];
json[key][p] = json[key][p] || [];
var selector = {};
selector[a] = null;
json[key][p].push(selector);
}
}
}
}
which produces:
{
"key" : {
"a1" : null,
"a2" : [
{
"b1" : null
}
],
"b2" : [
{
"c1" : null
}
]
}
}
What did i miss? I appreciate any help, thanks!
Try this
var Converter = (function() {
var pic;
function _rec(a, o) {
for(var k in o){
var s = o[k];
var arr = a.slice();
arr.push(k);
if (s instanceof Object) { _rec(arr, s); }
else {
if ("string" == typeof s) { arr.unshift(s); _attach(pic, arr); }
else console.log('Invalid key type');
}
}
}
function _attach(p, m) {
var k = m.shift();
if ( ! p.hasOwnProperty(k)) p[k] = (m.length == 0) ? null : {};
if (m.length > 0) p[k] = _attach(p[k], m);
return p;
}
function _reverse(obj) {
if ( ! (obj instanceof Object)) console.log('First parameter need to be an Object');
else { pic = {}; _rec([], obj); return pic; }
}
return {
reverse: _reverse,
attach: _attach
}
})();

Check if value exists in array of objects

If I have an array of objects like this:
"localValues" : [
{
"localValId" : "e3rQACssGkfp9zsue",
"localProductCode" : "271102502",
"localMembersPrice" : 7814.090000000001,
"localProductDescription" : "11R225 146/143L H DUN SP384 FM TL",
"fetPr" : "29.39",
"invPrice" : "353.85"
},
{
"localValId" : "NxtmZngRpGY56grkW",
"localProductCode" : "290132910",
"localMembersPrice" : "300",
"localProductDescription" : "215/70R16 99S DUN GRNDTRK ST20 BSWTL",
"fetPr" : "",
"invPrice" : "136.72"
},
{
"localValId" : "WXLiCMJMixndtQtqZ",
"localProductCode" : "271102502",
"localMembersPrice" : "444",
"localProductDescription" : "11R225 146/143L H DUN SP384 FM TL",
"fetPr" : "29.39",
"invPrice" : "353.85"
}];
Is there a way I can check if a new localProductCode already exists in the localValues array?
Thank you.
you can try:
function isExisted(localValues, localProductCode) {
for (var i = 0; i < localValues.length; ++i) {
if (localValues[i].localProductCode == localProductCode) {
return true;
}
}
return false;
}
This is a way to find the index of coincidence (like indexOf)
Array.prototype.indexOfObj = function(key, value){
for(var i = 0; i < this.length;)
if(this[i++][key] === value) return --i
return -1
}
Items.localValues.indexOfObj("localValId", "NxtmZngRpGY56grkW"); //1
Items.localValues.indexOfObj("localValId", "WXLiCMJMixndtQtqZ"); //2
Demo

Discrete Bar Chart using nvd3 library

I am implementing discrete bar chart using nvd3 library on click of button i am calling generateBarChart method
here is code for that :
generateBarChart = function(data, options) {
if (!$.isArray(data)) {
if (!$.isPlainObject(data)) {
data = $.makeArray(data);
} else {
try {
data = $.parseJSON(data);
} catch(err) {
console.error(data, "generateBarChart error", err.message, err.stack);
data = [];
}
}
}
var chart1;
if (!$.isPlainObject(options)) {
options = {};
}
options = $.extend(true, {
container : "#svgChart",
title : "",
titleStyle : {
x : 500,
y : 200,
fontFamily : "sans-serif",
fontSize : "20px",
fill : "black"
}
}, options);
nv.addGraph(function() {
chart1 = nv.models.discreteBarChart().x(function(d) {
return d.label || ""
}).y(function(d) {
return d.value || 0
});
var selectedChart = d3.select(options.container).datum(dataserver(data)).transition().duration(500);
selectedChart = selectedChart.call(chart1);
//d3.select(options.container).append("text").attr("x", options.titleStyle.x).attr("y", options.titleStyle.y).attr("font-family", options.titleStyle.fontFamily).attr("font-size", options.titleStyle.fontSize).attr("fill", options.titleStyle.fill).text(options.title);
$(options.container).css({
}).parent().css("text-align", "center").prepend('<h5 class="text-center" >' + options.title + '</h5>');
nv.utils.windowResize(function() {
chart1.update();
});
return chart1;
});
return chart1;
};
function dataserver(data) {
for ( i = 0; i < data.length; i++) {//TODO
var name = data[i].name;
//TODO
var itemAmount = data[i].itemAmount;
//TODO
}
return [{
"color" : "#d62728",
"values" : [{
"label" : name,
"value" : itemAmount
}]
}];
}
I am referring following link ( http://nvd3.org/livecode/#codemirrorNav ) to generate discreteBarchart i am kind of stuck in dataserver() method as in the for loop i can get only last value
Looking for other stuff about discreteBarChart I arrived here.
Hope you have fixed your problem by now, if not... you need to do something like this:
function dataserver(data) {
values = [];
for ( i = 0; i < data.length; i++) {
var name = data[i].name;
var itemAmount = data[i].itemAmount;
values.push({"label": name, "value": itemAmount});
}
return [{
"color" : "#d62728",
"values" : values
}];
}
In this way, you are filling an array of values (called "values" with all the imagination I have right now), and then returning this data to the graph.

Categories