I want to use AngularJS to display a shuffled list - but only the first couple of elements. At the moment, I perform both the shuffling and limiting in the HTML template, demonstrated in this fiddle:
<li ng-repeat="value in array | shuffle | limitTo:1">
{{value}}
</li>
This works fine, but causes Angular to exceed 10 $digest iterations when there are more items in the list than are shown, as in the example. What is happening, as far as I can tell, is that something is watching the filtered and limited value of the list, which is highly likely to change when there all the elements will not always be displayed.
How should I achieve this effect without breaking Angular? Of course, it still works like it should, but it's inefficient and probably an indication that what I'm doing is incorrect and should be achieved some other way.
The obvious solution is to shuffle the list in the controller before it is ever displayed - but I do want the selection of elements displayed to change each time the user updates the view.
EDIT: here's a better example of what I'm trying to achieve - the app now lets you switch between two lists, which get shuffled and limited each time.
As mentoined before - angular waits for an expression gets stabilized.
So, the best way, is to shuffle an array before passing it to view.
Anyway, there are some tricks, that would help you to keep it clean.
http://jsfiddle.net/zc3YH/3/
In this fiddle we cache shuffle result, while a length keeps the same. So, you get array reshuffled not when an array changes, but when it length changed. You can implement more complex caching behavior. So, the main idea is to shuffle the same array only once, and reshuffle it only on array update.
Current filter implementation is really bad, cause it caches only single array, so if you would use this filter twice - it would be broken. So, caching should be done using something like hashKey for an array to differ different arrays.
shufflemodule.filter('shuffle', function() {
var shuffledArr = [],
shuffledLength = 0;
return function(arr) {
var o = arr.slice(0, arr.length);
if (shuffledLength == arr.length) return shuffledArr;
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
shuffledArr = o;
shuffledLength = o.length;
return o;
};
});
Anyway, it is not good practice to modify data in scope within a filter. If you need this shuffled array be shared - shuffle it in your controller/service/.... So, var o = arr.slice(0, length) copies that array, thus we keep originar array unmodified.
This fiddle demonstrates the 'filter in the controller' approach. Instead of actually creating a filter, shuffling is defined as a regular function and applied to $scope.array.
I think tgat problem is that your filter modify source array. Even if you return new array it would be asane problem. Its how dirty checking works: it keeps evaluating expression until it gets "stabilized" (or 10 iterations). More isdescribed in this post: https://groups.google.com/forum/m/#!msg/angular/IEIQok-YkpU/InEXv61MrkMJ
Solution would be pre-shuffle values in controller so expression would return same vallue each dirty-check phase... Sorry, but that is the best way for now (unless you whant to play dirty games with $$hash to let ngRepeat beleave that yourexpression evaluates into same values :))
Related
I have a Map<string, boolean>. I want to extract the keys to an array based on their values. I want to end up with two string arrays; one being keys whose values are false and one being keys whose values are true.
I tried to filter them like this:
const trueKeys = [...myMap.entries()].filter(it => it[1] === true).map(it => it[0]);
const falseKeys = [...myMap.entries()].filter(it => it[1] === false).map(it => it[0]);
But it requires iterating over the entries twice for each array. Is there a better way to achieve this?
I don't think iterating over a map's entries twice vs once is a big deal, as it's hard for me to imagine a situation where changing that would make the difference between code that performs poorly and code that performs well (like running away from a tsunami; you're either too close or far enough away, and running is almost certainly not changing that).
Still, if you want to traverse only once, you can do so:
const trueKeys: string[] = [];
const falseKeys: string[] = [];
myMap.forEach((v, k) => (v ? trueKeys : falseKeys).push(k));
console.log(trueKeys, falseKeys);
Here I'm iterating the Map just once, with the forEach() method. Even [...myMap.entries()] iterates the array once before you even try to filter it, so I've avoided this to ensure that we're only traversing once.
And in the callback I push() each key into one of two arrays depending on the value. This mutates the output arrays, and you can rewrite not to do so, but then you'd almost certainly be throwing away the modest amount of performance gained.
Playground link to code
I'd like to know if there's a way to understand if an array contain another array inside him. I ask this question because I'm trying to loop throught two or plus nested arrays but first I need to check this in Angular.
Thanks in advance for the help
var a = [[1,2,3],[4,5,6]]
console.log(Array.isArray(a[0]))
You can check this using Array.isArray function.
There are many ways this can be accomplished, but it depends on the size of the parent array, how deeply nested it's elements are, how does much does performance matter, etc.
If your array only goes one level deep (inner arrays don't contain arrays), you can use these methods:
var arr = [[1,2,3], 'foo', 4, ['foo', 'bar']];
// use .some() to test elements
arr.some((element) => Array.isArray(element));
// returns the index of the first found array, or -1 if no arrays are present
arr.indexOf((element) => Array.isArray(element));
This article gives a pretty good overview of all options, including the performance of each.
Given an array of “visits” [{ date:DATE, summary:TEXT }, { date:DATE, summary:TEXT }, …]
, if I need to show the last visit, where would I do the calculation:
In the controller and add the calculated value to the $scope - <div>{{lastVisit}}</div>
Using a $scope method - <div>{{getLastVisit()}}</div>
In the view (this definitely doesn’t feel right) - <div>{{visits[visits.length-1]}}</div>
I am avoiding for now the question whether the model should be manipulated inside the controller or in its own service.
With option 1, you'd have to add a watch to update the lastVisit in model any time the visits array changes. Option 2 is better but requires writing an additional one-liner function in your model.
The third option is legit and require zero javascript so if you only need to simply show the last element of the array this is the way to go.
It's also the most efficient as it doesn't require any additional objects in memory, and doesn't call any other function (than angular parse internally)
If you don't want the logic in your view, Option 2 is your best choice. But I would create a more generic method that returns the last element of the array like that:
<div>{{getLastItem(visits)}}</div>
$scope.getLastItem = function(arr){
return arr[arr.length - 1];
};
See fiddle: http://jsfiddle.net/hZM23/1/
My personal preference on the above approaches:
Yes, the best approach, since you don't have your business logic in your view.
This will have the same effect as the 1st option, but accessing Model from view comes under best practices.
No, since your business logic might change in future.
Option 1: Doing calculations in the controller and storing the value to $scope. When that value is updated, angular will automatically update your view. This is likely what you want.
To clarify: In your controller, when the visits array is updated ( element added, deleted, etc... ), calculate the last visit value and store it to $scope.lastVisit.
var function newVisit( visit ){
visits.append( visit );
$scope.lastVisit = visit; // this will update your view
}
Why I don't think option 2 is right: Angular will be binding the function and not the value itself. Binding to the value itself is likely what you mean.
You are correct about 3, keep logic out of the view if possible.
I'm attempting to write a sort function to use with Array.sort(). I'm a bit stuck on how I can write exactly what I need though.
In my app items are added to this array at different times throughout execution, and each time an item is added, the array is sorted. The items in the Array are all objects and all have a property "weight". If a weight is greater, the item should go first, if it's less the item should go after. That is easy and I have a function that looks like this:
return a.weight - b.weight;
The problem is I have an added requirement that if an item is added later and it has the same weight as another item it MUST be put after that item in the array. It MUST go behind every item in the array that has already been added which has the same weight.
I'm having trouble coming up with a function to make sure that requirement is met every time.
Thanks for the help!
No need for writing a custom sort, the Array's sortOn can handle this case.
You will however need to add a new member to your items, I'll call it 'timestamp'.
arr.sortOn( [ 'weight', 'timestamp' ], [ Array.NUMERIC | Array.DESCENDING, Array.NUMERIC ] );
The first parameter defines which properties will be used for sorting, the second defines the options for each field. See http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/Array.html#sortOn() for more info.
The |-operator (bitwise OR-operator) is used to pass multiple options for one field.
So, in this case, the first field ('weight') gets sorted numerically and descending.
--EDIT:
For Vectors you need to use a comparison function:
var sortFunc : Function = function (x: <T>, y : <T>):Number{
var dw:Number = y.weight - x.weight
if( dw ==0 ){
//returns negative if y was added later
return x.timestamp - y.timestamp;
}else{
//returns negative if x has a higher weight
return dw;
}
}
vec.sort( sortFunc );
I would suggest to add another property to the object. Something like :
a.index = i; // where, i is the index before sorting
This will allow you to keep track of the order it entered the list before sorting.
Besides this you may also consider keeping another copy of the array itself (with index intact).
I'm using Google Docs Spreadsheet API to keep track of a competition between some of my friends. I have some big ideas, but I'm stumped right now. I'm trying to create 20 different arrays inside of loops (see below) and then evaluate each one outside of the loop, and I really don't want to write 20 "if...then" statements.
NOTE: the following SUMMARY may or may not help you answer my question. You might want to skip down to the code, then read this if you need it :)
Summary of the program: Each player assigns point values in favor of one possible outcome of a set of binary-outcome events. As the events happen, players either gain the points assigned if their outcome occurs, or gain no points if the opposite outcome occurs. My goal is to 1) figure out exactly when a player is eliminated, and 2) highlight all remaining events that must be won for them to have a chance at tying for first.
Instead of trying to somehow evaluate all possibilities (5 players picking, 2^16 outcomes... I have zero computer science knowledge, but this seems like an incredibly huge task even for the modern computer) I've come up with an alternate idea. The script loops through each player, against each other opponent. It calculates the maximum number of points a player can score based on their value assignments and the already determined game. For one player and one opponent, it checks the best possible outcome by the player against that opponent, and if there is any opponent he cannot beat, even in the best case, then he is eliminated.
This part is easy-- after the loop runs inside the loop, I just adjust a global variable that I created earlier, and when the outer loop is done, just grab those variables and write them to the sheet.
Unfortunately, this misses the case of where he could have a best case against each individual opponent, but not against multiple opponents at once.
So the next step is what I'm trying to do now. I'm not even sure I can give a good explanation without just showing you the entire spreadsheet w/script, but I'll try. So what I want to do now is calculate the "value" of each event for each player against a given other player. If both player and opponent assigned points in favor of the same event outcome for one event, the event's value is the difference between the picks (positive if player picked higher, negative if lower), and it's the SUM if they picked opposite event outcomes. Now, I do the same thing as before-- take a best-case scenario for a player against a given opponent-- but now I check by how much the player can beat the opponent in a best-case scenario. Then I evaluate the (absolute value of the) event value against this difference, and if it's greater, then the event is a must win (or must lose if the event value is negative). And, if an event is both a "must-win" and a "must lose" event, then the player is eliminated.
The problem is that this second step requires me to create a new array of values for each player-opponent combination, and then do things with the values after they're created.
I realize one approach would be to create 20 different arrays, and throughout the entire loops, keep checking "if (player == "1" && opponent == 2){}" and populate the arrays accordingly, but this seems kind of ridiculous. And more importantly, this entire project is my attempt at learning javascript, so what's the point in using a time-intensive workaround that doesn't teach me anything new?
I'm trying to understand square bracket notation, since it seems to be the answer to my question, but a lot of people are also suggesting that it's impossible to create variable names by concatenating with the value of another variable... so anyway, here's what I'm trying. I'd really appreciate either a fix to my approach, or a better approach.
for (var player=1; player<6; player++){
if(player==1){look up certain columns in the spreadsheet and save them to variables}
//ditto for other players
for(var opponent=1; opponent<6; opponent++){
if(player!=opponent){
if(opponent==1){save more values to variables}
//ditto for other players
for(var row=9; row<24; row++) {
//Now the script goes down each row of an array containing the original
//spreadsheet info, and, based on information determined by the variables
//created above, get values corresponding to the player and opponent.
//So what I'd like to do here is create "array[1,2]==" and then "array[1,3]=="
//and so forth, creating them globally (I think I understand that term by now)
//so I can refer to them outside of the loops later to do some evaluatin'.
}
}}
//get array[1,2]...array[5,4] and do some operations with them.
Thanks for getting through this little novel... really looking forward to your advice and ideas!
How can I create arrays within a loop (within another loop)?
Code update 2
As you said: "i am trying to understand square bracket notation" You may take a look at my new demo and the code:
function getTeam(){
var array = [[1,2,3],[4,5,6],[7,8,9]]; // arrays within arrays
// array myTeam
var myTeam = [[],[],[],[]];
var playerNames = ["John", "Bert", "Dave", "Milton"];
var ages =[];
var weight = 104;
// loop over the team arrayadd each player (name, age and weight) to the team
for (i=0; i < myTeam.length; i++){
// fill the age array in a loop
for (j=0;j<myTeam.length;j++) {
ages[j] = 23 + j;
}
myTeam[i].push([playerNames[i], ages[i], weight]);
}
return myTeam;
}
And pass them back out in Javascript
Could you elaborate on this part?
Update
var valuesOfPlayers=[];
for (var player=1; player<6; player++){
// look up certain columns in the spreadsheet and save them to variables
// you could call a funcntion and return the values you
// collected in an array within an array as in the demo above
valuesOfPlayers[player] = lookupColumnValues(player);
for(var opponent=1; opponent<6; opponent++){
if(player!=opponent){
// save more values to variables
valuesOfPlayers[player] = addValuesToVar(player);
}
for(var row=9; row<24; row++) {
// if you collect the values in your first and second if clause
// what other information do you want to collect
// Please elaborate this part?
}
}}
One workaround:
I could create an array before the execution of the loops.
At the start of each loop, I could push a string literal to the array containing the value of player and opponent.
After the loops are done, I could split the array into multiple arrays, or just evaluate them in one big array using regular expressions.
I'd still rather create new arrays each time-- seems like it is a more universal way of doing this, and learning how would be more educational for me than using this workaround.