简体   繁体   English

Typescript函数使用递归和yield关键字拉出嵌套列表

[英]Typescript function using recursion and yield keyword to pull out nested lists

I'm trying to rewrite a C# function that uses yield and recursion to pull out instances of a class out of nested lists into a single list. 我试图重写一个C#函数,该函数使用yield和递归从嵌套列表中将类的实例拉出到单个列表中。

This is the C# function: 这是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;
     }
 }

Being that javascript doesn't reliably support yield across browsers, how can I simulate it in this complex function? 由于javascript无法可靠地支持跨浏览器的yield ,如何在此复杂函数中进行模拟?

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

The way the yield keyword works internally is by creating a state machine. yield关键字在内部工作的方式是通过创建状态机。 You can create one yourself, or if the list is not too large and you can reasonably keep it in memory you can simply return and use a list eg: 您可以自己创建一个列表,或者如果列表不是太大并且可以合理地将其保留在内存中,则可以简单地返回并使用列表,例如:

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; 
 }

If you really really want yield you could use the google traceur compiler : https://github.com/google/traceur-compiler eg 如果你真的想要得到你可以使用谷歌traceur编译: https://github.com/google/traceur-compiler

function* cool() {
  yield 123;    
  yield 456;  
}

for (n of cool()) {    
    console.log(n);
}  

Try it online 在线尝试

As you can see the generated state machine is not trivial. 如您所见,生成的状态机并非无关紧要。

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

If you want to run some code for each of the items, you can pass in the callback to be executed. 如果要为每个项目运行一些代码,则可以传入要执行的回调。 This allows similar behaviour to the iterator pattern as the looping will pause while the callback is executed, and then continue once the callback finishes. 这允许与迭代器模式类似的行为,因为循环将在执行回调时暂停,然后在回调完成后继续。

This is useful if the iteration itself is obtaining data dynamically that you don't want to hold in memory for the whole process or if you want to avoid flattening the array. 如果迭代本身是动态获取您不想在整个过程中保留在内存中的数据,或者想要避免展平数组,则此功能很有用。

This isn't the same as using yield in C# but it is just as simple - all you need to do is write a function that will run the code against each item found. 这与在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);

Update for 2019 2019年更新

This is now super easy with typescript (or es6) 现在使用打字稿(或es6)超级简单

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

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

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