Which is faster for finding element in javascript array? - javascript

I'm a noob and wrote a whole program without knowing the easy way to find an element in an array...
my_array.indexOf("find this value");
Is indexOf a lot better than storing how many elements are in an array, and looping through the array until you find the element you want? I could have simplified my code alot.
I tried to make my lookups constant time by using multiple arrays, and storing the keys. It makes insertions/deletes slow because I have to update the keys though.
Should I have just used indexOf?
Thanks

The vast majority of the time you are much better off to use a native function that has been optimized over whatever solution you come up with. Aside from that, however, you said something about storing the amount of elements in the array. Not sure why you did that when arrays have a .length property.

Javascript basically has two types of collections: Arrays and hashmaps. Both are a bit special. The hash map is just an object with named properties. The keys are strings that you use to access the values directly. Here's an example:
// create the hash map
var hashMap = {};
// add a value
var key = "John Dillinger";
hashMap[key] = "Criminal";
// retrieve the value
var stuff = hashMap[key];
Javascript arrays have a double functionality. They are of course arrays, but are also stacks. A stack follows the "last in - first out" rule. Here's an example of an array and a stack:
// Array example
var anArray = []; // or: var anArray = new Array();
anArray[0] = "some value";
alert(anArray[0]); // pops up "some value"
// Stack example
var stack = [];
stack.push("first");
stack.push("second");
alert(stack.pop()); // pop up "second"
Finally, for some problems a linked list could come in handy. For that you use an object. Something like this:
var linkedList = {value: "stuff"};
linkedList.next = {value: "other"};
linkedList.next.next = {value: "yet another value"};
// Traverse the list
var node = linkedList;
while(node) {
alert(node.value)
node = node.next;
}
Given the problem that you describe, I would use a hash map. Just remember to choose the correct collection type for any given problem.

You could use a hash table implementation in javascript to map values to array indices.

Native functions should be faster since it would be the runtime-engine precompiled code.
However, indexOf wasn't implemented until version 1.6, meaning it doesn't work in jscript/IE afaik.
But I would just prototype a workaround for it in that case. native functions is usually your best option.
In your case however, it seems that you want a hashmap, which in js is just a regular object as Helgi pointed out.

It's probable that the implementation of the indexOf method just loops over the array until it finds the requested value because in the general case that's about all you can do. Using it would clean up your code but is unlikely to make it faster. (There are faster ways of searching arrays but they carry certain restrictions and/or up-front costs.)
You should use the right data structure for the job. Arrays are for situations where order is important. If you find yourself searching through them a lot you should probably be using a hash instead. Hashes are unordered but lookups happen in constant time (no searching).

I've implemented javascript HashMap which code can be obtained from http://github.com/lambder/HashMapJS/tree/master
Here is the code:
/*
=====================================================================
#license MIT
#author Lambder
#copyright 2009 Lambder.
#end
=====================================================================
*/
var HashMap = function() {
this.initialize();
}
HashMap.prototype = {
hashkey_prefix: "<#HashMapHashkeyPerfix>",
hashcode_field: "<#HashMapHashkeyPerfix>",
initialize: function() {
this.backing_hash = {};
this.code = 0;
},
/*
maps value to key returning previous assocciation
*/
put: function(key, value) {
var prev;
if (key && value) {
var hashCode = key[this.hashcode_field];
if (hashCode) {
prev = this.backing_hash[hashCode];
} else {
this.code += 1;
hashCode = this.hashkey_prefix + this.code;
key[this.hashcode_field] = hashCode;
}
this.backing_hash[hashCode] = value;
}
return prev;
},
/*
returns value associated with given key
*/
get: function(key) {
var value;
if (key) {
var hashCode = key[this.hashcode_field];
if (hashCode) {
value = this.backing_hash[hashCode];
}
}
return value;
},
/*
deletes association by given key.
Returns true if the assocciation existed, false otherwise
*/
del: function(key) {
var success = false;
if (key) {
var hashCode = key[this.hashcode_field];
if (hashCode) {
var prev = this.backing_hash[hashCode];
this.backing_hash[hashCode] = undefined;
if(prev !== undefined)
success = true;
}
}
return success;
}
}
//// Usage
// creation
var my_map = new HashMap();
// insertion
var a_key = {};
var a_value = {struct: "structA"};
var b_key = {};
var b_value = {struct: "structB"};
var c_key = {};
var c_value = {struct: "structC"};
my_map.put(a_key, a_value);
my_map.put(b_key, b_value);
var prev_b = my_map.put(b_key, c_value);
// retrieval
if(my_map.get(a_key) !== a_value){
throw("fail1")
}
if(my_map.get(b_key) !== c_value){
throw("fail2")
}
if(prev_b !== b_value){
throw("fail3")
}
// deletion
var a_existed = my_map.del(a_key);
var c_existed = my_map.del(c_key);
var a2_existed = my_map.del(a_key);
if(a_existed !== true){
throw("fail4")
}
if(c_existed !== false){
throw("fail5")
}
if(a2_existed !== false){
throw("fail6")
}
Bon Appétit,
Lambder

Related

Iterating over and comparing properties of two arrays of objects

I have set up a HBS helper which takes in two arrays of objects (users privileges). What I want to do is compare them and inject back into the template the privileges the user does and doesn't have.
Presently I can compare the names of the privileges with the following code:
hbs.registerHelper('selected', function(option, value){
var i;
var j;
var privName;
var userPriv;
var privObj = new Object();
var privArray = [];
for(i in option){
console.log('each ' + JSON.stringify(option[i]));
privName = option[i].privname;
for (y in value){
if(privName == value[y].privname){
userPriv = value[y].privname;
console.log('user has the following privileges', value[y].privname);
privObj = new Object();
privObj.name = userpriv;
privObj.id = value[y]._id;
privObj.state = 'selected';
privArray.push(privObj);
} else if (privName != value[y].privname){
console.log('user doesnt have priv ', privName);
privObj = new Object();
privObj.name = option[i].privname;
privObj.id = option[i].id;
privObj.state = '';
privArray.push(privObj);
}
}
}
console.log('privileges array ', privArray);
return privArray;
});
This works OK when the user only has one privilege, however when the user has more than one, for example two privileges, it returns the privileges twice. If the user has 3, thrice and so on. I know this is because the array is looping again because their are 2, 3 etc in the .length. However I can't seem to find an adequate solution.
Any help?
P.S. it would be nice if the Array.includes() method allowed you to search object properties.
The problem creating new objects the way you did is that for each property you add to your privilege-entity you will have to return to that function and set that property as well. You can instead just add/alter the state property of the existing objects:
hbs.registerHelper('selected', function(option, value) {
var names = option.map(function(opt) {
return opt.privname;
});
value.forEach(function(val) {
val.state = names.indexOf(val.privname) >= 0 ? 'selected' : '';
});
return value;
});
Basically:
The variable names is being mapped to be an array only with the privnames. You can check by using console.log(names).
The Array.forEach() function is helpful in this case because you just need to iterate over each object inside value and set its state-property.
To check if the privname exists, you just need to check the index in the previous names-mapped-array. For such a simple thing I used ternary operator (?:).
Finally, you return value, which is the array containing the objects you had updated.

javascript push inside init method not updating array length correctly

I'm trying to create an array of objects with an init method, and when I push an object into an array, it should push another object into the array, while keeping track of the length of the array.. The problem is that it doesn't update the length of the array until all the objects have been added, so when each object tries to grab the length, they all get 0.
How can I have it update the length in this process?
here's the jfiddle: http://jsfiddle.net/bg3Vg/13/
As you can see it gives a message showing that the grouptotal is 5, but it seems as if the total counts up from the last pushed object to the first.. I need it to work in the correct order so that the last pushed object can retrieve the correct length.
var colorGroup = [];
var grouptotal = 0;
colorGroup.push(new groupdata(0) );
alert(grouptotal+","+colorGroup[colorGroup.length-1].parent);
function groupdata(parent) {
this.parent = parent;
this.refnum;
this.init = function()
{
grouptotal++;
this.refnum = colorGroup.length;
if(grouptotal<5)colorGroup.push(new groupdata( this.refnum ) );
}
this.init();
}
edit:
ok, I found a way to solve my problem I think. Let me know how horrid this solution is..http://jsfiddle.net/EqAqv/1/
var colorGroup = [];
var grouptotal = 0;
var colorGroupWait = [];
colorGroup.push(new groupdata(0) );
while(colorGroupWait.length>0){
var newcolorGroup = colorGroupWait.shift();
colorGroup.push(new groupdata(newcolorGroup) );
}
alert(grouptotal+","+colorGroup[colorGroup.length-1].parent);
alert(grouptotal+","+colorGroup[colorGroup.length-2].parent);
function groupdata(parent) {
this.parent = parent;
this.refnum;
this.init = function()
{
grouptotal++;
this.refnum = colorGroup.length;
if(colorGroup.length<5)colorGroupWait.unshift( this.refnum );
}
this.init();
}
JavaScript arrays do update the length property as soon as you push something on to them. The problem is that you're recursively calling the constructor, so the statement this.refnum = colorGroup.length is getting executed for each initialization BEFORE any push occurs.
In other words, JavaScript is working as expected.
Is there any particular reason you are doing it in this convoluted manner? It be more straightforward (and achieve the result you're looking for) if you just did it like this:
for(grouptotal=0; grouptotal<5; grouptotal++){
colorGroup.push( new groupdata(grouptotal) );
}
Also, it is convention in JavaScript to name object constructors with a capital letter. So, while groupdata is not invalid syntax, it is confusing: you should consider naming it Groupdata.
#EthanBrown has already pointed out the problems. Here is a solution that puts all the logic in the constructor, and avoids the problem of pushing the instance after having it created from a wrong number.
function GroupData(parentnum) {
this.parentnum = parentnum;
this.refnum = GroupData.colorGroup.length;
GroupData.colorGroup.push(this);
if (GroupData.colorGroup.length < 5)
new GroupData(this.refnum);
}
GroupData.colorGroup = [];
var root = new GroupData(0);
alert(GroupData.colorGroup.length+", "
+GroupData.colorGroup[GroupData.colorGroup.length-1].parentnum);

create array, use $.each to loop, and add value if not in array

I have a bunch of .defined in a text and want to create an array of unique values with javascript. So basically, for each anchor with class defined, I want to first check the array to see if the pair already exists. If exists, go to next anchor. If does not exist, add to array. This is the code I have tried using, but it does not remove duplicate values.
var arr = new Array();
y = 0;
$("a.defined").each(function() {
var spanishWord = this.text;
var englishWord = this.title;
if(spanishWord in arr) {
console.log("do nothing");
} else {
arr.push({key: spanishWord, value: englishWord});
y++;
}
For example, I have these tags in the text:
<a title="read">Leer</a>
<a title="work">Trabajar</a>
<a title="like">Gustar</a>
<a title="read">Leer</a>
<a title="sing">Cantar</a>
<a title="like">Gustar</a>
And I would like my array to look like:
Spanish Word | English Word
Leer read
Trabajar work
Gustar like
Cantar sing
but instead it looks like:
Spanish Word | English Word
Leer read
Trabajar work
Gustar like
Leer read
Cantar sing
Gustar like
Any ideas?
I would do this in two steps.. one to eliminate duplicates, and one to create the array:
http://jsfiddle.net/uN4js/
var obj = {};
$('a.defined').each(function() {
obj[this.text] = this.title;
});
var arr = [];
for (var prop in obj) {
if (obj.hasOwnProperty(prop))
arr.push({key: prop, value: obj[prop]});
};
console.log(arr);
If the object is sufficient and you don't really need an array, you could stop after the object is created.
You can probably just use a javascript object here:
var dict = {};
y = 0;
$("a.defined").each(function() {
var spanishWord = this.text;
var englishWord = this.title;
dict[spanishWord] = englishWord;
}
And there isn't really a need for unique checks, since newer values will just overwrite the older ones. If you don't want that behaviour, you can do this:
var dict = {};
y = 0;
$("a.defined").each(function() {
var spanishWord = this.text;
var englishWord = this.title;
if (!(spanishWOrd in dict)) {
dict[spanishWord] = englishWord;
}
}
Javascript's in operator is not used for testing inclusion, it's used for iteration in a for .. in .. loop.
Other answers have suggested correctly that you need either .indexOf or JQuery's $.inArray method to test inclusion in an array, but there is a simpler (and faster) way of solving this problem: use a dictionary of key/value pairs instead!
var dict = {};
$("a.defined").each(function() {
dict[this.textContent] = this.title;
});
Afterwards, you can use for key in dict to iterate over the list of unique Spanish words, and dict[key] to get the corresponding English translation.
Try this:
JavaScript
var arr = {};
$("a").each(function() {
var spanishWord = this.text;
var englishWord = this.title;
if(spanishWord in arr) {
console.log("do nothing");
} else {
arr[spanishWord] = englishWord;
}
});
console.log(arr);

access javascript array element by JSON object key

I have an array that looks like this
var Zips = [{Zip: 92880, Count:1}, {Zip:91710, Count:3}, {Zip:92672, Count:0}]
I would like to be able to access the Count property of a particular object via the Zip property so that I can increment the count when I get another zip that matches. I was hoping something like this but it's not quite right (This would be in a loop)
Zips[rows[i].Zipcode].Count
I know that's not right and am hoping that there is a solution without looping through the result set every time?
Thanks
I know that's not right and am hoping that there is a solution without
looping through the result set every time?
No, you're gonna have to loop and find the appropriate value which meets your criteria. Alternatively you could use the filter method:
var filteredZips = Zips.filter(function(element) {
return element.Zip == 92880;
});
if (filteredZips.length > 0) {
// we have found a corresponding element
var count = filteredZips[0].count;
}
If you had designed your object in a different manner:
var zips = {"92880": 1, "91710": 3, "92672": 0 };
then you could have directly accessed the Count:
var count = zips["92880"];
In the current form, you can not access an element by its ZIP-code without a loop.
You could transform your array to an object of this form:
var Zips = { 92880: 1, 91710: 3 }; // etc.
Then you can access it by
Zips[rows[i].Zipcode]
To transform from array to object you could use this
var ZipsObj = {};
for( var i=Zips.length; i--; ) {
ZipsObj[ Zips[i].Zip ] = Zips[i].Count;
}
Couple of mistakes in your code.
Your array is collection of objects
You can access objects with their property name and not property value i.e Zips[0]['Zip'] is correct, or by object notation Zips[0].Zip.
If you want to find the value you have to loop
If you want to keep the format of the array Zips and its elements
var Zips = [{Zip: 92880, Count:1}, {Zip:91710, Count:3}, {Zip:92672, Count:0}];
var MappedZips = {}; // first of all build hash by Zip
for (var i = 0; i < Zips.length; i++) {
MappedZips[Zips[i].Zip] = Zips[i];
}
MappedZips is {"92880": {Zip: 92880, Count:1}, "91710": {Zip:91710, Count:3}, "92672": {Zip:92672, Count:0}}
// then you can get Count by O(1)
alert(MappedZips[92880].Count);
// or can change data by O(1)
MappedZips[92880].Count++;
alert(MappedZips[92880].Count);
jsFiddle example
function getZip(zips, zipNumber) {
var answer = null;
zips.forEach(function(zip){
if (zip.Zip === zipNumber) answer = zip;
});
return answer;
}
This function returns the zip object with the Zip property equal to zipNumber, or null if none exists.
did you try this?
Zips[i].Zip.Count

Count how many identical elements in 2 arrays occur

I have a form and as a result of that form I have two arrays - one for personA's details and one for personB's details. One part of the form is a bunch of checkboxes of dates - the dates are a fixed price, but booking for two on the same date is a reduced price.
What I want to do is compare personA's array with personB's array and for any duplicates issue a reduced rate.
The rate will be calculated into two variables - total full price dates (in £s) and total reduced price dates (in £s).
Here is my code so far (this occurs on the checkboxes onclick):
function processDates(){
var contentsA, valsA = [], dates_A = document.forms['registerForm']['personADates[]'];
for(var iA=0,elmA;elmA = dates_A[iA];iA++) {
if(elmA.checked) {
valsA.push(elmA.value);
}
}
contentsA = valsA.join(', ');
var contentsB, valsB = [], dates_B = document.forms['registerForm']['personBDates[]'];
for(var iB=0,elmB;elmB = dates_B[iB];iB++) {
if(elmB.checked) {
valsB.push(elmB.value);
}
}
contentsB = valsB.join(', ');
}
With my own http://phrogz.net/JS/ArraySetMath.js you could do:
// If the original order of the elements is not important
var common = array1.intersect( array2 );
// If the original order of the elements is important
var common = array1.unsortedIntersect( array2 );
With the larger JS.Set you could do:
// If the original order of the elements is not important
var common = (new JS.SortedSet(array1)).intersect(new JS.SortedSet(array2));
// If the original order of the elements is important
var common = (new JS.Set(array1)).intersect(new JS.Set(array2));
It is not clear to me what the values in your arrays actually are; are these dates entered by the user in plain text? If so, you'll want to ensure that they are normalized so that they are comparable and guaranteed to match.
If these are more than simple comparable objects (e.g. instead of String or Date instances you are creating arrays complex Objects) then my library allows you to supply a comparison function so you can determine which are equal. See the library for more details on this.
Instead of using an array you could use an object (warning... untested code):
function processDates(){
var valsA = {}, dates_A = document.forms['registerForm']['personADates[]'];
for(var iA=0,elmA;elmA = dates_A[iA];iA++) {
if(elmA.checked) {
valsA[elmA.value] = 1; // Store values
}
}
var samedate = [];
var dates_B = document.forms['registerForm']['personBDates[]'];
for(var iB=0,elmB;elmB = dates_B[iB];iB++) {
if(elmB.checked) {
if (valsB[elmB.value])
{
// Here we found a date that's present in both
samedate.push(elmB.value);
}
}
}
// Here samedate is a list of all dates listed in both A and B
}
If functional javascript is an option:
_.sum = function(array) {
return _.reduce(array, function(memo, val) {
return memo + val;
}, 0);
};
var duplicates = _.intersect(valsA, valsB);
var fullPrice = _.sum(valsA.concat(valsB));
var reducedPrice = fullPrice - discountScalar * _.sum(duplicates);
Relies on underscore.js for easy cross browser implentation of functional methods. Most of these can be implemented with array.reduce & array.map on modern browsers.

Categories

Resources