Javascript Iteration issue - variable variable - javascript

Not an expert on the old JS so here goes
I have
store1.baseParams.competition = null;
store2.baseParams.competition = null;
store3.baseParams.competition = null;
What I want to do is
for (i=1; 1<=3; 1++) {
store + i +.baseParams.competition = null;
}
Hope that makes sense what I want to do - is it possible
Basically make a variable / object by adding to it
Cheers

One way to accomplish this is via eval() - (usually a Very Bad Idea)
for (var i=1; i<=3; i++) {
eval("store" + i + ".baseParams.competition = null;");
}
Another, more complex but relatively efficient way would be to create a function which gives you the ability to mutate arbitrarily deep object hierarchies dynamically at run-time. Here's one such function:
/*
Usage:
Nested objects:
nested_object_setter(object, ['property', 'propertyOfPreviousProperty'], someValue);
Top-level objects:
nested_object_setter(object, 'property', someValue);
*/
function dynamic_property_setter_base(obj, property, value, strict) {
var shouldPerformMutation = !strict || (strict && obj.hasOwnProperty(property));
if(shouldPerformMutation) {
obj[property] = value;
}
return value;
}
function dynamic_property_setter(obj, property, value) {
return dynamic_property_setter_base(obj, property, value, false);
}
function nested_object_setter(obj, keys, value) {
var isArray = function(o) {
return Object.prototype.toString.call(o) === '[object Array]';
};
//Support nested keys.
if(isArray(keys)) {
if(keys.length === 1) {
return nested_object_setter(obj, keys[0], value);
}
var o = obj[keys[0]];
for(var i = 1, j = keys.length - 1; i < j; i++)
o = o[keys[i]];
return dynamic_property_setter(o, keys[keys.length - 1], value);
}
if(keys != null &&
Object.prototype.toString.call(keys) === '[object String]' &&
keys.length > 0) {
return dynamic_property_setter(obj, keys, value);
}
return null;
}
Your code would look like this:
for(var i = 1; i <= 3; i++)
nested_object_setter(this, ['store' + i, 'baseParams', 'competition'], null);
Here's another example, running in the JS console:
> var x = {'y': {'a1': 'b'}};
> var i = 1;
> nested_object_setter(this, ['x','y','a' + i], "this is \"a\"");
> x.y.a1
"this is "a""
Another way to do it, IMHO this is the simplest but least extensible way:
this['store' + i].baseParams.competition = null;

That won't work. You can make an object though, storing the 'store'+i as a property.
var storage = {},i=0;
while(++i<4) {
storage['store' + i] = { baseParams: { competition:null } };
}
Console.log(String(storage.store1.baseParams.competition)); //=> 'null'
In a browser, you can also use the window namespace to declare your variables (avoiding the use of eval):
var i=0;
while(++i<4) {
window['store' + i] = { baseParams: { competition:null } };
}
Console.log(String(store1.baseParams.competition)); //=> 'null'

for (i=1; i<=3; i++) {
this["store" + i + ".baseParams.competition"] = null;
}
Just another form of assigning variables in JS.

Related

Nesting dot notation within bracket notation to create nested objects

"Write a function arrayToList that builds up a list structure like"
let LL = { data: 1, next: { data: 2, next: { data: 3, next: null }}};
I understand the typical solution to this problem, where the list must be built from the inside out:
function arrToLList(arr) {
let LList = null;
for (let i = arr.length - 1; i >= 0; i--) {
LList = { data: arr[i], next: LList };
}
return LList;
}
But my initial solution was to brute force it with a typical for loop.
function arrayToLList(arr) {
let d = "data";
let n = "next";
let LList = nextNode();
for (let i = 0; i < arr.length; i++) {
LList[d] = arr[i];
d = "next." + d;
LList[n] = nextNode();
n = "next." + n;
}
function nextNode() {
return {
data: null,
next: null
};
}
return LList;
}
What you want to achieve is possible, but you need to customize the functionality of how getting a property works when you use bracket notation. As you mentioned, using dot notation with bracket notation won't work, you need a way to define this logic yourself. ES6 introduced Proxies which allows you to specify a set method trap for your object. Whenever you set a value on the object, the set method will be called. Using this idea, you can split the dot-notation string by . and traverse the path it returns to get your nested object. Once you have retrieved the nested object, you can set its value.
See example below:
function arrayToLList(arr) {
let d = "data";
let n = "next";
let LList = nextNode();
for (let i = 0; i < arr.length; i++) {
LList[d] = arr[i];
d = "next." + d;
if(i < arr.length-1) // don't add null object to last node
LList[n] = nextNode();
n = "next." + n;
}
function nextNode() {
const obj = {
data: null,
next: null
};
return new Proxy(obj, {
set: function(obj, key, val) {
const path = key.split('.');
const last = path.pop();
for(const prop of path)
obj = obj[prop];
obj[last] = val;
return true;
}
});
}
return LList;
}
console.log(arrayToLList([1, 2, 3]));
However, you don't need to use a proxy. A more straightforward way of doing this would be by creating a method such as setValueByPath(val, obj, strPath) which performs the logic in the proxy for you. Then, instead of setting your object using bracket notation, you simply call the setValueByPath(obj, strPath):
function setValudByPath(val, obj, strPath) { // pefroms same logic from proxy, just using reduce instead
const path = strPath.split('.');
const last = path.pop();
path.reduce((nested, p) => nested[p], obj)[last] = val;
}
function arrayToLList(arr) {
let d = "data";
let n = "next";
let LList = {data: null, next: null};
for (let i = 0; i < arr.length; i++) {
setValudByPath(arr[i], LList, d); // same as LList[d] = arr[i];
d = "next." + d;
if(i < arr.length-1) // don't add null object to last node
setValudByPath({data: null, next: null}, LList, n); // same as: LList[n] = nextNode();
n = "next." + n;
}
return LList;
}
console.log(arrayToLList([1, 2, 3]));
As you are trying to access an objects parameters using strings.
You can't use dot notation with string.
e.g.
let data = {name:'test'};
console.log("data.name");
this is what you're attempting and it will return data.name and not the value test.
you can do the following though: data['name'] so with nested object you can do the following:
LList['next']['next']...['next']['data']
to get the n'th data element.

How to do I unshift/shift single value and multiple values using custom methods?

I have prototypes to recreate how array methods work, pop/push/shift/etc, and I would like to extend the functionality to do the following:
Push/Pop/shift/unshift multiple values
array.push(0);
array.push(1);
array.push(2);
expect(array.pop()).to.be(2);
expect(array.pop()).to.be(1);
expect(array.pop()).to.be(0);
Push/Pop/unshift/etc single values
array.push(0);
array.push(1);
expect([0,1]);
array.pop(1);
expect([0]);
My assumption is that I would need a global array variable to store the elements. Is that the right?
Here is my code:
var mainArray = []; // array no longer destroyed after fn() runs
function YourArray(value) {
this.arr = mainArray; // looks to global for elements | function?
this.index = 0;
var l = mainArray.length;
if(this.arr === 'undefined')
mainArray += value; // add value if array is empty
else
for(var i = 0; i < l ; i++) // check array length
mainArray += mainArray[i] = value; // create array index & val
return this.arr;
}
YourArray.prototype.push = function( value ) {
this.arr[ this.index++ ] = value;
return this;
};
YourArray.prototype.pop = function( value ) {
this.arr[ this.index-- ] = value;
return this;
};
var arr = new YourArray();
arr.push(2);
console.log(mainArray);
My assumption is that I would need a global array variable to store
the elements. Is that the right?
No. That is not right.
You want each array object to have its own, independent set of data. Otherwise, how can you have multiple arrays in your program?
function YourArray(value) {
this.arr = []; // This is the data belonging to this instance.
this.index = 0;
if(typeof(value) != 'undefined')) {
this.arr = [value];
this.index = 1;
}
}
////////////////////////////////////
// Add prototype methods here
///////////////////////////////////
var array1 = new YourArray();
var array2 = new YourArray();
array1.push(2);
array1.push(4);
array2.push(3);
array2.push(9);
// Demonstrate that the values of one array
// are unaffected by the values of a different array
expect(array1.pop()).to.be(4);
expect(array2.pop()).to.be(9);
It's a bit late for this party, admitted but it nagged me. Is there no easy (for some larger values of "easy") way to do it in one global array?
The standard array functions work as in the following rough(!) sketch:
function AnotherArray() {
this.arr = [];
// points to end of array
this.index = 0;
if(arguments.length > 0) {
for(var i=0;i<arguments.length;i++){
// adapt if you want deep copies of objects
// and/or take a given array's elements as
// individual elements
this.arr[i] = arguments[i];
this.index++;
}
}
}
AnotherArray.prototype.push = function() {
// checks and balances ommitted
for(var i=0;i<arguments.length;i++){
this.arr[ this.index++ ] = arguments[i];
}
return this;
};
AnotherArray.prototype.pop = function() {
this.index--;
return this;
};
AnotherArray.prototype.unshift = function() {
// checks and balances ommitted
var tmp = [];
var alen = arguments.length;
for(var i=0;i<this.index;i++){
tmp[i] = this.arr[i];
}
for(var i=0;i<alen;i++){
this.arr[i] = arguments[i];
this.index++;
}
for(var i=0;i<tmp.length + alen;i++){
this.arr[i + alen] = tmp[i];
}
return this;
};
AnotherArray.prototype.shift = function() {
var tmp = [];
for(var i=1;i<this.index;i++){
tmp[i - 1] = this.arr[i];
}
this.arr = tmp;
this.index--;
return this;
};
AnotherArray.prototype.isAnotherArray = function() {
return true;
}
AnotherArray.prototype.clear = function() {
this.arr = [];
this.index = 0;
}
AnotherArray.prototype.fill = function(value,length) {
var len = 0;
if(arguments.length > 1)
len = length;
for(var i=0;i<this.index + len;i++){
this.arr[i] = value;
}
if(len != 0)
this.index += len;
return this;
}
// to simplify this example
AnotherArray.prototype.toString = function() {
var delimiter = arguments.length > 0 ? arguments[0] : ",";
var output = "";
for(var i=0;i<this.index;i++){
output += this.arr[i];
if(i < this.index - 1)
output += delimiter;
}
return output;
}
var yaa = new AnotherArray(1,2,3);
yaa.toString(); // 1,2,3
yaa.push(4,5,6).toString(); // 1,2,3,4,5,6
yaa.pop().toString(); // 1,2,3,4,5
yaa.unshift(-1,0).toString(); // -1,0,1,2,3,4,5
yaa.shift().toString(); // 0,1,2,3,4,5
var yaa2 = new AnotherArray();
yaa2.fill(1,10).toString(); // 1,1,1,1,1,1,1,1,1,1
Quite simple and forward and took only about 20 minutes to write (yes, I'm a slow typist). I would exchange the native JavaScript array in this.arr with a double-linked list if the content can be arbitrary JavaScript objects which would make shift and unshift a bit less memory hungry but that is obviously more complex and slower, too.
But to the main problem, the global array. If we want to use several individual chunks of the same array we need to have information about the starts and ends of the individual parts. Example:
var globalArray = [];
var globalIndex = [[0,0]];
function YetAnotherArry(){
// starts at the end of the last one
this.start = globalIndex[globalIndex.length-1][1];
this.index = this.start;
// position of the information in the global index
this.pos = globalIndex.length;
globalIndex[globalIndex.length] = [this.start,this.index];
}
So far, so well. We can handle the first array without any problems. We can even make a second one but the moment the first one wants to expand its array we get in trouble: there is no space for that. The start of the second array is the end of the first one, without any gap.
One simple solution is to use an array of arrays
globalArray = [
["first subarray"],
["second subarray"],
...
];
We can than reuse what we already wrote in that case
var globalArray = [];
function YetAnotherArray(){
// open a new array
globalArray[globalArray.length] = [];
// point to that array
this.arr = globalArray[globalArray.length - 1];
this.index = 0;
}
YetAnotherArray.prototype.push = function() {
for(var i=0;i<arguments.length;i++){
this.arr[ this.index++ ] = arguments[i];
}
return this;
};
// and so on
But for every new YetAnotherArray you add another array to the global array pool and every array you abandon is still there and uses memory. You need to manage your arrays and delete every YetAnotherArray you don't need anymore and you have to delete it fully to allow the GC to do its thing.
That will leave nothing but gaps in the global array. You can leave it as it is but if you want to use and delete thousands you are left with a very sparse global array at the end. Or you can clean up. Problem:
var globalArray = [];
function YetAnotherArray(){
// add a new subarray to the end of the global array
globalArray[globalArray.length] = [];
this.arr = globalArray[globalArray.length - 1];
this.index = 0;
this.pos = globalArray.length - 1;
}
YetAnotherArray.prototype.push = function() {
for(var i=0;i<arguments.length;i++){
this.arr[ this.index++ ] = arguments[i];
}
return this;
};
YetAnotherArray.prototype.toString = function() {
var delimiter = arguments.length > 0 ? arguments[0] : ",";
var output = "";
for(var i=0;i<this.index;i++){
output += this.arr[i];
if(i < this.index - 1)
output += delimiter;
}
return output;
}
// we need a method to delete an instance
YetAnotherArray.prototype.clear = function() {
globalArray[this.pos] = null;
this.arr = null;
this.index = null;
};
YetAnotherArray.delete = function(arr){
arr.clear();
delete(arr);
};
// probably won't work, just a hint in case of asynch. use
var mutex = false;
YetAnotherArray.gc = function() {
var glen, indexof, next_index, sub_len;
indexof = function(arr,start){
for(var i = start;i<arr.length;i++){
if (arr[i] == null || arr[i] == undefined)
return i;
}
return -1;
};
mutex = true;
glen = globalArray.length;
sublen = 0;
for(var i = 0;i<glen;i++){
if(globalArray[i] == null || globalArray[i] == undefined){
next_index = indexof(globalArray,i);
if(next_index == -1){
break;
}
else {
globalArray[i] = globalArray[next_index + 1];
globalArray[next_index + 1] = null;
sublen++;
}
}
}
globalArray.length -= sublen - 1;
mutex = false;
};
var yaa_1 = new YetAnotherArray();
var yaa_2 = new YetAnotherArray();
var yaa_3 = new YetAnotherArray();
var yaa_4 = new YetAnotherArray();
yaa_1.push(1,2,3,4,5,6,7,8,9).toString(); // 1,2,3,4,5,6,7,8,9
yaa_2.push(11,12,13,14,15,16).toString(); // 11,12,13,14,15,16
yaa_3.push(21,22,23,24,25,26,27,28,29).toString();// 21,22,23,24,25,26,27,28,29
yaa_4.push(311,312,313,314,315,316).toString(); // 311,312,313,314,315,316
globalArray.join("\n");
/*
1,2,3,4,5,6,7,8,9
11,12,13,14,15,16
21,22,23,24,25,26,27,28,29
311,312,313,314,315,316
*/
YetAnotherArray.delete(yaa_2);
globalArray.join("\n");
/*
1,2,3,4,5,6,7,8,9
21,22,23,24,25,26,27,28,29
311,312,313,314,315,316
*/
YetAnotherArray.gc();
globalArray.join("\n");
/*
1,2,3,4,5,6,7,8,9
21,22,23,24,25,26,27,28,29
311,312,313,314,315,316
*/
But, as you might have guessed already: it doesn't work.
YetAnotherArray.delete(yaa_3); // yaa_3 was 21,22,23,24,25,26,27,28,29
globalArray.join("\n");
/*
1,2,3,4,5,6,7,8,9
21,22,23,24,25,26,27,28,29
*/
We would need another array to keep all positions. Actual implementation as an exercise for the reader but if you want to implement a JavaScript like array, that is for arbitrary content you really, really, really should use a doubly-linked list. Or a b-tree. A b+-tree maybe?
Oh, btw: yes, you can do it quite easily with a {key:value} object, but that would have squeezed all the fun out of the job, wouldn't it? ;-)

In Javascript, is there an equivalent of a "find if", or a compact way of doing what I'm trying to do?

I have an ugly piece of Javascript code
for (var k = 0; k < ogmap.length; ++k)
{
if (ogmap[k]["orgname"] == curSelectedOrg)
{
ogmap[k]["catnames"].push(newCatName);
break;
}
}
Actually, I have a lot of pieces like that in my web app.
I'm wondering if there's a way to make it prettier and compacter. I know there are nice ways of doing this in other languages, like using find_if in C++ (http://www.cplusplus.com/reference/algorithm/find_if/) or FirstOrDefault in C# or fancy LINQ queries in C#.
At the very least, help me make that slightly more readable.
I'd say that you can just write yourself a utility function and then use it whenever necessary.
// finds the first object in the array that has the desired property
// with a value that matches the passed in val
// returns the index in the array of the match
// or returns -1 if no match found
function findPropMatch(array, propName, val) {
var item;
for (var i = 0; i < array.length; i++) {
item = array[i];
if (typeof item === "object" && item[propName] === val) {
return i;
}
}
return -1;
}
And, then you can use it like this:
var match = findPropMatch(ogmap, "orgname", curSelectedOrg);
if (match !== -1) {
ogmap[match]["catnames"].push(newCatName);
}
var find_if = function (arr, pred) {
var i = -1;
arr.some(function (item, ind) {
if (pred(item)) {
i = ind;
return true;
}
});
return i;
}
Call it like
var item_or_last = find_if(_.range(ogmap.length), function (item) {
return item["orgname"] == curSelectedOrg
});
Or without underscore.js
var range = function (a, b) {
var low = a < b ? a : b;
var high = a > b ? a : b;
var ret = [];
while (low < high) {
ret.push(low++);
}
return ret;
}
var item_or_last = find_if(range(0, ogmap.length), function (item) {
return item["orgname"] == curSelectedOrg
});
This lets you declare what you are looking for instead of looping over items and checking each one.

Nested for-loop overwrites object attribute

I broke down my code to a simplified jsFiddle. The problem is that the attribute is is only set for one object but in the end every object gets the value of the last iteration (in this case it is false but id05 should be true). Why is it? Do I overlook something?
jsFiddle (see in the console)
var reminder = {
id0: {
id: 0,
medId: 0
}
};
var chart = {
id0: {
medId: 0,
values: [[5,1]]
}
}
var tmp = {};
for(var i = 0; i < 10; i++) {
for (id in reminder) {
tmp[id + i] = reminder[id];
tmp[id + i].is = false;
for(var j = 0; j < chart["id" + reminder[id].medId].values.length; j++) {
if (chart["id" + reminder[id].medId].values[j][0] === i) {
tmp[id + i].is = true;
}
}
}
}
tmp[id + i] = reminder[id]; will copy the reference to the object and not clone the object itself.
Consider this:
var a = { a: [] };
var b = a.a;
b.push(1);
console.log(a.a); // [1]
This means that all your objects are the same and they share the same properties (tmp.id05 === tmp.id06 etc...)
tmp.id00.__my_secret_value__ = 1234;
console.log(tmp.id09.__my_secret_value__); // 1234
To clone objects in JavaScript you can use Object.create but this will only make a shallow clone (only clone top level properties)

Is there a library to support autovivification on Javascript objects?

Is there anyway, either natively or through a library, to use autovivification on Javascript objects?
IE, assuming foo is an object with no properties, being able to just do foo.bar.baz = 5 rather than needing foo.bar = {}; foo.bar.baz = 5.
You can't do it exactly with the syntax you want. But as usual, in JS you can write your own function:
function set (obj,keys,val) {
for (var i=0;i<keys.length;i++) {
var k = keys[i];
if (typeof obj[k] == 'undefined') {
obj[k] = {};
}
obj = obj[k];
}
obj = val;
}
so now you can do this:
// as per you example:
set(foo,['bar','baz'],5);
without worrying if bar or baz are defined. If you don't like the [..] in the function call you can always iterate over the arguments object.
Purely natively, I don't think so. undefined isn't extensible or changeable and that's about the only way I could imagine doing it without passing it through a function.
I had a desire to do this, so I wrote a package to handle it.
% npm install autovivify
% node
> Av = require('autovivify')
> foo = new Av()
{}
> foo.bar.baz = 5
5
> foo
{ bar: { baz: 5 } }
>
It'll even do arrays with numeric subscripts:
> foo = new Av()
> foo.bar.baz[0] = 'hum'
> foo
{ bar: { baz: [ 'hum' ] } }
Or you can use an eval-based solution. It's ugly, not recommended.
function av(xpr) {
var res = "";
var pos = 0;
while (true) {
var pos = xpr.indexOf("[",pos);
if (pos == -1) break;
var frag = xpr.substr(0,pos);
pos++;
res += "if (typeof(" + frag + ") != 'object') " + frag + " = {};\n";
} // while
return res + xpr;
} // av()
function main() {
var a = {};
a["keep"] = "yep";
eval(av('a[1][1]["xx"] = "a11xx"; '));
eval(av('a[1][2]["xx"] = "a12xx"; '));
console.log(a);
} // main()
#slebetman's code doesn't seem to work. The last key should not be assigned an empty object, but rather the val. This code worked:
function autoviv(obj,keys,val) {
for (var i=0; i < keys.length; i++) {
var k = keys[i];
if (typeof obj[k] === 'undefined') {
if(i === keys.length-1) {
obj[k] = val;
return;
}
obj[k] = {};
}
obj = obj[k];
}
}
foo = {}
autoviv(foo,['bar','baz'],5);
console.log(foo.bar.baz);
5

Categories

Resources