I am thinking to use the following function:
function delDups(arr){
var out=[],obj={};
for(var i=0,len=arr.length;i<len;i++){
obj[arr[i]]=0;
}
for(i in obj){
out.push(i);
}
return out;
}
The function is slightly modified, original at be found here
However, I am sure there are some values that will make it crash, and I want to know exactly what values will (so I can do soemthing about it)
Well, if arr isn't an array-like object (e.g. with a length and indexed properties) it will crash.
It may however also not do what you expect whenever the data in arr isn't an array of strings. In that case you'd get an array back with strings only, the original objects and their data type will be lost. But it will not crash...
Crashing is not the only inconvenient outcome from executing code. Also of (perhaps greater) interest are cases where the code runs but returns an incorrect result. Your analysis might be something like:
for(var i=0,len=arr.length;i<len;i++){
In the above, it is assumed that the value of arr.length is a numeric value greater than or equal to zero. If arr does not have a length property, or its value isn't a non-negative number, the for loop will not behave as expected (e.g. it may result in an error or an infinite loop).
obj[arr[i]]=0;
In this line, the result of evaluating arr[i] is used as a property name, so wherever that expression returns something that isn't suitable as a property name, you will get an error, e.g. if arr[i] is an ActiveX object, you can expect unexpected outcomes. If it's a native Object the value will be the result of calling its toString method, which might provide the same value for different objects, or an error, or "just work".
for(i in obj){
will iterate over all enumerable properties of obj, including those it inherits. If an enumerable property is added to Object.prototype, it will turn up in the loop so it's common to use a hasOwnProperty test to filter out inherited properties.
How much you test for errors depends on the environment you expect the code to be used in. If you have reasonable control and have documented the values that are expected to be passed to the function (e.g. array of primitive values) then it is reasonable to do minimal (or no) testing of input values. If someone passes in an ActiveX object instead of an array and it goes belly up, you respond with "RTFM".
On the other hand, if it is known that the code will be used in a library in uncontrolled and widely varying situations, testing that the input has a non-negative, numeric length property seems sensible, as does adding a hasOwnProperty test to the for..in loop.
How much time and effort you put into making your code robust is a function of where you expect it to run, but adding some sensible and obvious checks up front may well save some grief later. So I'd do something like:
function delDups(arr) {
var out=[],obj={};
var len = arr && arr.length;
if (len && len > 0) {
for(var i=0; i<len; i++) {
obj[arr[i]] = 0;
for (i in obj) {
if (obj.hasOwnProperty(i)) {
out.push(i);
}
}
}
return out;
}
Woohoo! I crashed it, where is my prize?
var arr3 = delDups(eval());
Related
I'm looking for an efficient way to test whether or not a JavaScript object is some kind of ordered object. Hopefully someone knows a trick that doesn't involve writing some huge type assessor function. Fortunately, I don't have to deal with IE < 9 so most of the newer methods are OK.
The fundamental problem is this: I need to figure out whether or not to run a for loop or a for...in loop on an object. However, I don't always know if the object is going to be an object literal, an array, a jQuery object, etc.
Here are some of the obstacles I'm running up against:
I clearly can't only use typeof because both arrays and objects return object.
I clearly can't only use Object.prototype.toString.call(collection) because, while arrays do return [object Array], custom ordered collections such as jQuery objects still return [object Object] and the whole reason I want this test is to determine whether I need a for loop or a for...in loop. Using a for...in loop on a jQuery object includes properties that aren't actually part of the collection and messes things up.
I did come up with one idea that looks like this:
function isOrdered(item) {
// Begin by storing a possible length property
// and defaulting to false for whether the item
// is ordered.
var len = item.length, isOrdered = false;
// Functions are an easy test.
if (typeof item === 'function') {
return false;
}
// The Arguments object is the only object I know of
// with a native length property that can be deleted
// so we account for that specifically too.
if (Object.prototype.toString.call(item) === '[object Arguments]') {
return true;
}
// Attempt to delete the item's length property.
// If the item is ordered by nature, we won't get
// an error but we also won't be able to delete
// this property.
delete item.length;
// So if the length property still exists as a
// number, the item must be an ordered collection.
if (typeof item.length === 'number') {
isOrdered = true;
}
// If we originally stored a custom length property,
// put it back.
if (len !== undefined) {
item.length = len;
}
// Return the result.
return isOrdered;
}
So far this technique has passed all of my tests but I'm worried about possible performance hits that could arise from deleting properties on custom objects and I'm not 100% confident I'm not missing something. Does anyone have any better ideas?
You can do something like this:
function isOrdered(item) {
var len = item.constructor.prototype.length;
return len === +len;
}
If 'item' is a js object this will evaluate to false but if it's an array or jQuery collection this will return true.
Note that things other than arrays can have numeric length properties (try checking the length of a function). If you might pass things like integers or functions you will need to do something more complicate, but this should catch all the cases you catch in your posted code. Also, I think deleting the length property can have unpredictable results so I would avoid that approach in favor something that does not mutate the object you are testing.
If your trick is working in Firefox, Chrome, and recent IE, I think it's probably okay....though a bit wonky.
Delete goes through and deletes the key and value from an object, rather than just clearing the reference. It will actually return false if it cannot delete the property, so you could tighten up your logic a bit further.
There's a bit of performance overhead to property deletion, and it does some weird stuff to regular objects currently in memory [see this article], but negligible depending on the browser. It really depends on how many collections you'd be doing this on at a time. For a single array/object/mystery, I think it would be fine. But for a nested collection of thousands of arrays, you might want to look into some other alternative.
Here's a JS perf if you're primarily concerned with performance:
http://jsperf.com/delete-vs-undefined-vs-null/3
update
Alternatively, if you are concerned with Jquery collections vs arrays, you could do something like the following:
if (foo.length != undefined) {
if (Array.isArray(foo)) {
// regular array. Could also do
// foo instanceof Array
}
else if(foo.constructor.toString().match(/jQuery/g)) {
// jquery collection
}
} else {
// not an ordered collection
}
What possible programming use could you have where a sparse array would be better than an (regular) object?
By sparse array I mean one where:
arr = []; //Initialize
arr[0] = 'W';
arr[1] = 'T';
arr[3] = 'F';
console.log(arr[0] !== undefined) //true
console.log(arr[1] !== undefined) //true
console.log(arr[2] === undefined) //true
console.log(arr[3] !== undefined) //true
Or more formally:
An object, O, is said to be sparse if the following algorithm returns true:
1. Let len be the result of calling the [[Get]] internal method of O with argument
"length".
2. For each integer i in the range 0≤i<ToUint32(len)
a. Let elem be the result of calling the [[GetOwnProperty]] internal method of O
with argument ToString(i).
b. If elem is undefined, return true.
3. Return false.
ECMA 262 5.1 - 15.4 Array Objects
Moreover, the ECMA 262 5.1 Standard further defines length specifically as:
The length property of this Array object is a data property whose value is always numerically greater than the name of every deletable property whose name is an array index.
So the example above, arr.length === 4 despite there only being three elements defined.
In fact, according to the standard, any Number greater than 3 is a valid length for arr, including Math.PI.
Consequently, does this mean that no one should use:
for(var i=0; i<arr.length; i++)
//Cannot trust arr[i] exists
and instead it would be more appropriate to use
for(key in arr)
//Always exists
I've never encountered an intentional one in the wild, and really only began thinking about it while reading an odd Q&A here, and now I'm a little unsettled.
I've long known that there's not a neat way to remove an element from an Array, but now I'm even more confused as to why you would intentionally leave a hole, let alone define a standard where length can be any number greater than the last defined element.
If I wanted random key value pairs, I'd use an Object. If I want to be able to cleanly iterate, I use an Array. Am I missing something?
Note, I'm looking for a specific use case, or a class of generalized use cases not a reference to the standards, or an opinion. I know it's allowed, and I already have an opinion. :)
To add a test or see some of the ones I've seen where the Array standard works in unexpected ways, check out this fiddle
Sorry if this is a bit abstract. Been thinking about it all night.
One possible use-case for sparse arrays that I've come across in real usage is for a heat-map.
Start with your map being an empty array of X × Y elements. Load your data, and populate it into the map by incrementing the array elements at the relevant co-ords.
Another similar example might be a battleship game, where boats are placed into an empty grid by populating the array elements at the appropriate co-ordinates.
That's not to say this is this only way to do this, or even the best way -- both examples can quite easily be achieved without using a sparse array -- but the question was asking for use cases, so there you go.
implementing the mathematical idea of 'sparse array' with JavaScript plain object ({}):
source={}
source[1000000] = 1; source[2000000]=2; source[3000000]=3
start = new Date();
target = (function(){
var ret = {};
Object.keys(source).forEach(function(key){
ret[key] = source[key]*source[key]
});
return ret
})();
end = new Date();
document.getElementById('runtime').textContent = end - start;
document.getElementById('result').textContent = JSON.stringify(target);
........................ result ..........................
<div id="result"></div>
...................... run time ..........................
<div id="runtime"></div>
implementing the mathematical idea of 'sparse array' with JavaScript array ([]):
source=[]
source[1000000] = 1; source[2000000]=2; source[3000000]=3
start = new Date();
target = source.map(function(u){ return u*u; });
end = new Date();
document.getElementById('runtime').textContent = end - start;
//document.getElementById('result').textContent = JSON.stringify(target);
console.log(target)
........................ result ..........................
<div id="result">see console</div>
...................... run time ..........................
<div id="runtime"></div>
The runtime is show to be zero in the first snippet (below what can be properly measured unless you run it repeatedly). In the 2nd snippet the runtime is about 100 milliseconds on my machine.
Would you like to "pay" for a little less typing with such differences in runtime? And if you don't do the things that have this horrible runtime (do you know what they are??) what remains then of the saved typing? the answer to the last question is: nothing.
{} is the correct choice. [] is the wrong choice. IMHO.
related question
I have some simple Javascript looping through an array of items (Tridion User Groups) to check if the user is a member of a specific group.
I can easily code around the issue shown below ( see && extensionGroup !== 'true') but I want to understand why the isArray = true is counted as a value in the array - any ideas?
The screenshot below demonstrates that the value extensionGroups has been set thus
var extensionGroups = ["NotEvenARealGroup", "Author", "ExampleGroupAfterOneUserIsActuallyIn"];
but returns the isArray value as a 4th value?
updated to show images a little clearer
You're using for in to iterate an array; don't do that. Use for (or forEach):
for(var i = 0; i < extensionGroups.length; i++) {
var extensionGroup = extensionGroups[i];
// ...
}
The reason this fails is because for in is used to iterate over an object's properties in JavaScript. Iterating over an array in this way means you get anything else assigned to it, such as this property or length.
And if you're able to use Array#forEach, it's probably most appropriate here:
extensionGroups.forEach(function(extensionGroup) {
// ...
});
For..in, technically speaking, doesn't iterate through values. It iterates through property names. In an array, the values ARE properties, under the hood. So when you iterate over them with for..in you get funky stuff like that happening.
Which highlights my next point: don't use for..in. Don't use it for arrays -- don't use it for anything, really. Ok -- maybe that's going a bit too far. How about this: if you feel the need to use for..in, think hard to see if it's justifiable before you do it.
When iterating over a string or array (or anything else with a length property), I've always used a loop like this:
var foo = [...];
var i;
for(i=0; i<foo.length; i++) {
// do something
}
However, I just encountered someone who did this:
var foo = [...];
var fooLen = foo.length;
var i;
for(i=0; i<fooLen; i++) {
// do something
}
He said he thought the ".length" was recalculating the length, thus the loop would recalculate the length of the string/array over and over, so by saving its length to a variable it would be more optimized.
I always assumed length was just a value property because of the way it's used (it's not "asdf".length(), it's "asdf".length) but is this not the case?
There are some situations where putting the length into a local variable is faster than accessing the .length property and it varies by browser. There have been performance discussions about this here on SO and numerous jsperf tests. In a modern browser, the differences were not as much as I thought they would be, but they do exist in some cases (I can't seem to find those previous threads).
There are also different types of objects that may have different performance characteristics. For example, a javascript array may have different performance characteristics than the array-like object returned from some DOM functions like getElementsByClassName().
And, there are some situations where you may be adding items to the end of the array and don't want to be iterating through the items you add so you get the length before you start.
From MDC
for (var i = 0; i < a.length; i++) {
// Do something with a[i]
}
This is slightly inefficient as you are looking up the length property
once every loop. An improvement is this:
for (var i = 0, len = a.length; i < len; i++) {
// Do something with a[i]
}
Maybe not much of a difference with "regular" arrays, but for something like "node.children.length" I would err on the safe side and call it only once. CoffeeScript does that for you automatically.
Note that there is an actual difference in behaviour if the length can change during the loop.
It depends on if you are changing the foo's length.
var foo = [1,2,3];
while(foo.length){
foo.shift();
}
Obviously the code is keeping track of the foo's length, not simply remembering a value.
You can assign the length as a number in the loop.
for(i=0, L=foo.length;i<L; i++) {
// do something to foo[i]
}
I just tried the for...in statement in Javascript.
This gives no error:
var images = document.getElementsByTagName('img');
for(x in images){
document.write(images[x]) + " ");
}
However, this does what it should but gives an error in the FF error console.
for(x in images){
images[x].style.visibility="visible";
}
This made me VERY curious as to what's going on.
Doing this:
for(x in images){
document.write(x);
}
...gave me this:
01234567891011121314151617lengthitemnamedItem
What's there at the end? I assume this makes the document.images / document.getElementsByTagName('img') array not suitable to use with the for...in statement since the values for x at the end won't correspond to an image? Maybe a for loop is better?
Don't iterate through arrays with for ... in loops. Use an index:
for (var i = 0; i < arr.length; ++i) {
// images[i] ...
}
The for ... in construct isn't wrong, it's just not what you want to do; it's for when you want to iterate through all properties of an object. Arrays are objects, and there are other properties besides the semantically-interesting indexed elements.
(Actually what comes back from getElementsByTagName isn't really an Array; it's a node list. You can however treat it like an array and it'll generally work OK. The same basic caveat applies for for ... in in any case.)
for..in does not loop through the indexes of an array, it loops through the enumerable property names of an object. It happens that the only enumerable properties array instances have, by default, are array indexes, and so it mostly works to think it does array indexes in limited situations. But that's not what for..in does, and misunderstanding this will bite you. :-) It breaks as soon as you add any further properties to the array (a perfectly valid thing to do) or any library you're using decides to extend the array prototype (also a valid thing to do).
In any case, what you get back from document.getElementsByTagName isn't an array. It's a NodeList. Your best bet for iterating through NodeLists is to use an explicit index a'la Pointy's answer -- e.g., a straight counting loop:
var i;
for (i = 0; i < list.length; ++i) {
// ... do your thing ...
}
Somewhat off-topic because it doesn't relate to your NodeList, but: When you are actually working with a real array, because arrays in JavaScript are sparse, there is applicability for for..in, you just have to be clear about what you're doing (looping through property names, not indexes). You might want to loop only as many times as the array has actual entries, rather than looping through all the indexes in the gaps in the sparse array. Here's how you do that:
var a, name;
a = [];
a[0] = "zero";
a[10000] = "ten thousand";
for (name in a) {
// Only process this property name if it's a property of the
// instance itself (not its prototype), and if the name survives
// transition to and from a string unchanged -- e.g., it's numeric
if (a.hasOwnProperty(name) && parseInt(name) == name) {
alert(a[name]);
}
}
The above only alerts twice, "zero" and "ten thousand"; whereas a straight counting loop without the checks would alert 10,001 times (mostly saying "undefined" because it's looping through the gap).
The problem with the for ... in construct is that everything from the prototype(s) gets included in the enumeration.
What your output is showing is basically every attribute of images, which is an array object. So you get
all the elements in your array, i.e. the numbers that appear in your output.
all the properties available on your array. This is why you see length in your output for example.
Your code breaks due to number 2, since a functions does not have a style attribute
So, yes, as Pointy shows, the correct way to iterate over the elements of an array in JavaScript is by using a for loop.