I'm new here and I'm trying to convert a recursive function to iterative.
I have been reading about the subject for some days and I have found some good sites that gave me ideas to try. But I couldn't find a working solution so far.
This is the code I'm trying to convert:
function dump(value, recursionLevel) {
if(!recursionLevel) recursionLevel = 0;
var vType = typeof value;
var out = vType;
switch (vType) {
case "number":
case "boolean":
out += ": " + value;
break;
case "string":
out += "(" + value.length + '): "' + value + '"';
break;
case "object":
if (value === null) {
out = "null";
}
else if(Array.isArray(value)) {
out = 'array(' + value.length + '): {\n';
for(var i = 0; i < value.length; i++) {
out += ' '.repeat(recursionLevel) + " [" + i + "]: " + dump(value[i], recursionLevel + 1) + "\n";
}
out += ' '.repeat(recursionLevel) + "}";
}
break;
}
return out;
}
I can't find a way to convert it, mostly beceause of the for loop. Any kind of help will be much appreciated.
Thank you so much!
EDIT:
This is the final result of the code:
Recursive version:
function varDumpR(value, indentationLevel) {
// CONFIGURABLE.
indentationSpaces = ' ';
indentationLevel = indentationLevel || 0;
// https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
var valueType = ({}).toString.call(value).match(/\s([a-zA-Z]+)/)[1];
var output = '';
if(valueType === 'Null' || valueType === 'Undefined') {
output += valueType.toLowerCase();
}
else {
// This variable is used to distinguish between "primitive" and "object" String/Boolean/Number.
var isObject = true;
switch(valueType) {
case 'Function':
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + '(';
var functionLines = value.toString().split('\n');
for(var i = 0, functionLinesLength = functionLines.length; i < functionLinesLength; i++) {
// Don't indent the first line. Indent all lines with 2 additional levels except the last one, which is indented with 1 additional level.
output += (i != 0 ? '\n' + indentationSpaces.repeat(indentationLevel + 1 + (+(i > 0 && i < functionLinesLength - 1))) : '') + functionLines[i].trim();
}
output += ')\n' + indentationSpaces.repeat(indentationLevel) + ') ';
break;
case 'Arguments':
case 'Array':
output += valueType + '(' + value.length + ') {\n';
break;
case 'String':
isObject = value instanceof String;
if(isObject) {
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + '(' + value.length + ') "' + value + '"\n' + indentationSpaces.repeat(indentationLevel) + ') ';
}
else {
output += valueType + '(' + value.length + ') "' + value + '"'; // Beware of Strings containing "s.
}
break;
case 'Boolean':
isObject = value instanceof Boolean;
if(isObject) {
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + '(' + value.toString() + ')\n' + indentationSpaces.repeat(indentationLevel) + ') ';
}
else {
output += valueType + '(' + value.toString() + ')';
}
break;
case 'Number':
isObject = value instanceof Number;
// http://cwestblog.com/2014/02/25/javascript-testing-for-negative-zero/
var number = value.valueOf();
var isNegative = (((number = +number) || 1 / number) < 0);
number = number < 0 ? -number : number;
var numberValue = '';
// Integer.
if(parseInt(number, 10) == parseFloat(number)) {
numberValue = 'Integer';
}
// NaN, Infinity, -Infinity.
else if(!isFinite(number)) {
numberValue = 'Number';
}
// Float.
else {
numberValue = 'Float';
}
numberValue += '(' + (isNegative ? '-' : '') + number + ')';
if(isObject) {
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + numberValue + '\n' + indentationSpaces.repeat(indentationLevel) + ') ';
}
else {
output += numberValue;
}
break;
case 'Date':
case 'RegExp':
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + ' "' + value.toString() + '"\n' + indentationSpaces.repeat(indentationLevel) + ') ';
break;
// 'Object'
// 'Error'
// 'Math'
// 'JSON'
default:
output += (valueType === 'Object' ? 'Object': 'Object(' + valueType + ') ');
break;
}
if(isObject) {
if(valueType == 'Arguments' || valueType == 'Array') {
for(var i = 0, valueLength = value.length; i < valueLength; i++) {
output += indentationSpaces.repeat(indentationLevel) + ' [' + i + ']=>\n ' + indentationSpaces.repeat(indentationLevel) + varDumpR(value[i], indentationLevel + 1) + '\n';
}
}
else {
var objectProperties = [];
for(var property in value) {
objectProperties.push(property);
}
output += '(' + objectProperties.length + ') {\n';
for(var i = 0, objectPropertiesLength = objectProperties.length; i < objectPropertiesLength; i++) {
output += indentationSpaces.repeat(indentationLevel) + ' ["' + objectProperties[i] + '"]=>\n ' + indentationSpaces.repeat(indentationLevel) + varDumpR(value[objectProperties[i]], indentationLevel + 1) + '\n';
}
}
output += indentationSpaces.repeat(indentationLevel) + '}';
}
}
return output;
}
Iterative version:
function varDumpI(value) {
// CONFIGURABLE.
indentationSpaces = ' ';
var output = '';
var recursionStack = [{value: value, indentationLevel: 0, output: null}];
while(recursionStack.length > 0) {
var entry = recursionStack.pop();
if(entry.output) {
output += entry.output;
}
else {
value = entry.value;
indentationLevel = entry.indentationLevel;
// https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
var valueType = ({}).toString.call(value).match(/\s([a-zA-Z]+)/)[1];
if(valueType === 'Null' || valueType === 'Undefined') {
output += valueType.toLowerCase();
}
else {
// This variable is used to distinguish between "primitive" and "object" String/Boolean/Number.
var isObject = true;
switch(valueType) {
case 'Function':
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + '(';
var functionLines = value.toString().split('\n');
for(var i = 0, functionLinesLength = functionLines.length; i < functionLinesLength; i++) {
// Don't indent the first line. Indent all lines with 2 additional levels except the last one, which is indented with 1 additional level.
output += (i != 0 ? '\n' + indentationSpaces.repeat(indentationLevel + 1 + (+(i > 0 && i < functionLinesLength - 1))) : '') + functionLines[i].trim();
}
output += ')\n' + indentationSpaces.repeat(indentationLevel) + ') ';
break;
case 'Arguments':
case 'Array':
output += valueType + '(' + value.length + ') {\n';
break;
case 'String':
isObject = value instanceof String;
if(isObject) {
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + '(' + value.length + ') "' + value + '"\n' + indentationSpaces.repeat(indentationLevel) + ') ';
}
else {
output += valueType + '(' + value.length + ') "' + value + '"'; // Beware of Strings containing "s.
}
break;
case 'Boolean':
isObject = value instanceof Boolean;
if(isObject) {
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + '(' + value.toString() + ')\n' + indentationSpaces.repeat(indentationLevel) + ') ';
}
else {
output += valueType + '(' + value.toString() + ')';
}
break;
case 'Number':
isObject = value instanceof Number;
// http://cwestblog.com/2014/02/25/javascript-testing-for-negative-zero/
var number = value.valueOf();
var isNegative = (((number = +number) || 1 / number) < 0);
number = number < 0 ? -number : number;
var numberValue = '';
// Integer.
if(parseInt(number, 10) == parseFloat(number)) {
numberValue = 'Integer';
}
// NaN, Infinity, -Infinity.
else if(!isFinite(number)) {
numberValue = 'Number';
}
// Float.
else {
numberValue = 'Float';
}
numberValue += '(' + (isNegative ? '-' : '') + number + ')';
if(isObject) {
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + numberValue + '\n' + indentationSpaces.repeat(indentationLevel) + ') ';
}
else {
output += numberValue;
}
break;
case 'Date':
case 'RegExp':
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + ' "' + value.toString() + '"\n' + indentationSpaces.repeat(indentationLevel) + ') ';
break;
// 'Object'
// 'Error'
// 'Math'
// 'JSON'
default:
output += (valueType === 'Object' ? 'Object': 'Object(' + valueType + ') ');
break;
}
if(isObject) {
recursionStack.push({output: indentationSpaces.repeat(indentationLevel) + '}'});
if(valueType == 'Arguments' || valueType == 'Array') {
// Loop through the array in reverse order to maintain the consistency with the recursive function.
for(var i = value.length - 1; i >= 0; i--) {
recursionStack.push({output: '\n'});
recursionStack.push({value: value[i], indentationLevel: indentationLevel + 1});
recursionStack.push({output: indentationSpaces.repeat(indentationLevel) + ' [' + i + ']=>\n ' + indentationSpaces.repeat(indentationLevel)});
}
}
else {
var objectProperties = [];
for(var property in value) {
objectProperties.push(property);
}
output += '(' + objectProperties.length + ') {\n';
// Loop through the object in reverse order to maintain the consistency with the recursive function.
for(var i = objectProperties.length - 1; i >= 0; i--) {
recursionStack.push({output: '\n'});
recursionStack.push({value: value[objectProperties[i]], indentationLevel: indentationLevel + 1});
recursionStack.push({output: indentationSpaces.repeat(indentationLevel) + ' ["' + objectProperties[i] + '"]=>\n ' + indentationSpaces.repeat(indentationLevel)});
}
}
}
}
}
}
return output;
}
Iterative version that accepts multiple parameters:
function varDump() {
// CONFIGURABLE.
indentationSpaces = ' ';
var output = '';
for(arg = 0, argumentsLength = arguments.length; arg < argumentsLength; arg++) {
value = arguments[arg];
var recursionStack = [{value: value, indentationLevel: 0, output: null}];
var seenObjects = [];
if(arg > 0) {
output += '\n';
}
while(recursionStack.length > 0) {
var entry = recursionStack.pop();
if(entry.output) {
output += entry.output;
}
else {
value = entry.value;
indentationLevel = entry.indentationLevel;
// https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
var valueType = ({}).toString.call(value).match(/\s([a-zA-Z]+)/)[1];
if(seenObjects.indexOf(value) !== -1) {
output += '*RECURSION*';
}
else if(valueType === 'Null' || valueType === 'Undefined') {
output += valueType.toLowerCase();
}
else {
// This variable is used to distinguish between "primitive" and "object" String/Boolean/Number.
var isObject = true;
switch(valueType) {
case 'Function':
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + '(';
var functionLines = value.toString().split('\n');
for(var i = 0, functionLinesLength = functionLines.length; i < functionLinesLength; i++) {
// Don't indent the first line. Indent all lines with 2 additional levels except the last one, which is indented with 1 additional level.
output += (i != 0 ? '\n' + indentationSpaces.repeat(indentationLevel + 1 + (+(i > 0 && i < functionLinesLength - 1))) : '') + functionLines[i].trim();
}
output += ')\n' + indentationSpaces.repeat(indentationLevel) + ') ';
break;
case 'Arguments':
case 'Array':
output += valueType + '(' + value.length + ') {\n';
break;
case 'String':
isObject = value instanceof String;
if(isObject) {
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + '(' + value.length + ') "' + value + '"\n' + indentationSpaces.repeat(indentationLevel) + ') ';
}
else {
output += valueType + '(' + value.length + ') "' + value + '"'; // Beware of Strings containing "s.
}
break;
case 'Boolean':
isObject = value instanceof Boolean;
if(isObject) {
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + '(' + value.toString() + ')\n' + indentationSpaces.repeat(indentationLevel) + ') ';
}
else {
output += valueType + '(' + value.toString() + ')';
}
break;
case 'Number':
isObject = value instanceof Number;
// http://cwestblog.com/2014/02/25/javascript-testing-for-negative-zero/
var number = value.valueOf();
var isNegative = (((number = +number) || 1 / number) < 0);
number = number < 0 ? -number : number;
var numberValue = '';
// Integer.
if(parseInt(number, 10) == parseFloat(number)) {
numberValue = 'Integer';
}
// NaN, Infinity, -Infinity.
else if(!isFinite(number)) {
numberValue = 'Number';
}
// Float.
else {
numberValue = 'Float';
}
numberValue += '(' + (isNegative ? '-' : '') + number + ')';
if(isObject) {
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + numberValue + '\n' + indentationSpaces.repeat(indentationLevel) + ') ';
}
else {
output += numberValue;
}
break;
case 'Date':
case 'RegExp':
output += 'Object(' + '\n' + indentationSpaces.repeat(indentationLevel + 1) + valueType + ' "' + value.toString() + '"\n' + indentationSpaces.repeat(indentationLevel) + ') ';
break;
// 'Object'
// 'Error'
// 'Math'
// 'JSON'
default:
output += (valueType === 'Object' ? 'Object': 'Object(' + valueType + ') ');
break;
}
if(isObject) {
if(valueType !== 'Math' && valueType !== 'JSON') {
seenObjects.push(value);
}
recursionStack.push({output: indentationSpaces.repeat(indentationLevel) + '}'});
if(valueType == 'Arguments' || valueType == 'Array') {
// Loop through the array in reverse order to maintain the consistency with the recursive function.
for(var i = value.length - 1; i >= 0; i--) {
recursionStack.push({output: '\n'});
recursionStack.push({value: value[i], indentationLevel: indentationLevel + 1});
recursionStack.push({output: indentationSpaces.repeat(indentationLevel) + ' [' + i + ']=>\n ' + indentationSpaces.repeat(indentationLevel)});
}
}
else {
var objectProperties = [];
for(var property in value) {
objectProperties.push(property);
}
output += '(' + objectProperties.length + ') {\n';
// Loop through the object in reverse order to maintain the consistency with the recursive function.
for(var i = objectProperties.length - 1; i >= 0; i--) {
recursionStack.push({output: '\n'});
recursionStack.push({value: value[objectProperties[i]], indentationLevel: indentationLevel + 1});
recursionStack.push({output: indentationSpaces.repeat(indentationLevel) + ' ["' + objectProperties[i] + '"]=>\n ' + indentationSpaces.repeat(indentationLevel)});
}
}
}
}
}
}
}
return output;
}
Test code:
(function testVarDump() {
var func1 = function(par1, par2) {
var sum;
sum = par1 + par2;
return sum;
}
function func2(par1, par2) {
var sum;
sum = par1 + par2;
return sum;
}
var date = new Date(2016, 1, 21);
date.prop = 'date';
var regex = new RegExp(/a/);
regex.prop = 'regex';
var error = new Error('ERROR');
error.prop = 'error';
var math = Math;
math.prop = 'math';
var json = JSON;
json.prop = 'json';
var circular = [];
circular[0] = 0;
circular[1] = circular;
var test = [
'a', String('a'), new String('a'),
true, Boolean(true), new Boolean(true),
12, 12.6, 0, -0, NaN, Infinity, -Infinity,
Number(12), Number(12.6), Number(0), Number(-0), Number(NaN), Number(Infinity), Number(-Infinity),
new Number(12), new Number(12.6), new Number(0), new Number(-0), new Number(NaN), new Number(Infinity), new Number(-Infinity),
null, undefined,
['a', String('a'), new String('a'),
true, Boolean(true), new Boolean(true),
12, 12.6, 0, -0, NaN, Infinity, -Infinity,
Number(12), Number(12.6), Number(0), Number(-0), Number(NaN), Number(Infinity), Number(-Infinity),
new Number(12), new Number(12.6), new Number(0), new Number(-0), new Number(NaN), new Number(Infinity), new Number(-Infinity),
null, undefined],
{
a: [{aa: 1, bb: 2}, Object(), new Object()],
b: [func1, func2, new Function, function() { return false; }, Function(), new Function()],
c: [arguments],
d: [date, Date(), new Date(2016, 1, 21)],
e: [regex, /a/, RegExp(/a/), new RegExp(/a/)],
f: [error, Error('ERROR'), new Error('ERROR')],
g: [math, Math],
h: [json, JSON]
},
]
console.log(varDumpR(test));
console.log(varDumpI(test));
console.log(varDump(test, circular));
})('arg1', 'arg2');
NOTES:
The original code was taken from here:
It ended up being really similar to this one (I didn't copy it, but both are really similar):