IT戦記 このページをアンテナに追加 RSSフィード Twitter

2007-10-15

「再帰→ループ」の変換が大変だった件

まず、ループは再帰で表現できる

ループというのはすべて再帰呼び出しで表現できる。

たとえば、コレは

var array = [1, 2, 3];
for (var i = 0; i < array.length; i ++) alert(array[i]);

こんな感じになる

(function f(array, i) {
  if (i < array.length) {
    alert(array[i]);
    return f(array, i+1);
  }
})([1, 2, 3], 0);

もし、 array がこの目的以外に使われないならコッチのがキレイかも

(function f(array) {
  alert(array.shift());
  if (array.length) return f(array);
})([1, 2, 3]);

ということは、再帰はループで表現できるはず

ということで、 document ノードから全部の DOM再帰的に辿っていく例をループで表現してみようと思った

まずは、元のコード

(function f(node) {
  console.log(node);
  for (node = node.firstChild; node; node = node.nextSibling) f(node);
})(document);

これをループで表現してみる。

まず、 for 文を while 文に分解。

(function f(node) {
  console.log(node);
  node = node.firstChild;
  while (node) {
    f(node);
    node = node.nextSibling;
  }
})(document);

次に、 functiongoto 文とスタックに分解。

var stack = [];
var node = document;

f:
console.log(node);
node = node.firstChild;
while (node) {
  stack.push(node);
  goto f;
ret:  
  node = node.nextSibling;
}
node = stack.pop();
if (stack.length) goto ret;

次に、 f から goto f をループで表現します。 ret: 以下の部分は外に出します。

var stack = [];
var node = document;

while (true);
  console.log(node);
  node = node.firstChild;
label0:
  if (!node) goto label1;
  stack.push(node);
}

ret:
node = node.nextSibling;
goto label0;

label1:
node = stack.pop();
if (stack.length) goto ret;

だんだんコードがスパゲッティになってきました。。

次に、 ret には goto 文以外で到達するケースがないため、自由に移動できます。

var stack = [];
var node = document;

while (true) {
  console.log(node);
  node = node.firstChild;
label0:
  if (!node) goto label1;
  stack.push(node);
}

label1:
node = stack.pop();
if (stack.length) goto ret;
else goto end;

ret:
node = node.nextSibling;
goto label0;

end:

次に goto label1 を break にして、 goto ret を削除します。

var stack = [];
var node = document;

while (true) {
  console.log(node);
  node = node.firstChild;
label0:
  if (!node) break;
  stack.push(node);
}

node = stack.pop();

if (stack.length) {
  node = node.nextSibling;
  goto label0;
}

次に、 label0 を while ループの外に出します。無限ループは巻き紙のようなものなので、一行ずつ上にずらして一番上の行を外に出していけば OK です。

var stack = [];
var node = document;

console.log(node);
node = node.firstChild;
label0:
while (true) {
  if (!node) break;
  stack.push(node);
  console.log(node);
  node = node.firstChild;
}

node = stack.pop();

if (stack.length) {
  node = node.nextSibling;
  goto label0;
}

次に、 goto label0 をループで表現します。

var stack = [];
var node = document;

console.log(node);
node = node.firstChild;

while (true) {

  while (true) {
    if (!node) break;
    stack.push(node);
    console.log(node);
    node = node.firstChild;
  }

  node = stack.pop();

  if (stack.length) {
    node = node.nextSibling;
  }
  else break;
}

あとは、少しコードをキレイにして

var stack = [];
var node = document;

console.log(node);
node = node.firstChild;

while (true) {

  while (node) {
    stack.push(node);
    console.log(node);
    node = node.firstChild
  }

  node = stack.pop();
  if (!stack.length) break;
  node = node.nextSibling;
}

はい!完成!わーわーぱちぱちぱち。

まとめ

関数呼び出しをやめてみて、関数呼び出しやスタックフレームの理解がほんの少しだけ深まったような気がします。

LiosKLiosK 2007/10/15 23:12 JavaScriptってgoto使えたんですね!

amachangamachang 2007/10/16 00:07 いやいや使えないです><
ここでは仮にです仮に

YuichirouYuichirou 2007/10/16 00:42 ちゃんと今公開しているソースで動作確認しましたか? documentのfirstChildの構造(と言ってもDOCTYPE宣言なのですが)を読み終えた後、stack.pop()するとstack[]が空になってすぐbreakしてしまいます。stackには本来ルートのdocumentもpushされていなければならないのです。
具体的には4〜5行目の「console.log(node);」「node = node.firstChild;」が不要で、これらを削除すると正常に動作します(Firebugで動作確認しました)。

@”time is (C*100)”@”time is (C*100)” 2007/12/26 19:40 //とある言語で解放時のメソッドで再帰メソッドを呼べない現象に出くわして
//その対処コード(書きかけでたぶんリーク有り、効率度外視)です。
//構造は、考える事は皆同じなんだなあという感じ。

if( 0==[[treeData children] count] )
{
[treeData release];
}
else
{

//※retain無し版。
CFArrayCallBacks arrayCallbacks = kCFTypeArrayCallBacks;
arrayCallbacks.retain = NULL;
arrayCallbacks.release = NULL;
NSMutableArray *node_in_Children_arr = (NSMutableArray*)
CFArrayCreateMutable(
kCFAllocatorDefault,
0,
&arrayCallbacks);
[node_in_Children_arr addObjectsFromArray:[treeData children]];

//これなら再帰いらない。
while(0!=[node_in_Children_arr count])
{
unsigned i = 0;
const unsigned i_end = [node_in_Children_arr count];
//NSEnumerator *delNodesEnum = [node_in_Children_arr objectEnumerator];
McOlNode *delNode = nil;
for(; i < i_end ;++i )
{
delNode = [node_in_Children_arr objectAtIndex:i];

if( [delNode isLeaf] )
{
[delNode release];
}
else
{
[node_in_Children_arr addObjectsFromArray:[delNode children]];
[delNode release];
}
}
//forで既存を処理し終わったら終わった分だけ取り除く。
NSRange del_range;
del_range.location = 0;
del_range.length = i_end;
[node_in_Children_arr removeObjectsInRange:del_range];

//if(0==[node_in_Children_arr count])
//{
//break;
//}
}
[[treeData nodeData] setIsLeaf:YES];
[treeData release];
}

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。