Javascript Arrays - Alternative to arr[arr.indexOf()]? - javascript

var arr = [foo,bar,xyz];
arr[arr.indexOf('bar')] = true;
Is there an easier way to do this in JS?

You could just use objects.
var obj = {foo: true, baz: false, xyz: true};
obj.baz = true;

All values in that array are already undefined. (You edited your post) I don't know why you are complaining about 2 whole lines of code though.
Short answer no, you can't access an index of an array without knowing the index.
One IE safe way would be to create a prototyped function which lets you set it easily:
Array.prototype.setKeysWithValue = function(keyValue,newValue)
{
var i;
for (i in this)
{
if (this[i] === keyValue)
this[i] = newValue;
}
}
This can then be used like:
var arr = ['foo','bar','xyz'];
arr.setKeysWithValue('bar',true);

In your example you would really only be replacing "bar" with true; your resultant array would look like [foo, true, xyz].
I think it's assumed that what you're asking for is an alternative to having one set of arrays for keys and one set of arrays for values, of which there is no better way.
However, you can use an associative array, or objects, to maintain a key value pair.
var f = false, t = true;
// associative array
var arr = new Array();
arr["foo"] = arr["bar"] = arr["foobar"] = f;
arr["bar"] = t;
// object
var obj;
obj = {"foo":f, "bar":f, "foobar":f};
obj["bar"] = t;
// the difference is seen when you set arr[0]=t and obj[0]=t
// the array still maintains it's array class, while the object
// is still a true object
It's important to realize a few things if you use this method:
the array.length no longer applies, as it only accounts arrays by numerical index, it does not count array properties, which is what the keys in an associative array are
looping through keys/properties becomes a little more difficult since the Array object should have some native properties/methods
you may only have one key/value pair. The array structure you listed would be allowed to have [foo, bar, xyz, bar, foobar, foo], where the index should return the first occurrence in anything browser other than IE<=8
One other way to do what you were specifically asking is:
Array.prototype.replace = function(from,to){ this[this.indexOf(from)]=to; };
Array.prototype.replaceAll = function(from,to){ while(this.indexOf(from)>=0){this[this.indexOf(from)]=to;} };
var arr = new Array();
arr=[ "foo", "bar", "foobar", "foo" ];
arr.replace("bar",true); // [ "foo", true, "foobar", "foo" ]
arr.replaceAll("foo",false); // [ false, true, "foobar", false ]

Related

Array of objects: accessing an object's value without iterating through the array

I have an array of similarly structured objects:
var my_arr = [{property1: some_value, property2: another_value}, {}, {}, ...];
Currently, to find the object containing a target value, I iterate through each element of the array:
var my_obj, target_value;
for (let obj_in_arr of my_arr) {
if (obj_in_arr.property1 === target_value) {
my_obj = obj_in_arr;
break;
}
}
Is there a faster way? How can I access the object with the target value directly, without resorting to iteration?
If you prepopulate a new Map all subsequent searches will be in O(1)
const objMap = new Map()
for (let obj of my_arr) {
objMap.set(obj.property1, obj)
}
function getObject(target, map) {
return map.get(target)
}
I think you need to iterate the array anyway, but you can try _.findIndex of underscore.js
http://underscorejs.org/#findIndex
If you only need to find a value once, then iteration is really the only way.
If you will want to find many values in the array, you could create an object keyed on your target property to serve as a lookup table:
var lookup = {};
for (var i = 0; i < my_arr.length; i++) {
lookup[my_arr[i].property1] = my_arr[i];
}
That front loads some work, but could save you time ultimately if you have many lookups to make.
Lookups would be as simple as:
my_obj = lookup[target_value];
If you have access to es2015 you could make your lookup table generation a little more concise:
const lookup = my_arr.reduce((m, v) => (m[v.property1] = v, m), {});
this will still iterate through the array but you could use the native js find function.
const objArray = [{ val: 1}, { val: 2}];
const targetObj = objArray.find((obj) => obj.val == 2 ) // { val: 2}

JavaScript Object declaration by combining two different keys

I'm not sure if it's possible or not. So here I'm looking for an answer.
Is there any way to declare an object like:
var objectName = {
key1 : 'value1',
key2,key3 : 'value2;
}
I'm trying to combine key2 and key3 together.
If you don't want to assign the values like Patric Roberts says, create an array of keys and set the same value to those keys like so:
var obj = {};
obj['key1'] = 1;
// Array of keys
var arrayOfKeys = ['key2','key3','key4'];
// Common value for keys in array
var val = 2;
// Iterate the array
for(var k in arrayOfKeys) {
obj[arrayOfKeys[k]] = val;
}
console.log(obj);
You can also check this answer
You could use a slightly different approach, which will work on primitive values, too, by adding an object for keeping the same reference to different keys in the array. For example, you have a value object, like
temp = {
value: 42
}
and an array, like
object = {
key2: temp,
key3: temp
}
then you can use the keys independently for the value of the referenced object.
To change the value, you need to address it with
object.key2.value = 2000;
console.log(object.key3.value); // 2000

For a deep copy of a JavaScript multidimensional array, going one level deep seems sufficient. Is this reliably true?

Note: I'm only a novice coder, so there might be a glaring error or misconception at the heart of this question.
Essentially, I need to deep copy multidimensional arrays 'by value' in JavaScript to an unknown depth. I thought this would require some complex recursion, but it seems that in JavaScript you only need to copy one level deep in order to copy the whole array by value.
As an example, here is my test code, using a deliberately convoluted array.
function test() {
var arr = [ ['ok1'],[],[ [],[],[ [], [ [ ['ok2'], [] ] ] ] ] ];
var cloned = cloneArray(arr);
arr = ''; // Delete the original
alert ( cloned );
}
function cloneArray(arr) {
// Deep copy arrays. Going one level deep seems to be enough.
var clone = [];
for (i=0; i<arr.length; i++) {
clone.push( arr[i].slice(0) )
}
return clone;
}
In my running of this test (latest stable Chrome and Firefox on Ubuntu), even the deepest parts of the array seem to be successfully copied by value in the clone, even after the original is deleted, despite the fact that the slice() "copying" only went one layer deep. Is this the standard behaviour in JavaScript? Can I depend on this to work for older browsers?
Your test is flawed for whether a true copy is being made which makes your conclusion incorrect that you are getting a full copy of all the data in the nested arrays. You are only doing a two level copy, not an N level copy.
Javascript is a garbage collected language so you don't actually delete variables or objects and, even if you tried that doesn't affect the same variable if it's being referenced somewhere else in your code. To see if you truly have a completely independent copy, try nesting an object two levels deep and then change a property on the object in the original array. You will find that the same object changes in the cloned array because you aren't doing a deep clone. Both arrays have a reference to the exact same object.
Here's an example.
function cloneArray(arr) {
// Deep copy arrays. Going one level deep seems to be enough.
var clone = [];
for (i=0; i<arr.length; i++) {
clone.push( arr[i].slice(0) )
}
return clone;
}
var x = [[{foo: 1}]];
var y = cloneArray(x);
x[0][0].foo = 2;
// now see what the value is in `y`
// if it's 2, then it's been changed and is not a true copy
// both arrays have a reference to the same object
console.log(y[0][0].foo); // logs 2
The same result would happen if the third level was another array too. You will have to recursively traverse every element that is an object type and then clone that object itself to get a complete clone of everything in the nested arrays.
If you want code that will do a deep copy (to an arbitrary level) and work for all data types, see here.
FYI, your cloneArray() function assumes that all first level members of your array are arrays themselves and thus doesn't work if it contains any other type of value.
Array.prototype.slice is not suitable for cloning arrays
This should work well for you
function deepClone(arr) {
var len = arr.length;
var newArr = new Array(len);
for (var i=0; i<len; i++) {
if (Array.isArray(arr[i])) {
newArr[i] = deepClone(arr[i]);
}
else {
newArr[i] = arr[i];
}
}
return newArr;
}
If you need to support older browser, make sure to use this polyfill (via MDN)
if(!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
Your code does not work:
If your array contains other variables such as numbers or strings (not only arrays), it will fail, because it will call arr[i].slice(), but since that arr[i] is a number, it hasn't got any .slice property and this will throw an error.
Your function will, in any case, leave all the references to objects and other stuff inside the array alive. So you will not actually obtain a copy of your array.
Example:
var a = [1,2, [11,13], ['ok']];
var b = cloneArray(a);
> TypeError: undefined is not a function // because numbers have no .slice method
Solution:
To copy an array you will need to make a deep copy of it. Since that creating a deep copy will need a function that uses recursion to deep copy any object or array inside of the main one, the easiest way to do it is by using jQuery and its .extend method, which performs a deep copy of the array, see here for more info.
var a =[[1], [2], [3]];
var b = $.extend(true, [], a);
b[0][0] = 99;
a[0][0] // still 1
Here's my recursive approach to "cloning" a multidimensional array. It runs all the way down to the deepest levels:
if ( !Array.clone )
{
Array.prototype.clone = function()
{
var _arr = ( arguments[0] == null ) ? [] : arguments[0] ;
for( var _p = 0 ; _p < this.length ; _p++ )
{
if ( this[_p] instanceof Array )
{
var _sub = [] ;
this[_p].clone( _sub ) ;
_arr.push( _sub.slice() );
}
else _arr.push( this[_p] );
}
return _arr ;
}
}
Now try this code:
var _a = [ "a", "b", [ "c", "d", [ "e", "f" ] ] ];
var _b = _a.clone();
console.log( _b );
Vars _a and _b are two distinct objects: if you remove an element from var _b, then var _a would not be affected.

Return unique keys from JavaScript Array

I'm hoping my question is using the correct terminology...
Can someone explain to me how I can perform the following:
If I have an array consisting of:
Object { id=1498, brandName="Booths", quality="Standard"}
Object { id=1499, brandName="Booths", quality="Standard"}
How can I iterate throughout that array and return another array of distinct 'keys'?
Ultimately I want an array which would return something like:
[id,brandName,quality] (but the original array is going to return different keys at different times.
Have I made sense?
You can use Object.keys:
var a1 = [{ id:1498, brandName:"Booths", quality:"Standard"},
{ id:1499, brandName:"Booths", quality:"Standard"}],
a1Keys = a1.map(function(a){return Object.keys(a);});
//a1Keys now:
[['id','brandName','quality'],['id','brandName','quality']]
The keys method is described #MDN, including a shim for older browsers
var a = {"a": 1, "b": "t" };
var keys = new Array();
for(var o in a){
keys.push(o);
}
console.log(keys)

Is array both associative and indexed?

Can an array in JavaScript be associative AND indexed?
I'd like to be able to lookup an item in the array by its position or a key value.
There are no such things as associative arrays in Javascript. You can use object literals, which look like associative arrays, but they have unordered properties. Regular Javascript arrays are based on integer indexes, and can't be associative.
For example, with this object:
var params = {
foo: 1,
bar: 0,
other: 2
};
You can access properties from the object, for example:
params["foo"];
And you can also iterate over the object using the for...in statement:
for(var v in params) {
//v is equal to the currently iterated property
}
However, there is no strict rule on the order of property iteration - two iterations of your object literal could return the properties in different orders.
After reading the Wikipedia definition of associative array, I'm going to break with traditional JavaScript lore and say, "yes, JavaScript does have associative arrays." With JavaScript arrays, you can add, reassign, remove, and lookup values by their keys (and the keys can be quoted strings), which is what Wikipedia says associative arrays should be able to do.
However, you seem to be asking something different--whether you can look up the same value by either index or key. That's not a requirement of associative arrays (see the Wikipedia article.) Associative arrays don't have to give you the ability to get a value by index.
JavaScript arrays are very closely akin to JavaScript objects.
arr=[];
arr[0]="zero";
arr[1]="one";
arr[2]="two";
arr["fancy"]="what?";
Yes, that's an array, and yes, you can get away with non-numeric indices. (If you're curious, after all this, arr.length is 3.)
In most cases, I think you should stick to numeric indices when you use arrays. That what most programmers expect, I think.
The link is to my blog post about the subject.
Native JS objects only accept strings as property names, which is true even for numeric array indices; arrays differ from vanilla objects only insofar as most JS implementations will store numerically indexed properties differently (ie in an actual array as long as they are dense) and setting them will trigger additional operations (eg adjustment of the length property).
If you're looking for a map which accepts arbitrary keys, you'll have to use a non-native implementation. The script is intended for fast iteration and not random-access by numeric indices, so it might nor be what you're looking for.
A barebones implementation of a map which would do what you're asking for could look like this:
function Map() {
this.length = 0;
this.store = {};
}
Map.prototype.get = function(key) {
return this.store.hasOwnProperty(key) ?
this.store[key] : undefined;
};
Map.prototype.put = function(key, value, index) {
if(arguments.length < 3) {
if(this.store.hasOwnProperty(key)) {
this.store[key].value = value;
return this;
}
index = this.length;
}
else if(index >>> 0 !== index || index >= 0xffffffff)
throw new Error('illegal index argument');
if(index >= this.length)
this.length = index + 1;
this[index] = this.store[key] =
{ index : index, key : key, value : value };
return this;
};
The index argument of put() is optional.
You can access the values in a map map either by key or index via
map.get('key').value
map[2].value
var myArray = Array();
myArray["first"] = "Object1";
myArray["second"] = "Object2";
myArray["third"] = "Object3";
Object.keys(myArray); // returns ["first", "second", "third"]
Object.keys(myArray).length; // returns 3
if you want the first element then you can use it like so:
myArray[Object.keys(myArray)[0]]; // returns "Object1"
The order in which objects appear in an associative javascript array is not defined, and will differ across different implementations. For that reason you can't really count on a given associative key to always be at the same index.
EDIT:
as Perspx points out, there aren't really true associative arrays in javascript. The statement foo["bar"] is just syntactic sugar for foo.bar
If you trust the browser to maintain the order of elements in an object, you could write a function
function valueForIndex(obj, index) {
var i = 0;
for (var key in obj) {
if (i++ == index)
return obj[key];
}
}
var stuff = [];
stuff[0] = "foo";
stuff.bar = stuff[0]; // stuff.bar can be stuff["bar"] if you prefer
var key = "bar";
alert(stuff[0] + ", " + stuff[key]); // shows "foo, foo"
I came here to wanting to know if this is bad practice or not, and instead found a lot of people appearing not to understand the question.
I wanted to have a data structure that was ordered but could be indexed by key, so that it wouldn't require iteration for every lookup.
In practical terms this is quite simple, but I still haven't read anything on whether it's a terrible practice or not.
var roygbiv = [];
var colour = { key : "red", hex : "#FF0000" };
roygbiv.push(colour);
roygbiv[colour.key] = colour;
...
console.log("Hex colours of the rainbow in order:");
for (var i = 0; i < roygbiv.length; i++) {
console.log(roygbiv[i].key + " is " + roygbiv[i].hex);
}
// input = "red";
console.log("Hex code of input colour:");
console.log(roygbiv[input].hex);
The important thing is to never change the value of array[index] or array[key] directly once the object is set up or the values will no longer match. If the array contains objects you can change the properties of those objects and you will be able to access the changed properties by either method.
Although I agree with the answers given you can actually accomplish what you are saying with getters and setters. For example:
var a = [1];
//This makes a["blah"] refer to a[0]
a.__defineGetter__("blah", function(){return this[0]});
//This makes a["blah"] = 5 actually store 5 into a[0]
a.__defineSetter__("blah", function(val){ this[0] = val});
alert(a["blah"]); // emits 1
a["blah"] = 5;
alert(a[0]); // emits 5
Is this what you are looking for? i think theres a different more modern way to do getters and setters but cant remember.
The tide has changed on this one. Now you can do that... and MORE! Using Harmony Proxies you could definitely solve this problem in many ways.
You'll have to verify that your targeted environments support this with maybe a little help from the harmony-reflect shim.
There's a really good example on the Mozilla Developer Network on using a Proxy to find an array item object by it's property which pretty much sums it up.
Here's my version:
var players = new Proxy(
[{
name: 'monkey',
score: 50
}, {
name: 'giraffe',
score: 100
}, {
name: 'pelican',
score: 150
}], {
get: function(obj, prop) {
if (prop in obj) {
// default behavior
return obj[prop];
}
if (typeof prop == 'string') {
if (prop == 'rank') {
return obj.sort(function(a, b) {
return a.score > b.score ? -1 : 1;
});
}
if (prop == 'revrank') {
return obj.sort(function(a, b) {
return a.score < b.score ? -1 : 1;
});
}
var winner;
var score = 0;
for (var i = 0; i < obj.length; i++) {
var player = obj[i];
if (player.name == prop) {
return player;
} else if (player.score > score) {
score = player.score;
winner = player;
}
}
if (prop == 'winner') {
return winner;
}
return;
}
}
});
console.log(players[0]); // { name: 'monkey', score: 50 }
console.log(players['monkey']); // { name: 'monkey', score: 50 }
console.log(players['zebra']); // undefined
console.log(players.rank); // [ { name: 'pelican', score: 150 },{ name: 'giraffe', score: 100 }, { name: 'monkey', score: 50 } ]
console.log(players.revrank); // [ { name: 'monkey', score: 50 },{ name: 'giraffe', score: 100 },{ name: 'pelican', score: 150 } ]
console.log(players.winner); // { name: 'pelican', score: 150 }
The latest MDN documentation makes it quiet clear that Array index must be integers.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
let arr=[];
arr[0]="zero";
arr[1]="one";
arr[2]="two";
arr["fancy"]="what?";
//Arrays cannot use strings as element indexes (as in an associative array) but must use integers.
//Setting non-integers using bracket notation will not set an element to the Array List itself
//A non-integer will set a variable associated with that ARRAY Object property collection
let denseKeys = [...arr.keys()];
console.log(denseKeys);//[ 0, 1, 2 ]
console.log("ARRAY Keys:"+denseKeys.length);//3
let sparseKeys = Object.keys(arr);
console.log(sparseKeys);//[ '0', '1', '2', 'fancy' ]
console.log("Object Keys:"+sparseKeys.length);//4
const iterator = arr.keys();
for (const key of iterator) {
console.log(key);//0,1,2
}
Yes.
test = new Array();
test[0] = 'yellow';
test['banana'] = 0;
alert(test[test['banana']]);

Categories