How do you selectively remove models from a Backbone collection and from the server? - javascript

For example, if you want to remove the completed todos from a todo list.
Selectively removing models from a Backbone collection and server seems like a common task. What are the common ways to do it, and what are the costs and benefits associated with each way?

Solution One
var toRemove = collection.filter(function(model) {
return condition(model);
});
_.invoke(toRemove, 'destroy');
This seems like the cleanest way. It's what Addy used in his book to delete completed todos (which is a big reason why I list this solution first). It's especially clean if you're reusing the filter function (like he is).
However, I think it's slower than Solution Two because it involves iteration over collection and toRemove, whereas Solution Two only involves iteration over collection. Still, they both have linear run times so it's not too big a deal.
Solution Two
for (var i = collection.models.length-1; i >= 0; i--) { // looping from back to front
var model = collection.models[i];
if (condition(model)) model.destroy();
}
I think this is relatively clean. And as mentioned above, this has the benefit of only having to loop through the collection, not the collection + a filtered version of the collection.
It's important that you loop from back to front. Consider what happens when you loop from front to back:
for (var i = 0; i < collection.models.length; i++) { // looping from front to back
var model = this.models[i];
if (condition(model)) {
model.destroy();
i--;
}
}
When you destroy a model, the models in collection.models basically get shifted up by one. This has two implications:
The length of the array decreases by one.
Say you delete element two. The next element will be four, not three. The index gets incremented to 3, and since the models get shifted up by one, three has an index of 2 and four has an index of 3.
Solutions:
Calculate collection.models.length after each iteration of the loop. Ie. for (var i = 0; i < **collection.models.length**; i++)
Decrement i after you destroy a model.
You could loop from front to back, but you would just have to address these things, which makes it a bit more complicated.
Solution Three
var toRemove = [];
collection.forEach(function(model) {
if (condition(model)) toRemove.push(model);
});
toRemove.forEach(function(model) {
model.destroy();
});
This is pretty similar to Solution One.
Differences:
We're using a forEach to construct toRemove instead of filter.
We're manually iterating through and calling destroy instead of using invoke.
Like Solution One, you have to iterate through collection and through toRemove, and so it presumably takes longer than Solution Two.
Note: we can't destroy the model in the first forEach loop. If we do, then it has the same problem as the front-to-back loop in Solution Two. To get around this limitation, we have to use the toRemove array.
Solution Four
This uses reset() + the reset event.
var toKeep = collection.filter(function(model) {
return condition(model);
});
collection.reset(toKeep);
and
collection.on('reset', function(after, before) {
// let after.length = k
// let before.length = n
after.sort(); // k*logk
for (var i = before.length-1; i >= 0; i--) { // n
var model = before[i];
if (!binarySearch(model, after)) model.remove(); // logk
}
// total runtime: n*logk + k*logk
});
This seems a bit excessive to me, but is an option.

Related

Turning off JavaScript setInterval timers stored in array of timer / websocket client pairs

so I have a node js / websocket server going, where I want to store multiple instances of setInterval timers...
I have them stored in an array of [timer, client] objects that I want to step through when a client disconnects, then step through the array and turn off all the timers that are paired with the disconnecting client before removing them from the array.
Removing the objects connected with the clients is working, however, the timers are not stopping...
I am declaring the timers like this -
clientSchedulePairs.push([setInterval(makeVisibleHandler, master_period, item, name), client]);
then trying to turn the schedule off like this when a client disconnects-
clearInterval(clientSchedulePairs[i][0]);
in my research i found this question -
clearInterval() is not stopping setInterval() - Firefox Extension Development
which says that I need to make a timer a global variable? but that isnt helping because if I have multiple timers for multiple clients how can i make them global variables?
I am storing them in a global const array that is declared globally such as-
const clientSchedulePairs = [];
however it is not working...
any idea on if the reason its not working is because its not declared as a global variable? or how I can get around this and get it to work? (i already tried declaring the schedule as a variable in my method before adding it to the array)
thanks.
UPDATED---
i got it working turning off the timers --- not sure if it had something to do with the way the client was disconnecting, after I removed my for loop into an external method that took in the client socket as a variable, then looped through my array of client / timer pairs and checked for the clients and removed them it started working. However, I am running into a kind of strange issue now...
as i said, i am looping through a clientTimerPairs array and checking if the client paired to that timer is === to the client_socket that was passed in from when the method was called when a client disconnects, in this loop, calls this--
clearInterval(clientTimerPairs[i].interval);
and the timers turn off, however I had a problem removing the client - timer tuples from the clientTimerPairs array now
i couldnt get them to remove from the array, so i changed it to work like this-
var indexToRemove = [];
for (var i = 0; i < clientTimerPairs.length; i++) {
if (clientTimerPairs[i].pairedClient === client_socket) {
clearInterval(clientTimerPairs[i].interval);
indexToRemove.push(i);
}
}
for (var i = 0; i < indexToRemove.length; i++) {
console.log('removing index ' + indexToRemove[i] + '...');
clientSchedulePairs.splice(indexToRemove[i], 1);
}
however, even if i console print indexToRemove, and it has all index 0 - 6 in it, (because during testing i only connected 1 client with 6 timers), it should be stepping through clientTimerPairs and removing every single index so clientTimerPairs is empty, however, for some strange reason instead of going from 6 to 0 size, the array always ends up at 3 size!
even though it prints the removing index line 6 times and says it is removing index 0 - 5, 3 items are always left over in the clientTimerPairs array!
any idea why this might happen?
That push statement does not provide a proper key-value pair. How about using es6 Map to secure actual pairs?
However, it should work as intended.
const timers = [];
for (let i = 0; i < 5; i++) {
timers.push([setInterval(runTimer.bind({ id: i}), 100), i]);
}
function runTimer() {
console.log(`running for id ${this.id}`);
}
setTimeout(() => {
console.log('clearing timers')
for (let i = 0; i < timers.length; i++) {
clearInterval(timers[i][0]);
}
}, 2000);
Edit regarding the addition to your post
The splicing does exactly as it is intended to do. Say you have an array of 6 elements. You are clearing elements from the array using an array of indices and remove the first element. That array length becomes 5. The second index points to the 6th element. When you try to splice the 6th element by its index, nothing happends. This is because you removed one element from the array, which made the 6th element shift to the 5th position.
A simple solution could be using es6 filter:
let items = ['one', 'two', 'three', 'four'];
let indices = [1, 3];
items = items.filter((item, index) => indices.indexOf(index) === -1);
This returns an array with the first and third elements.

Angular Js stuck in inserting the array values after sorting

Hello guys i am stuck in angular js.
What i have to do is to show the steps of selectionSort and the code i made is
var myApp = angular.module('myApp',[]);
var arraymain = [];
myApp.controller('myController',function($scope) {
$scope.array2 = [];
$scope.selectionSort = function(list) {
n=list.length;
temp2 = list;
for(i=0; i<n-1; i++) { //need to do n-2 passes
i_min=i;
//finding mining index
for(j=i+1;j<n;j++){//ith position: elements from i till n-1 candidates
if(temp2[j]<temp2[i_min])
i_min=j; //update the index of minimim element
}
temp=temp2[i];
temp2[i]=temp2[i_min];
temp2[i_min]=temp;
alert(temp); //It shows as needed
$scope.array2.push(temp2); //Here i am having problem it saves the sorted final array i.e the last every time of loop but i want to save current array on every outer loop execution
}
return list;
$scope.selectionSort([3,2,3,4,5,1,2]);
console.log($scope.array2[0]);
console.log($scope.array2[1]);
console.log($scope.array2[2]);
});
Sorry for my bad English.
It's not clear what you need to do. I'm pretty sure you've got a real mess on your hands beyond your main problem, and using Angular is only making it worse.
If your goal is to console.log the state of the array at each step of the selectionSort outer loop, then you need to run console.log at the bottom of each loop.
The first thing that looks fishy suspicious is:
$scope.array2.push(temp2);
If you run console.log(array2) at each step of the loop as I suggested, you need this line to be instead:
$scope.array2 = temp2;
That way, you overwrite array2, which holds the state of the array at each iteration of the for loop. Then you log it. Appending each state to a larger array, and then running console.log on each item in that array is not the best way to do this.
But, I'm not sure that's what your problem is.

remove specific item from a json object

i am trying to remove some items in an json object list, the ones that have a specific group. My JSON looks like this.
var events = [
{"id":"19","name":"sports","group":"1"},
{"id":"20","name":"school","group":"2"},
{"id":"21","name":"fun","group":"1"}
]
I tried this
for(var i in events)
if(events[i].group == deleted_group)
events.splice(i, 1);
But the problem of this, is that if i have to remove more items, it bugs out. Is there another easy way to to this ? I am open for sugestion even using underscore.js .
Thank you in advance, Daniel!
Try this
var events = [
{"id":"19","name":"sports","group":"1"},
{"id":"20","name":"school","group":"2"},
{"id":"21","name":"fun","group":"1"}
]
console.log(_.reject(events, function(event){ return event.group == '1'; }));
When you're using the "splice" function to remove elements from the array inside a for loop,
you need to shift your current index back when removing an item since the array is reindexed.
Also take a look at the array functions like "filter" for a more convenient way, read more on MDN.
You can use delete operator to delete objects (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete):
delete events[0]
The problem with delete is, that in your array, as a value of events[0] it will leave undefined.
So another way (the way I would choose for your simple example) is to just rewrite the array:
var temp_events = [];
for(var i in events)
if(events[i].group != deleted_group)
temp_events[temp_events.length] = events[i];
events = temp_events;
Executing splice in a for loop has complexity n^2 (where n is number of elements). Rewriting has linear complexity.

Which javascript structure has a faster access time for this particular case?

I need to map specific numbers to string values. These numbers are not necessarily consecutive, and so for example I may have something like this:
var obj = {};
obj[10] = "string1";
obj[126] = "string2";
obj[500] = "string3";
If I'm doing a search like this obj[126] would it be faster for me to use an object {} or an array []?
There will be no difference. ECMAScript arrays, if sparse (that is don't have consecutive indices set) are implemented as hash tables. In any case, you are guaranteed the O(n) access time, so this shouldn't concern you at all.
I created a microbenchmark for you - check out more comprehensive test by #Bergi. On my browser object literal is a little bit slower, but not significantly. Try it yourself.
A JS-array is a object, so it should not matter what you choose.
Created a jsperf test (http://jsperf.com/array-is-object) to demonstrate this.
Definetely an object should be the best choice.
If you have such code:
var arr = [];
arr[10] = 'my value';
, your array becomes an array of 11 values
alert(arr.length); // will show you 11
, where first 10 are undefined.
Obviously you don't need an array of length 1000 to store just
var arr = [];
arr[999] = 'the string';
Also I have to notice that in programming you have to chose an appropriate classes for particular cases.
Your task is to make a map of key: value pairs and object is the better choice here.
If your task was to make an ordered collection, then sure you need an array.
UPDATE:
Answering to your question in comments.
Imagine that you have two "collections" - an array and an object. Each of them has only one key/index equal to 999.
If you need to find a value, you need to iterate through your collection.
For array you'll have 999 iterations.
For object - only one iteration.
http://jsfiddle.net/f0t0n/PPnKL/
var arrayCollection = [],
objectCollection = {};
arrayCollection[999] = 1;
objectCollection[999] = 1;
var i = 0,
l = arrayCollection.length;
for(; i < l; i++) {
if(arrayCollection[i] == 1) {
alert('Count of iterations for array: ' + i); // displays 999
}
}
i = 0;
for(var prop in objectCollection) {
i++;
if(objectCollection[prop] == 1) {
alert('Count of iterations for object: ' + i); // displays 1
}
}
​
Benchmark
​
In total:
You have to design an application properly and take into account possible future tasks which will require some different manipulations with your collection.
If you'll need your collection to be ordered, you have to chose an array.
Otherwise an object could be a better choice since the speed of access to its property is roughly same as a speed of access to array's item but the search of value in object will be faster than in sparse array.

Photoshop/JavaScript script to clean up nested layers. Need foreach alternative for recursive function

Background: I'm writing a script to clean up all non-visible layers in a photoshop document. Photoshop layers can be grouped and groups can be nested similar to a directory tree so I'm using recursion.
The problem I'm having is there is there is no foreach function in JavaScript to iterate through each layer in a set. I used for(i=0; i<layers.length; i++) etc but each time I remove a layer it changes the index of the layers and eventually fails. I fixed this by pushing each layer to a new array and removing them with another for loop. I'm having the same problem with removing empty groups.
Anyway, I have a *semi-working function but it seems like a mess. I tried using for-in loop but it loops through properties like .length and .parent instead of the actual layers. Is there a foreach alternative that would work in this situation? Do I need to run two separate recursive functions to remove the layers then to remove empty groups? Thanks!
Here is the semi-working script.
And a sample .psd document.
If you're unfamiliar with running scripts in photoshop just select File>Scripts>Browse and select the .jsx file.
There are a number of possible solutions here:
Backwards Iteration
One typical way to solve an issue like this is to iterate the array from the end to the front. That way, when you remove the item you're currently interating, it doesn't affect the array position of the elements you still need to iterate:
for (i=layers.length - 1; i >= 0; i--)
Make a Static Copy of the Array
Another possibility is to make a copy of the array so you have a static copy to iterate through that isn't being modified when layers are removed:
var staticLayersArray = Array.prototype.slice.call(layers);
for (i=0; i < staticLayersArray.length; i++) {
// process staticLayersArray[i]
}
Accumulate Items to Delete
You can iterate through the array and accumulate the items to delete and only delete them when you're done with the iteration:
var layersToDelete = [];
for (i=0; i < layers.length; i++) {
if (we need to delete this item) {
layersToDelete.push(layers[i]);
}
}
// after the iteration, process the deletions
for (i = 0; i < layersToDelete.length; i++) {
// remove the layer layersToDelete[i];
}
FYI, modern implementations of javascript arrays do have a .forEach() method, but it is not safe from changing the array while iterating so you would still have to use some other solution anyway.

Categories