関数型スタイルで再帰アルゴリズムを書くことについて質問があります。ここでは例として Scala を使用しますが、問題はどの関数型言語にも当てはまります。
各ノードにラベルと可変数の子があるn分木の深さ優先列挙を行っています。以下は、リーフ ノードのラベルを出力する簡単な実装です。
case class Node[T](label:T, ns:Node[T]*)
def dfs[T](r:Node[T]):Seq[T] = {
if (r.ns.isEmpty) Seq(r.label) else for (n<-r.ns;c<-dfs(n)) yield c
}
val r = Node('a, Node('b, Node('d), Node('e, Node('f))), Node('c))
dfs(r) // returns Seq[Symbol] = ArrayBuffer('d, 'f, 'c)
ここで、例外をスローして、大きすぎるツリーの解析をあきらめたい場合があるとします。これは関数型言語で可能ですか? 具体的には、可変状態を使用せずにこれは可能ですか? それはあなたが「特大」の意味に依存しているようです。これは、深さ 3 以上のツリーを処理しようとすると例外をスローするアルゴリズムの純粋に機能的なバージョンです。
def dfs[T](r:Node[T], d:Int = 0):Seq[T] = {
require(d < 3)
if (r.ns.isEmpty) Seq(r.label) else for (n<-r.ns;c<-dfs(n, d+1)) yield c
}
しかし、深すぎるのではなく幅が広すぎるために木が大きすぎる場合はどうなるでしょうか。具体的には、再帰の深さに関係なく、関数が再帰的に呼び出されるn回目に例外をスローしたい場合はどうすればよいでしょうか? dfs()
これを行う方法を確認できる唯一の方法は、呼び出しごとにインクリメントされる可変カウンターを用意することです。可変変数なしでそれを行う方法がわかりません。
私は関数型プログラミングが初めてで、変更可能な状態でできることは何でもできるという前提で作業してきましたが、ここで答えがわかりません。私が考えられる唯一のことはdfs()
、ツリー内のすべてのノードのビューを深さ優先順で返すバージョンを作成することです。
dfs[T](r:Node[T]):TraversableView[T, Traversable[_]] = ...
次に、 と言って制限を課すことができますがdfs(r).take(n)
、この関数の書き方がわかりません。Python では、ノードにアクセスしたときにノードを ing してジェネレーターを作成するだけですyield
が、Scala で同じ効果を実現する方法がわかりません。(Python スタイルのステートメントに相当する Scalayield
は、パラメーターとして渡されるビジター関数のように見えますが、シーケンス ビューを生成するこれらの 1 つを記述する方法がわかりません。)
EDIT答えに近づく。
Stream
これは、深さ優先順でノードの a を返す関数です。
def dfs[T](r: Node[T]): Stream[Node[T]] = {
(r #:: Stream.empty /: r.ns)(_ ++ dfs(_))
}
それはほとんどそれです。唯一の問題は、Stream
すべての結果をメモすることであり、これはメモリの無駄です。横断可能なビューが必要です。以下はアイデアですが、コンパイルされません。
def dfs[T](r: Node[T]): TraversableView[Node[T], Traversable[Node[T]]] = {
(Traversable(r).view /: r.ns)(_ ++ dfs(_))
}
これは、オペレーターに「found TraversableView[Node[T], Traversable[Node[T]]]
, requiredTraversableView[Node[T], Traversable[_]]
エラー」を++
返します。戻り値の型を に変更するTraversableView[Node[T], Traversable[_]]
と、「found」節と「required」節が入れ替わって同じ問題が発生します。まだですが、これは近いです。