再帰とループの2通りの方法でS式(リスト)を走査してみる。ループの方はスタックの使い方これでいいのかよくわからんけど。
using System; using System.Collections.Generic; abstract class SExp { } class Pair : SExp { public SExp Car { get; private set; } public SExp Cdr { get; private set; } public Pair(SExp car, SExp cdr) { this.Car = car; this.Cdr = cdr; } public override string ToString() { return "(" + this.Car + " . " + this.Cdr + ")"; } } class Atom : SExp { public string Value { get; set; } public Atom(string str) { this.Value = str; } public override string ToString() { return this.Value; } } class Nil : SExp { public override string ToString() { return "()"; } } class App { static void Main(string[] args) { // exp2 == (x y (a b c) z) SExp exp1 = new Pair(new Atom("a"), new Pair(new Atom("b"), new Pair(new Atom("c"), new Nil()))); SExp exp2 = new Pair(new Atom("x"), new Pair(new Atom("y"), new Pair(exp1, new Pair(new Atom("z"), new Nil())))); Scan1(exp2); Console.WriteLine("=========="); Scan2(exp2); } // 再帰処理で走査 static void Scan1(SExp exp) { if (exp is Pair) { Scan1((exp as Pair).Car); Scan1((exp as Pair).Cdr); } else { Console.WriteLine(exp); } } // ループ+スタックで走査 static void Scan2(SExp exp) { Stack<SExp> cs = new Stack<SExp>(); while (true) { if (exp is Pair) { SExp car = (exp as Pair).Car; SExp cdr = (exp as Pair).Cdr; cs.Push(cdr); exp = car; } else { Console.WriteLine(exp); if (0 == cs.Count) break; exp = cs.Pop(); } } } }
car部にPairがくると木構造(?)になってしまう。car部の部分木を走査し終わった後にcdr部の部分木を走査するためには、位置を記憶しておかないといけないので、スタックが必要になる。メソッド呼び出しは、この「戻ってくる場所をスタックで記憶しておく」という仕事を処理系が裏でやってくれるので簡潔に書ける。
以前二分木のイテレータで散々悩んだときのやり方でも考えてみるかな。S式も木構造だけど、あの時の木構造と違うのは、ノードが「自分の値」を持っていない(leftとrightだけ)ってことなのかな?