3

我正在尝试重写一个 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 () {
           ??
    }
}
4

3 回答 3

6

2019 年更新

现在使用 typescript(或 es6)非常容易

function* recursiveIterator( obj: { children?: any[]; } ):IterableIterator<any> {
    yield obj;
    for ( const child of obj.children ) {
        yield* recursiveIterator( child );
    }
}
于 2019-07-09T00:22:04.213 回答
4

关键字在内部工作的方式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; 
 }

如果你真的想要产量你可以使用 google traceur 编译器:https ://github.com/google/traceur-compiler例如

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

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

在线尝试

如您所见,生成的状态机并非微不足道。

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);
  }
}
于 2013-10-14T05:05:42.323 回答
1

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

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

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

这是一个例子

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);
于 2013-10-14T08:13:08.257 回答