【问题标题】:Typescript function using recursion and yield keyword to pull out nested listsTypescript 函数使用递归和 yield 关键字提取嵌套列表
【发布时间】:2013-10-13 19:34:23
【问题描述】:

我正在尝试重写一个 C# 函数,该函数使用 yield 和递归将类的实例从嵌套列表中拉出到单个列表中。

这是 C# 函数:

 public static IEnumerable<TargetObject> GetRecursively(params TargetObject[] startingObjects)
 {
    foreach (TargetObject startingObject in startingObjects)
    {
        yield return startingObject;
        if (startingObject.InnerObjects != null)
            foreach (TargetObject innerObject in startingObject.InnerObjects.ToArray())
                foreach (TargetObject recursiveInner in GetRecursively(innerObject))
                    yield return recursiveInner;
     }
 }

由于 javascript 不能跨浏览器可靠地支持yield,我该如何在这个复杂的函数中模拟它?

function getRecursively(...startingObjects: TargetObject[])
{
    return function () {
           ??
    }
}

【问题讨论】:

    标签: javascript recursion typescript yield-return


    【解决方案1】:

    2019 年更新

    现在使用 typescript(或 es6)超级简单

    function* recursiveIterator( obj: { children?: any[]; } ):IterableIterator<any> {
        yield obj;
        for ( const child of obj.children ) {
            yield* recursiveIterator( child );
        }
    }
    

    【讨论】:

      【解决方案2】:

      yield 关键字在内部工作的方式是创建一个状态机。您可以自己创建一个,或者如果列表不是太大并且您可以合理地将其保存在内存中,您可以简单地返回并使用一个列表,例如:

      function getRecursively(...startingObjects:TargetObject[] ):TargetObject[] 
       {
          var toreturn = [];
          for (var key in startingObjects)
          {
              var startingObject = startingObjects[key];
              toreturn.push(startingObject);
              if (startingObject.InnerObjects != null)
                  for (var key2 in startingObject.InnerObjects){
                      var innerObject = startingObject.InnerObjects[key2];
                      var superInner = getRecursively(innerObject);
                      for (var key3 in superInner)
                          toreturn.push(superInner[key3]);                        
                  }
           }
          return toreturn; 
       }
      

      如果你真的真的想要yield,你可以使用 google traceur 编译器:https://github.com/google/traceur-compiler e.g.

      function* cool() {
        yield 123;    
        yield 456;  
      }
      
      for (n of cool()) {    
          console.log(n);
      }  
      

      Try it online

      如您所见,生成的状态机并不简单。

      var $__generatorWrap = function(generator) {
        return $traceurRuntime.addIterator({
          next: function(x) {
            switch (generator.GState) {
              case 1:
                throw new Error('"next" on executing generator');
              case 3:
                throw new Error('"next" on closed generator');
              case 0:
                if (x !== undefined) {
                  throw new TypeError('Sent value to newborn generator');
                }
              case 2:
                generator.GState = 1;
                if (generator.moveNext(x, 0)) {
                  generator.GState = 2;
                  return {
                    value: generator.current,
                    done: false
                  };
                }
                generator.GState = 3;
                return {
                  value: generator.yieldReturn,
                  done: true
                };
            }
          },
          'throw': function(x) {
            switch (generator.GState) {
              case 1:
                throw new Error('"throw" on executing generator');
              case 3:
                throw new Error('"throw" on closed generator');
              case 0:
                generator.GState = 3;
                throw x;
              case 2:
                generator.GState = 1;
                if (generator.moveNext(x, 1)) {
                  generator.GState = 2;
                  return {
                    value: generator.current,
                    done: false
                  };
                }
                generator.GState = 3;
                return {
                  value: generator.yieldReturn,
                  done: true
                };
            }
          }
        });
      };
      function cool() {
        var $that = this;
        var $arguments = arguments;
        var $state = 0;
        var $storedException;
        var $finallyFallThrough;
        var $G = {
          GState: 0,
          current: undefined,
          yieldReturn: undefined,
          innerFunction: function($yieldSent, $yieldAction) {
            while (true) switch ($state) {
              case 0:
                this.current = 123;
                $state = 1;
                return true;
              case 1:
                if ($yieldAction == 1) {
                  $yieldAction = 0;
                  throw $yieldSent;
                }
                $state = 3;
                break;
              case 3:
                this.current = 456;
                $state = 5;
                return true;
              case 5:
                if ($yieldAction == 1) {
                  $yieldAction = 0;
                  throw $yieldSent;
                }
                $state = 7;
                break;
              case 7:
                $state = -2;
              case -2:
                return false;
              case -3:
                throw $storedException;
              default:
                throw "traceur compiler bug: invalid state in state machine: " + $state;
            }
          },
          moveNext: function($yieldSent, $yieldAction) {
            while (true) try {
              return this.innerFunction($yieldSent, $yieldAction);
            } catch ($caughtException) {
              $storedException = $caughtException;
              switch ($state) {
                default:
                  this.GState = 3;
                  $state = -2;
                  throw $storedException;
              }
            }
          }
        };
        return $__generatorWrap($G);
      }
      for (var $__0 = $traceurRuntime.getIterator(cool()), $__1; !($__1 = $__0.next()).done;) {
        n = $__1.value;
        {
          console.log(n);
        }
      }
      

      【讨论】:

      • 两个很好的答案,谢谢。尽管这是一个更直接的翻译,但我还是选择了史蒂夫的,因为它具有内存使用优势,通常更易于理解,并且在实践中使用变量回调似乎更灵活。
      【解决方案3】:

      如果你想为每个项目运行一些代码,你可以传入回调来执行。这允许与迭代器模式类似的行为,因为循环将在回调执行时暂停,然后在回调完成后继续。

      如果迭代本身正在动态获取您不想在整个过程中保存在内存中的数据,或者如果您想避免展平数组,这将非常有用。

      这与在 C# 中使用 yield 不同,但它同样简单 - 您需要做的就是编写一个函数来针对找到的每个项目运行代码。

      Here is an example:

      class TargetObject {
          constructor(public name: string, public innerObjects: TargetObject[]) {
          }
      
          static forEachRecursive(callback: (item: TargetObject) => any, targetObjects: TargetObject[]){
              for (var i = 0; i < targetObjects.length; i++) {
                  var item = targetObjects[i];
      
                  callback(item);
      
                  if (item.innerObjects) {
                      TargetObject.forEachRecursive(callback, item.innerObjects);
                  }
              }
          }
      }
      
      var targetObjects = [
          new TargetObject('Example', []),
          new TargetObject('With Inner', [
              new TargetObject('Inner 1', []),
              new TargetObject('Inner 2', [])
          ])
      ];
      
      var callback = function (item: TargetObject) {
          console.log(item.name);
      };
      
      TargetObject.forEachRecursive(callback, targetObjects);
      

      【讨论】:

      • 这真是太好了,谢谢。我无法像我希望的那样使其通用(item instanceof T 不起作用),但我能够使用具有强类型派生类型的回调来实现相同的效果。
      • 我很高兴它有帮助:)
      猜你喜欢
      • 2019-12-01
      • 2021-10-08
      • 2021-04-19
      • 2016-11-05
      • 2020-02-24
      • 2020-01-26
      • 1970-01-01
      • 2016-07-09
      • 2018-08-21
      相关资源
      最近更新 更多