socket-io - matching learning and speaking languages - angularjs

I'm trying to make a chat app (similar to Omegle.com) that matches native speakers to learners. I believe I have the correct algorithm, but my Javascript keeps throwing a message that Javascript heap out of memory error.
For example, let's say Speaker 1 speaks English and learns French and Speaker 2 speaks French and learns English (perfect match). My algorithm displays match found.
Queue Controller
socket.emit('in queue', {
speakingLanguages: speakingLanguages,
learningLanguages: learningLanguages
});
socket.on('chat start', function(data){
room = data.room;
$location.path('/chat');
});
server.js
var queue = [];
var rooms = {};
io.sockets.on('connection', function(socket){
console.log('User ' + socket.id + ' connected');
socket.on('in queue', function(data){
socket.speakingLanguages = data.speakingLanguages;
socket.learningLanguages = data.learningLanguages;
if (queue.length != 0){
for (var i = 0; i < queue.length; i++){
for (var j = 0; j < queue[i].speakingLanguages.length; j++){
for (var y = 0; y < socket.learningLanguages.length; y++){
if (queue[i].speakingLanguages[j] === socket.learningLanguages[y]){
console.log('a match was found!');
break;
}else{
queue.push(socket);
break;
}
}
}
}
}else{
queue.push(socket);
}
});
socket.on('send message', function(data){
var room = rooms[socket.id];
io.sockets.in(room).emit('send message', data);
});
});
But see, when I open up multiple windows on my browser to test a lot of cases, I get that error above. It says that I ran out of Javascript heap and I've been struggling with this for hours, but I don't know what to do.
Information:
speakingLanguages and learningLanguages are of type array (because people can speak multiple languages and learn multiple languages)
Every time a match is not found, I put them into the queue array

I was diligently writing down notes and investigating the algorithm and I think I've finally got it. I tested the algorithm and it seems to be working!
socket.on('in queue', function(data){
socket.speakingLanguages = data.speakingLanguages;
socket.learningLanguages = data.learningLanguages;
var found = false;
if (queue.length != 0){
for (var i = 0; i < socket.learningLanguages.length; i++){
for (var j = 0; j < queue.length; j++){
for (var y = 0; y < queue[j].speakingLanguages.length; y++){
if (socket.learningLanguages[i] === queue[j].speakingLanguages[y]){
console.log('a match was found');
var peer = queue.pop();
var room = socket.id + '#' + peer.id;
peer.join(room);
socket.join(room);
rooms[peer.id] = room;
rooms[socket.id] = room;
peer.emit('chat start', { room: room });
socket.emit('chat start', { room : room });
found = true;
break;
}
}
}
}
if (!found){
queue.push(socket);
}
}else{
queue.push(socket);
}
});

There is way too much for loop nesting in your code.
I made some changes to your code.
io.sockets.on('connection', function(socket){
console.log('User ' + socket.id + ' connected');
socket.on('in queue', function(data){
socket.speakingLanguages = data.speakingLanguages;
socket.learningLanguages = data.learningLanguages;
var searchingFor = [];
var toQueue = {};
var toUnqueue = {};
for (let spoken of socket.speakingLanguages) {
for (let learning of socket.learningLanguages) {
var searchKey = `S:${learning}L:${spoken}`;
var queueKey = `S:${spoken}L:${learning}`;
searchingFor.push(searchKey);
toQueue[queueKey] = socket;
toUnqueue[queueKey] = undefined;//Using `undefined` instead of `delete` for performance
}
}
//Search for a peer
var peer = false;//use an array[] if you want all matches
for (let searching of searchingFor) {
let result = queue[searching];
if(result) {
peer = result;
break;//You can change this part to find all matches
}
}
if (!peer) {
//No peer(s) found
//Add all possible combination of `spoken:learning` keys to the queue
//If someone searchs for one of these combinations we will be matched
socket.toUnqueue = toUnqueue;
Object.assign(queue, toQueue);
} else {
//We found a matching peer
console.log('a match was found');//If you use multiple matches you can ask what he prefers
//Unqueue other language combinations of the peer
Obeject.assign(queue, peer.toUnqueue);
//The rest of you logic goes here
var room = socket.id + '#' + peer.id;
peer.join(room);
socket.join(room);
rooms[peer.id] = room;
rooms[socket.id] = room;
peer.emit('chat start', { room: room });
socket.emit('chat start', { room : room });
}
});
socket.on('send message', function(data){
var room = rooms[socket.id];
io.sockets.in(room).emit('send message', data);
});
});
I changed the way you're using the queue.
Searching by key should be faster.
I didn't test it but it should be good.Tell me if it does the job!

I revised my code a bit using amenzou's answer and another one here
var queue = [];
var rooms = {};
var index = 0;
var intersect_safe = function(a, b){
var ai=0, bi=0;
var result = [];
while( ai < a.length && bi < b.length )
{
if (a[ai] < b[bi] ){ ai++; }
else if (a[ai] > b[bi] ){ bi++; }
else /* they're equal */
{
result.push(a[ai]);
ai++;
bi++;
}
}
return result;
}
io.sockets.on('connection', function(socket){
console.log('User ' + socket.id + ' connected');
socket.on('in queue', function(data){
socket.speakingLanguages = data.speakingLanguages;
socket.learningLanguages = data.learningLanguages;
if (queue.length != 0){
for (var i = 0; i < queue.length; i++){
var match = false;
var match1 = [];
var match2 = [];
match1 = intersect_safe(socket.learningLanguages, queue[i].speakingLanguages);
match2 = intersect_safe(socket.speakingLanguages, queue[i].learningLanguages);
if (match1.length != 0 && match2.length != 0){
console.log('match');
index = i;
match = true;
break;
}
}
if (match){
var peer = queue.splice(index, 1);
var room = socket.id + '#' + peer[0].id;
peer[0].join(room);
socket.join(room);
rooms[peer[0].id] = room;
rooms[socket.id] = room;
peer[0].emit('chat start', match2);
socket.emit('chat start', match1);
}else{
queue.push(socket);
}
}else{
queue.push(socket);
}
});
Works perfectly! Thank you very much for your help and advice, amenzou.

Related

How to fix 'Cannot convert Array to Object[][]' error in Google Apps Script

I'm attempting to get a list of all groups and all members of a group to be posted to a spreadsheet titled 'allGroups'. However, whenever I try to print the Array to the sheet I'm getting the error that says I can't convert the Array to an Object.
I've tried setting the array to be different sizes, changing the range so that it's more specific, and changing the code so that the group name is posted first (clearing the Array) and then moving from there but it hasn't worked.
function listAllGroupsAndMembers() {
var ss = SpreadsheetApp.getActive();
var groupPageToken, groupPage;
var listArray = [];
var outputSheet = ss.getSheetByName('allGroups') || ss.insertSheet('allGroups', 1);
outputSheet.clear();
do {
groupPage = AdminDirectory.Groups.list({
domain: 'google.com',
maxResults: 100,
pageToken: groupPageToken
});
var groups = groupPage.groups; //Gets the list of groups and begins to iterate for each one
if (groups) {
for (var i = 0; i < groups.length; i++) {
var group = groups[i];
listArray.push([group.name]);
var membersPageToken, membersPage;
do {
membersPage = AdminDirectory.Members.list(group.email, {
maxReults: 100,
pageToken: membersPageToken});
var members = membersPage.members;
if (members) {
for (var u = 0; u < members.length; u++) {
var member = members[u];
listArray.push(member.email);
outputSheet.getRange(u+1, i+1, listArray.length, listArray[0].length).setValues(listArray);
}
listArray = [];
} membersPageToken = membersPage.nextPageToken;
} while (membersPageToken);
}
}
} while (groupPageToken);
try {
outputSheet = ss.getSheetByName('allGroups');
outputSheet.getDataRange();
} catch(err) {
outputSheet = ss.insertSheet('allGroups', 2);
}
}
Expected results would be that a list of groups would populate across row 1, and the list of member's emails would appear below each group. Currently once I get to
outputSheet.getRange(u+1, i+1, listArray.length, listArray[0].length).setValues(listArray);
it tells me that can't convert the Array to an Object and fails.
EDIT
I've managed to get it working thanks to AMolina, Ross, and Cooper. This is the code I've got now:
function listAllGroupsAndMembers() {
var ss = SpreadsheetApp.getActive();
var groupPageToken, groupPage;
var listArray = [];
var outputSheet = ss.getSheetByName('allGroups') || ss.insertSheet('allGroups', 1);
var p = 0;
outputSheet.clear();
do {
groupPage = AdminDirectory.Groups.list({
domain: 'google.com',
pageToken: groupPageToken
});
var groups = groupPage.groups; //Gets the list of groups and begins to iterate for each one
if (groups) {
for (var i = 0; i < groups.length; i++) {
var group = groups[i];
listArray.push([group.name]);
var membersPageToken, membersPage;
do {
membersPage = AdminDirectory.Members.list(group.email, {
maxReults: 100,
pageToken: membersPageToken});
var members = membersPage.members;
if (members) {
for (var u = 0; u < members.length; u++) {
var member = members[u];
listArray.push([member.email]);
}
if(membersPageToken != undefined) {
p = p + 200;
} else { p = 0; }
Logger.log(p);
outputSheet.getRange(p+1, i+1, listArray.length, listArray[0].length).setValues(listArray);
listArray = [];
} membersPageToken = membersPage.nextPageToken;
} while (membersPageToken);
}
}
} while (groupPageToken);
try {
outputSheet = ss.getSheetByName('allGroups');
outputSheet.getDataRange();
} catch(err) {
outputSheet = ss.insertSheet('allGroups', 2);
}
}
It also is able to handle when there's more than 200 members in a group.
Try this:
function listAllGroupsAndMembers() {
var ss = SpreadsheetApp.getActive();
var groupPageToken, groupPage;
var listArray = [];
var outputSheet = ss.getSheetByName('allGroups') || ss.insertSheet('allGroups', 1);
outputSheet.clear();
do {
groupPage = AdminDirectory.Groups.list({
domain: 'sbtagent197.eu',
maxResults: 100,
pageToken: groupPageToken
});
var groups = groupPage.groups; //Gets the list of groups and begins to iterate for each one
if (groups) {
for (var i = 0; i < groups.length; i++) {
var group = groups[i];
listArray.push([group.name]);
var membersPageToken, membersPage;
do {
membersPage = AdminDirectory.Members.list(group.email, {
maxReults: 100,
pageToken: membersPageToken});
var members = membersPage.members;
if (members) {
for (var u = 0; u < members.length; u++) {
var member = members[u];
listArray.push([member.email]);
}
// This is where I made the change, moving this line outside the inner loop.
outputSheet.getRange(1, i+1, listArray.length, listArray[0].length).setValues(listArray);
listArray = [];
} membersPageToken = membersPage.nextPageToken;
} while (membersPageToken);
}
}
} while (groupPageToken);
try {
outputSheet = ss.getSheetByName('allGroups');
outputSheet.getDataRange();
} catch(err) {
outputSheet = ss.insertSheet('allGroups', 2);
}
}
I modified the code you had by changing the location of the outputSheet.getRange(1, i+1, listArray.length, listArray[0].length).setValues(listArray); line, you were calling it several times during the loop, which was causing the title to appear many times. basically, you were writing the block of group_name -> members over and over, one row lower every time, so it looked like you were writing the title a bunch of times when it was the whole thing.
The edited code sets the values once when the array is complete, it will write the name of the group in the first row and the members in the rows below it. Moving forward I would suggest you consider #Cooper 's advice, using getValues() and setValues() can make working with sheets much easier.

How to set factory properties so they are independent from one another?

Consider the below Angularjs 'service'. I would like to keep all my 'entries' related variables in this service so I can use them across controllers - as I believe the ideal angular pattern calls for. However, if I manipulate anyone of the variables from a controller - entries, entries_Sorted, entries_Loaded within the service object - they all seem to take on the same new value. I understand the factory object is a singleton but shouldn't these variables be independent? I don't expect or understand the behavior I am seeing. How is this useful? I must be doing something wrong.
To be clear:
If I set local variables within my controllers using this service's return methods, then update those local variables, all the three entries variables within the service will take on the new values.
Service code:
angular.
module('core.entry').
factory('Entry', ['$http', 'Topten', 'Stack', 'User',
function($http, Topten, Stack, User) {
var entries = [];
var entries_Sorted = [];
var entries_Loaded = [];
var service = {};
service.getEntries = function(stackId, callback) {
return $http.get('stacks/' + stackId + '/entries/')
.success(function(data) {
entries = data["entries"];
Topten.setToptens(data["topTen"]);
Stack.setOpenStack(data["stack"]);
callback(null, data);
})
.error(function(err) {
callback(err, null);
});
};
service.returnEntries = function() {
return entries;
}
service.sortEntries = function(callback) {
// 1. Loop through entries inner looping on toptens - adding topten score to total score
for (var i = 0; i < entries.length; i++) {
var thisEntry = entries[i];
var totalScore = 0;
var toptens = Topten.returnToptens();
for (var j = 0; j < toptens.length; j++) {
var thisTopten = toptens[j];
if (thisTopten["entryId"]) {
if (thisEntry["_id"] == thisTopten["entryId"]._id) {
totalScore = totalScore + thisTopten["score"];
}
}
}
thisEntry.totalScore = totalScore;
// 2. Add net (likes - dislikes) to entry.totalScore
for (var j = 0; j < thisEntry.votes.length; j++) {
var thisVote = thisEntry.votes[j]["vote"];
if (thisVote == "up") {
thisEntry["up"] = thisEntry["up"] + 1;
} else if (thisVote == "down") {
thisEntry["down"] = thisEntry["down"] + 1;
}
}
var netLikes = thisEntry["up"] - thisEntry["down"]; // one point each
thisEntry["totalScore"] = thisEntry["totalScore"] + netLikes;
}
// 3. Sort entries by entry.totalScore and return
entries_Sorted = entries.sort(function(a, b) {
return b.totalScore - a.totalScore;
});
callback();
};
service.returnEntries_Sorted = function() {
return entries_Sorted;
};
return service;
}
]);
My controller's code:
Entry.getEntries($routeParams.stackId, function(err, data) {
if(err) {
}
// get sorted entries (after return from getEntries)
Entry.sortEntries(function() {
self.entries_Sorted = Entry.returnEntries_Sorted();
self.loadMore();
});
});
self.loadMore = function() {
self.entries_Loaded = self.entries_Loaded.concat(self.entries_Sorted.splice(page * increment, increment));
self.page +=1;
}
Problem: After I call this local 'load_More' function, the properties in my service - entries, _Sorted, _Loaded - will all have the new 'spliced' value. ie. Entry.entries will have the same value as the controller's local self.entries_Sorted.

Infdig in Custom groupBy

i recently needed some kinda customized repeater, which group data by their key, but not simply group them all together, so i read several references and things,... and at last i come up to copy
groupBy from this article which he seem to complete it at best, ...
http://sobrepere.com/blog/2014/10/14/creating-groupby-filter-angularjs/
And then i customized so it become like this:
the things my group by do... is:
Group Data Together Until It Reach Differences.
but the matter is that though it work, it still generate infdig, i know it's because the digest done call other one, but what i don't know is how to solve it in very easy and understandable manner, as i'm new to JavaScript...
app.filter('groupBy', function () {
var results = {};
return function (data, key) { //Data => My Objects Array - Key => Name Of Filtered Property
if (!(data && key)) return;
var result;
if (!this.$id) {
result = {};
} else {
var scopeId = this.$id;
if (!results[scopeId]) {
results[scopeId] = {};
this.$on("$destroy", function () {
delete results[scopeId];
});
}
result = results[scopeId];
}
for (var groupKey in result)
result[groupKey].splice(0, result[groupKey].length);
var grpKey = -1; //GroupKey
var lastUserId;
for (var i = 0; i < data.length; i++) {
if (!result[grpKey] || lastUserId && lastUserId != data[i][key]) // Ex.: result[data[0]["UserId"]]{ => return UserId
result[++ grpKey] = [];
result[grpKey].push(data[i]);
lastUserId = data[i][key];
}
var keys = Object.keys(result);
for (var k = 0; k < keys.length; k++) {
if (result[keys[k]].length === 0)
delete result[keys[k]];
}
return result;
};
});
In this url is working perfectly...
http://plnkr.co/edit/8jB4wSRtKfVmEsTGZtfV?p=preview
app.filter('groupBy', function ($timeout) {
return function (data, key) {
if (!key) return data;
var outputPropertyName = '__groupBy__' + key;
if(!data[outputPropertyName]){
var result = {};
for (var i=0;i<data.length;i++) {
if (!result[data[i][key]])
result[data[i][key]]=[];
result[data[i][key]].push(data[i]);
}
Object.defineProperty(result, 'length', {enumerable: false, value: Object.keys(result).length});
Object.defineProperty(data, outputPropertyName, {enumerable:false, configurable:true, writable: false, value:result});
$timeout(function(){delete data[outputPropertyName];},0,false);
}
return data[outputPropertyName];
};
});

How can I send many(10) requestify, sync in nodejs?

I am developing nodejs application
I have array with 10 URL
var urlArray = [{url01},{url02},{url03},......,{url10}];
var arrayLength = 10;
var reqData = {message:'Wel Come'};
for(var i = 0; i < arrayLength ; i ++ ){
requestify.post(urlArray[i],reqData)
.then(function(resonse){
console.log(response);
},function(err){
console.log(err);
});
}
I need to send 10 requestify sync way
You can do it following way.
var i = 0;
var urlArray = [{url01},{url02},{url03},......,{url10}];
var makeCall = function(x){
if( x < urlArray.length ) {
requestify.post(urlArray[i],reqData)
.then(function(resonse){
makeCall(x+1);
},function(err){
console.log(err);
});
}
};
makeCall(0);

How to implement a do while loop on a promise on angularjs

I'm quite stuck in the development of an app right now. What i want to do is upon submission by ng-click the following task will be performed:
generate a 5 digit random number.
this random number will then be validated in the database if exist.
if it exist it will generate another number which will then be validated again until such time that the value returns to zero.
for number 1 i already have this:
var gRandomNum = function(){
var snumRand = Math.floor((Math.random()*10000)+1);
var numRand = snumRand.toString();
var str_f = numRand.length;
if(str_f == 1){
str_final = "0000" + numRand;
} else if(str_f == 2){
str_final = "000" + numRand;
} else if(str_f == 3){
str_final = "00" + numRand;
} else if(str_f == 4){
str_final = "0" + numRand;
} else {
str_final = numRand;
}
return str_final;
}
for number 2
var validataRandNum = function(pdata){
return $http.get('api/cntTc/'+pdata).
success(function(data){
return data.tc_count;
});
}
for number 3
do{
var pdata = gRandomNum();
var ifValid = validataRandNum(pdata);
} while(ifValid < 0);
Here is what it looks like on my Scope function
$scope.ok = function(){
do{
var pdata = gRandomNum();
var ifValid = validataRandNum(pdata);
} while(ifValid < 0);
}
When i tried to verify the value of ifValid all i'm getting is undefined but when i clicked again the value will show up but it was from the last clicked value. I tried to implement a promise but got stucked on how to implement it inside a promise. If you can provide a code that will do just that it will be great.
Is there any way to move this to the server side?
To stick to doing this on the client, perhaps you can try to have validateRandNum() call itself recursively (but consider putting in some limits so it doesn't go on forever)...
var validataRandNum = function(getRandNumFunc, deferred){
deferred = deferred || $q.defer();
var pdata = getRandNumFunc();
$http.get('api/cntTc/'+pdata).
success(function(data){
if (data.tc_count < 0) {
validataRandNum(getRandNumFunc, deferred);
} else {
deferred.resolve(data.tc_count);
}
});
return deferred.promise;
};
$scope.ok = function(){
validataRandNum(gRandomNum).then(function (tc_count) {
$scope.tc_count = tc_count;
});
};

Resources