Javascript: attach child several deep to object? - javascript

If I want to iterate through a list like this:
var inputArray = [
'CHILD0',
'PARENT0_CHILD1',
'PARENT1_PARENT2_CHILD2',
'PARENT1_PARENT3_CHILD3',
'PARENT1_PARENT3_CHILD4'
];
And have it return an object like so:
var resultObject = {
CHILD0: null,
PARENT0: {CHILD1: null},
PARENT1: {
PARENT2: {CHILD2: null},
PARENT3: {
CHILD3: null,
CHILD4: null
}
}
};
How could I iterate through the array to return the result?
I've got something like this:
function iterateArray (inputArray) {
var _RESULT = {};
for (var i = 0; i < inputArray.length; i += 1) {
var _inputName = inputArray[i];
var _inputNameArray = _input.split('_');
var _ref;
for (var n = 0; n < _inputNameArray.length; n += 1) {
//...?
}
_RESULT[_ref] = null;
}
return _RESULT;
}
var resultObject = iterateArray(inputArray);
Not sure what to do from this point. Think I might need a recursive function of sorts. Thoughts?

With minimal changes to your code:
function iterateArray (inputArray) {
var _RESULT = {};
for (var i = 0; i < inputArray.length; i += 1) {
var _inputName = inputArray[i];
var _inputNameArray = _inputName.split('_');
var _ref = _RESULT;
for (var n = 0; n < _inputNameArray.length - 1; n += 1) {
if (!_ref[_inputNameArray[n]]) _ref[_inputNameArray[n]] = {};
_ref = _ref[_inputNameArray[n]];
}
_ref[_inputNameArray[n]] = null;
}
return _RESULT;
}
You never need recursion, as it can always be unwrapped into iteration (and vice versa). Things are just sometimes much nicer one way or another.
EDIT: What's with all the underscores? :)
EDIT2: The key point to understanding this is reference sharing. For example:
For CHILD0, _ref = _RESULT means both of the variables are pointing at the same {}. When you do _ref['CHILD0'] = null, it is the same as doing _RESULT['CHILD0'] = null.
For PARENT0_CHILD1, first _ref = _RESULT as above, so _ref['PARENT0'] = {} is the same as _RESULT['PARENT0'] = {}. Then we switch the meaning of _ref to be the same thing as _RESULT['PARENT0']; when we assign _ref['CHILD1'] = null, it is the same as assigning _RESULT['PARENT0']['CHILD1'] = null.

Related

Recursion: How can I remove

here's my code:
var asset = ['1234_12', '1234_34', '1234_33', '4321_22', '4321_90'];
var largest = removeElements(asset);
function removeElements(asset) {
var retVal = [];
for (i = 0; i < asset.length; i++) {
for (var j = 0; j < asset.length; j++) {
if (asset[i].split('_')[0] == asset[j].split('_')[0]) {
if (asset[i].split('_')[1].split('.')[0] > asset[j].split('_')[1].split('.')[0]) {
retVal = removeElements(asset, asset[j]);
for (var k = 0; k < retVal.length; k++) {
for (var l = 0; l < retVal.length; l++) {
if (retVal[k].split('_')[0] == retVal[l].split('_')[0]) {
removeElements(retVal);
} else {
return retVal;
}
}
}
}
}
}
}
return retVal;
}
Here's the structure of array:
var asset = ['1234_12', '1234_34', '1234_33', '4321_22', '4321_90'];
What I want is to get largest in '1234' or '4321' series. For example, in this case, I need to grab '1234_34' and '4321_90'.
RangeError: Maximum call stack size exceeded
What am I doing wrong?
You are making it harder than it is for yourself. You can just iterate over each item and store the matched values in an object:
var asset = ['1234_12', '1234_34', '1234_33', '4321_22', '4321_90'];
var intermediate = {};
asset.forEach(function(v) {
var parts = v.split('_');
var key = parts[0];
var val = parts[1];
if (!intermediate[key] || intermediate[key] < val) {
intermediate[key] = val;
}
});
This will produce an object like:
{"1234": "34", "4321": "90"}
Which you can then be turned into the expected array:
var output = Object.keys(intermediate).map(function(key) {
return key + '_' + intermediate[key];
});
console.log(output); // ["1234_34", "4321_90"]
Take a look at .forEach, .map and Object.keys
Here's an example of something that will grab those values (see the jsbin):
var asset = ['1234_12', '1234_34', '1234_33', '4321_22', '4321_90'];
var ids = _.values(_.mapValues(asset.reduce(function(agg, curr) {
var parts = curr.split('_');
agg[parts[0]] = agg[parts[0]] || [];
agg[parts[0]].push(parts[1]);
return agg;
}, {}), function(value, key) {
return [key, Math.max.apply(Math, value)].join('_');
}));
console.log(ids); // => ["1234_34", "4321_90"]
It uses lodash for convenience, but the principles are the same without it.
First you split each string into a key-value pair of the prefix and suffix (so 1234_12 and 1234_34, etc., becomes like { 1234: ['12', '34'] }). Then you just find the max value in that array and join it back with its key.

unexpected token break in ternary conditional

The function below is intended to return the values from a (potentially nested) object as an array - with the list parameter being any object. If I move my break statement to after the for loop, I don't get any errors, but of course then my function doesn't behave as needed. What's wrong with the way I'm using break?
function listToArray(list) {
var objectArray = [];
function objectPeeler() {
let peel = Object.getOwnPropertyNames(list);
for(var i = 0; i < peel.length; i++) {
list[peel[i]] && typeof list[peel[i]] != 'object' ?
objectArray.push(list[peel[i]]):
list[peel[i]] ?
(list = list[peel[i]], objectPeeler()) :
break;
}
return objectArray;
}
objectPeeler();
}
In case anyone else has this issue: ternary operators only work with value expressions, not statements (like break) and aren't meant to be used in these cases.
This works:
function listToArray(list) {
var objectArray = [];
function objectPeeler() {
let peel = Object.getOwnPropertyNames(list);
for(var i = 0; i < peel.length; i++) {
list[peel[i]] != null && typeof list[peel[i]] != 'object' ?
objectArray.push(list[peel[i]]):
list[peel[i]] ?
(list = list[peel[i]], objectPeeler()): null;
}
}
objectPeeler();
return objectArray;
}
But using the jquery .next method allows a better solution:
function listToArray(list) {
var array = [];
for (var obj = list; obj; obj = obj.next)
array.push(obj.value);
return array;
}
why not writing something like this :
var obj = { 0: "a", 1: "b", 2: "c"}; //test target
var objectArray = [];
var keyArray = Object.getOwnPropertyNames(obj);
for (var i = 0; i < keyArray.length; i++) objectArray.push(obj[keyArray[i]]);
console.log(objectArray); // test result

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? ;-)

javascript object - Dynamically setting a nested value using recursive function

I am trying to set one of the nested subobject properties, but the nested level is dynamic.
how can I dynamically set the nested properties?
It's working only one level properties,i can't set next inner level....
my code:
function deSerialize(qualifiedNameArray, currentIndex, resultJSON, valueToBeInitializedForFinalNode)
{
if (currentIndex == (qualifiedNameArray.length - 1)){
resultJSON [qualifiedNameArray[currentIndex++]] = valueToBeInitializedForFinalNode;
}
else
{
resultJSON [qualifiedNameArray[currentIndex++]] = {};
}
if (currentIndex < qualifiedNameArray.length)
deSerialize( qualifiedNameArray, currentIndex, resultJSON, valueToBeInitializedForFinalNode);
return resultJSON;
}
var results = {"columnname":"person.name.first", "varcharvalue":"david", "objecttype" : "user"};
var valueToBeInitializedForFinalNode = results["varcharvalue"];
var qualifiedNameArray = results["columnname"].split('.');
var resultJSON = {};
deSerialize(qualifiedNameArray, 0, resultJSON, valueToBeInitializedForFinalNode);
A simple solution might be, not sure if this is what you are looking for:
function makeObj(arry, initValue){
var obj = {}, objRef = obj, idx = 0;
while(idx < arry.length -1){
obj[arry[idx]] = {};
obj = obj[arry[idx]];
idx++;
}
obj[arry[idx]] = initValue;
return objRef;
}
usage:
resultJSON = makeObj( qualifiedNameArray, valueToBeInitializedForFinalNode);
another way is:
function makeObj(objRef, arry, initValue){
var obj = objRef, idx = 0;
while(idx < arry.length -1){
if(!obj[arry[idx]]) obj[arry[idx]] = {};
obj = obj[arry[idx]];
idx++;
}
if(!obj[arry[idx]]) obj[arry[idx]] = initValue;
}
this way, you do not change any values that might have been already present, usage:
makeObj( resultJSON, qualifiedNameArray, valueToBeInitializedForFinalNode);

Finding an Object's value from an Array

Is there a simpler (or more efficient) way of achieving the following:
var _dataObjects = [{id:0, data:"data0", nextID:1},
{id:1, data:"data1", nextID:2},
{id:2, data:"data2", nextID:3} .. etc.];
generateNextPieceOfData();
function generateNextPieceOfData(){
var len = _dataObjects.length;
for ( var i = 0; i < len; i ++ ) {
var nextDataID = _dataObjects[i].nextID;
var nextData;
for ( var j = 0; j < len; j ++ ) {
if( _dataObjects[j].id == nextDataID ){
nextData = _dataObjects[j].data;
break;
}
}
}
}
The above example is abstracted from the problem I'm having and I realise the ID numbers are sequential in this instance but in the real problem nextID numbers do not run sequentially.
Thanks in advance.
Use the right data structure for your problem. Since you want to find an object by ID, create a hash map with the IDs as keys and objects as values:
var object_map = {};
for(var i = 0, l = _dataObjects.length; i < l; i++) {
objects[_dataObjects[i].id] = _dataObjects[i];
}
Then getting the next object is simply:
var next_object = object_map[someObject.nextID];
You still have iterate until some terminal condition is met though. For example:
function generatePath(id_a, id_b) {
var obj = object_map[id_a];
var path = [obj];
while (obj && obj.id !== id_b) {
obj = object_map[obj.nextID];
path.push(obj);
}
return path;
}
If your code works sequentially only, then you can sort the items by id or whatever and your code should work right? Try this:
_dataObjects = _dataObjects.sort(function(a, b) {
return a.id > b.id;
});

Categories

Resources