简体   繁体   English

如何将此递归函数转换为迭代函数? (JavaScript中的var_dump)

[英]How to convert this recursive function to a iterative one? (var_dump in javascript)

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. 我找不到转换它的方法,主要是因为for循环。 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): 最终确实与此相似(我没有复制它,但两者确实相似):

This sounds a lot like the kind of problem you have to do for homework. 这听起来很像您要做家庭作业时遇到的问题。 To solve that, you are going to need a stack. 为了解决这个问题,您将需要一个堆栈。 In JavaScript, stacks are implemented by arrays - they already have the push() and pop() method you need. 在JavaScript中,堆栈是通过数组实现的-它们已经具有所需的push()和pop()方法。

Here is a little program that does the dump in JavaScript notion (not exactly JSON, but similar) - changing the output format to your needs is left as an exercise. 这是一个以JavaScript概念(不是完全JSON,而是类似)进行转储的小程序-剩下的练习是更改输出格式。

function dump(value) {
   var stack=[{value:value}];
   var out = "";
   while (stack.length>0) {
     var entry = stack.pop();
     if (entry.output) {
       out+=entry.output;
     }
     else {
       value = entry.value;
       switch(typeof value) {
         case "number":
         case "boolean":
           out += value;
           break;
         case "string":
           out += '"'+value+'"';  // beware of Strings containing "s
           break;
         case "object":
           if (value === null) {
              out += "null";
           }
           else if (Array.isArray(value)) {
              out += "[";
              stack.push({output:"]"});
              for (var i=value.length-1; i>=0; i--) {
                 stack.push({value: value[i]});
                 if (i>0) {
                   stack.push({output:","});
                 }
              }
           }
           else {
              out += "{";
              stack.push({output:"}"});
              var s = "";
              var f;
              for (f in value ) {
                 if (s) {
                   stack.push({output: s});
                 }
                 stack.push({value: value[f]});
                 stack.push({output: f+":"});
                 s = ",";
              }
           }
           break;
       }
     }
   }
   return out;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM