Reputation: 22924
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.
This is the C# function:
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?
function getRecursively(...startingObjects: TargetObject[])
{
return function () {
??
}
}
Upvotes: 4
Views: 6847
Reputation: 7733
This is now super easy with typescript (or es6)
function* recursiveIterator( obj: { children?: any[]; } ):IterableIterator<any> {
yield obj;
for ( const child of obj.children ) {
yield* recursiveIterator( child );
}
}
Upvotes: 9
Reputation: 250992
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.
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);
Upvotes: 1
Reputation: 276027
The way the yield
keyword works internally is by creating a state machine. 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 e.g:
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 e.g.
function* cool() {
yield 123;
yield 456;
}
for (n of cool()) {
console.log(n);
}
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);
}
}
Upvotes: 4