There's a gap in my JavaScript knowledge here. I want to search an array of objects values for a particular value and return it.
For the year I have been writing JavaScript, I have been implementing it like this:
var itemClicked = (function(){
var retval;
//Note self.inventory.itemsArray is an array of JS objects
$(self.inventory.itemsArray).each(function(i){
if(parseInt(this.id) === parseInt(idOfItem)){
retval = this;
return false;
}
});
return retval;
})();
It works, but I'm sure as anything there is a more elegant way. Tell me please!
EDIT - Solution
Thanks to #gdoron with his answer below.
var myVar = $(self.owner.itemsArray).filter(function(){
return parseInt(this.id) == parseInt(recItemID);
}).get(0);
Note: .get(0) was added at the end because myVar is wrapped as a jQuery object.
The native jQuery function for this is filter:
$(data).filter(function(){
return this.id == "foo";
});
It's shorter than code you have and more important a lot more readable.
About efficiency, it will iterate all the elements in the set to find as much as possible matches, but I hardly believe it will be the bottle neck of your application, don't focus on micro-optimisations.
I suggest you read Eric Lipper blog about Which is faster.
You can also use grep as suggested by #Mattias Buelens:
$.grep(data, function(ele){
retun ele.id == "foo";
});
Just another option using jQuery's $.grep( ) function
var arr = $.grep( self.inventory.itemsArray, function ( n ) {
return n.id == idOfItem;
});
The above returns an array of matching array elements. If you just want the first it is easy enough to return arr[0] if it exists.
Although I'm unsure what the function is actually supposed to do (due to the external contexts' variables), the following should be more efficient cycle-wise
var itemClicked = (function(){
var i, array = self.inventory.itemsArray, length = array.length;
for( i=0; i < length; i++) {
if(parseInt(array[i].id) === parseInt(idOfItem)){
return array[i];
}
}
return undefined;
})();
It's an array of Javascript objects
Then do not use jQuery at all. At least, use $.each instead of building a wrapper object around the array. Still, a simple for-loop is much shorter and more performant:
var itemClicked = (function(idnum) {
var arr = self.inventory.itemsArray;
for (var i=0, l=arr.length; i<l; i++)
if (parseInt(arr[i].id, 10) === idnum)
return arr[i];
})( parseInt(idOfItem, 10) );
You might as well think of storing the id properties as numbers right away, so you don't need to convert it each time.
Related
I need help with this JS callback function. I am trying to figure out how exactly callbacks work in JS.
--quick test code follows:
function debFilter(deb_array, fillCb){
var filt_darr = [];
for (var inx in deb_array) {
filt_darr.push(fillCb(deb_array[inx]));
}
return filt_darr;
}
console.log(debFilter(savedInp, function(x) { if (x%2 == 0) { return x;}} ));
Let's say my savedInp array contains [2,3,4,5,6,7,8,9] something like this. How do I make sure my callback returns only the even elements and not the odd ones? so my filt_darr would be [2,4,6...etc].
With the above test code I am getting [2,undefined,4,undefined,..etc]. I have tried with other similar conditions too with no avail. I just need to know how to tell JS not to 'push/return' something I dont need. Sorry if this is a beginner Q.
Thanks for the help.
Iterate the array and then push evens into a new array:
var a = [1,2,3,4,5];
function getEvens(originalArray){
var evens = [];
for(var i = 0; i < originalArray.length; ++i){
if(originalArray[i] % 2 === 0){
evens.push(originalArray[i]);
}
}
return evens;
}
As you probably noticed, you are collecting every return value into your result array and your callback returns undefined for every odd.
You could change your code to sth like
function debFilter(deb_array, fillCb){
'use strict';
var filt_darr = [],
len = deb_array.length;
for (var i = 0; i < len; i++) {
if (fillCb(deb_array[i])) {
filt_darr.push(deb_array[i]);
}
}
return filt_darr;
}
By the way, ES 5 supports Array.prototype.filter which might be what you are looking for. There is also a polyfill that you can take some inspiration from.
Chrome developer tools says that the value function doesn't work on a null value and points to the line in the for loop. Why isn't getElementByID fetching my values? (this is a refactor, getElement work perfect with the actual values typed in).
locationStops = ["start","end"];
var stopNum = locationStops.length;
var stopAddresses = [];
for(val in locationStops) {
stopAddresses.push(document.getElementById(val).value);
}
You could avoid the for loop, and the potential for bugs that you ran into with, by using map:
stopAddresses = locationStops . map(function(id) {
return document.getElementById(id).value;
});
Depending on your stylistic preferences, you might find the following more readable:
function get_value_from_id(id) {
return document.getElementById(id).value;
}
stopAddresses = locationStops . map(get_value_from_id);
If you want to use a loop, you could use the new for...of construct:
for (let val of locationStops) {
^^
stopAddresses.push(document.getElementById(val).value);
}
If you have an environment that supports ES7 array comprehensions:
[ for (id of locationStops) document.getElementById(id).value ]
If you want to stick with your for...in loop, then as other answers have pointed out, the loop variable is the index, not the value, so you have to access the ID with locationStops[i], but you are better off using a regular for loop.
Do not use for in for arrays.
Use a simple for loop instead.
var a = ["start", "end"];
for(var i = 0; i < a.length; ++i)
{
console.log(document.getElementById(a[i]).value);
}
You can use for-in also but, it is not recommended as it results in unexpected behaviour sometimes.
val refers to 0,1 etc. So there must be elements with ID's 0,1.
for(var val in a)
{
console.log(document.getElementById(a[val]).value);
}
Your code is not working because your for loop syntax is incorrect
Try This
var locationStops = ["start","end"];
var stopNum = locationStops.length;
var stopAddresses = [];
for(i = 0; i < locationStops.length; i++) {
stopAddresses.push(document.getElementById(locationStops[i]).value);
}
Alternatively, you could use Array.prototype.map.
var locationStops = ["start","end"];
var stopAddresses = locationStops.map(function(val) {
return document.getElementById(val).value;
});
Honestly, though, looping over a two element array is kind of silly and if it was my code I would even prefer to simply assign each address directly.
var stopAddresses = [document.getElementById("start").value, document.getElementById("end").value];
My values naturally come in this form:
[1,2,3,4,5,6,7,8,9]
I am developing against a server api which requires an input parameter like:
[1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9]
Is there a faster or more js-style way to do than a simple for loop?
var f = function(values) {
var newList = [];
var i;
for (i = 0; i < values.length; i++) {
newList.push(values[i]);
newList.push(values[i]);
}
return newList;
}
You could avoid a .push() call by combining them since .push() is variadic.
newList.push(values[i], values[i]);
Other than that, I doubt you'll get much quicker.
You can use each function.
this will reduce your step.
var list=[1,2,3,4,5,6,7,8,9]
var newlist=[];
$.each(list,function(index,data){newlist.push(data);newlist.push(data)})
hope this helps.
May be assignment is faster...
L2 = [];
for (var i=L1.lenght*2; i-->0;) {
L2[i>>1] = L1[i];
}
but this kind of micro-optimization really needs to be profiled on the specific implementations (and I wouldn't be surprised in big differences between different Javascript engines).
I'd keep the most readable way unless this is a key issue (and if it's a key issue they probably Javascript is the wrong tool).
Try: [].concat.call([1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9]).sort();
or more generic:
(function(){
return this.concat(this).sort(function(a,b){return a-b;});}
).call([1,2,10,20,30,40,50,60,70,80,9]);
or just a function:
function(v) {
var i = v.length, nw = [];
while (i--) { nw.push(v[i],v[i]); }
return nw.reverse();
}
or using map
var nw = ([1,2,3,4,5].map(function(a){this.push(a,a)},nw=[]),nw);
I suspect the function is the most efficient.
Assume you have an array:
var arrStateCityAll=['CA_Alameda','CA__Pasadena','CA_Sacramento','NY_Albany','NY_Buffalo','NY_Ithaca']
Is there an easy way using javascript and/or jQuery to filter the arrStateCityAll to get a new array (a subset of arrStateCityAll); something like this:
// return's ['CA_Alameda','CA__Pasadena','CA_Sacramento']
var arrStateCityCA=FilterArray('CA',arrStateCityAll);
Likely you want to do a regex on each item. You can do this with jQuery's grep function.
http://api.jquery.com/jQuery.grep/
You can use javascript's Array.filter.
var arrStateCityAll = ['CA_Alameda','CA__Pasadena','CA_Sacramento','NY_Albany','NY_Buffalo','NY_Ithaca']
var arrStateCityCA = arrStateCityAll.filter( function (element) {
return element.indexOf("CA_") == 0;
});
The mozilla documentation linked to above has a solution for browsers that don't implicitly support filter.
This should work.
var arrStateCityCA = [];
for (var i = 0;i<arrStateCityAll.length;i++){
if (arrStateCityAll[i].substr(0,2) == 'CA'){
arrStateCityCA.push(arrStateCityAll[i]);
}
}
You could use jQuery.grep
var arrStateCityCA =
$.grep(arrStateCityAll,function(el,i){return (el.substring(0,2)=='CA')});
Demo at jsfiddle
To implement you actual FilterArray function as shown in your post you could do
function FilterArray(state,arr){
return $.grep(arr,
function(el,i) {return (el.substring(0,2)==state)}
);
}
This makes a few assumptions.
State is always 2 chars.
State is always the first 2 chars.
And of course remember case-sensitivity (this function is case sensitive) ie 'CA' not equal to 'Ca'.
if you are going to have an undescore between your state and city name, you can split on the underscore and test against the first array value
function getSubSetByState(set,state) {
var result = [];
for(var i=0,l=set.length;i<l;++i) {
if(set[i].split('_')[0] === state) {
result.push(set[i]);
}
}
return result;
}
Use if by giving it the set of places, and then the state you are searching for.
I have an array of items (terms), which will be put as <option> tags in a <select>. If any of these items are in another array (termsAlreadyTaking), they should be removed first. Here is how I have done it:
// If the user has a term like "Fall 2010" already selected, we don't need that in the list of terms to add.
for (var i = 0; i < terms.length; i++)
{
for (var iAlreadyTaking = 0; iAlreadyTaking < termsAlreadyTaking.length; iAlreadyTaking++)
{
if (terms[i]['pk'] == termsAlreadyTaking[iAlreadyTaking]['pk'])
{
terms.splice(i, 1); // remove terms[i] without leaving a hole in the array
continue;
}
}
}
Is there a better way to do this? It feels a bit clumsy.
I'm using jQuery, if it makes a difference.
UPDATE Based on #Matthew Flaschen's answer:
// If the user has a term like "Fall 2010" already selected, we don't need that in the list of terms to add.
var options_for_selector = $.grep(all_possible_choices, function(elem)
{
var already_chosen = false;
$.each(response_chosen_items, function(index, chosen_elem)
{
if (chosen_elem['pk'] == elem['pk'])
{
already_chosen = true;
return;
}
});
return ! already_chosen;
});
The reason it gets a bit more verbose in the middle is that $.inArray() is returning false, because the duplicates I'm looking for don't strictly equal one another in the == sense. However, all their values are the same. Can I make this more concise?
var terms = $.grep(terms, function(el)
{
return $.inArray(el, termsAlreadyTaking) == -1;
});
This still has m * n performance (m and n are the lengths of the arrays), but it shouldn't be a big deal as long as they're relatively small. To get m + n, you could use a hashtable
Note that ECMAScript provides the similar Array.filter and Array.indexOf. However, they're not implemented in all browsers yet, so you would have to use the MDC implementations as a fallback. Since you're using jQuery, grep and inArray (which uses native indexOf when available) are easier.
EDIT:
You could do:
var response_chosen_pk = $.map(response_chosen_items, function(elem)
{
return elem.pk;
});
var options_for_selector = $.grep(all_possible_choices, function(elem)
{
return $.inArray(elem.pk, response_chosen_pk) == -1;
});
http://github.com/danstocker/jorder
Create a jOrder table on termsAlreadyTaking, and index it with pk.
var table = jOrder(termsAlreadyTaking)
.index('pk', ['pk']);
Then you can search a lot faster:
...
if ([] == table.where([{ pk: terms[i].pk }]))
{
...
}
...