NyaRuRuさんの日記より引用:
流行っているっぽいのでやってみました.
http://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3a%e3%83%aa%e3%82%b9%e3%83%88%e5%87%a6%e7%90%86#H-ne4pu7与えられた木から、子→親への対応を作る
Shiro(2008/05/24 11:55:47 PDT): たまたま昨日、仕事で扱った小ネタ。初級編クイズになりそうなので書き留めておく。
木構造が与えられる。たとえばこんなの:
(define *tree* '(Root (Spine (Neck (Head)) (RClavicle (RUpperArm (RLowerArm (RHand)))) (LClavicle (LUpperArm (LLowerArm (LHand))))) (RHip (RUpperLeg (RLowerLeg (RFoot)))) (LHip (LUpperLeg (LLowerLeg (LFoot))))))つまり、
:= ( ...) という構造。 これから、子→親の対応を表すalistを作る手続きを書け、というもの。結果の例はこんな感じ。各要素の順序は問わない。
((LHip . Root) (LUpperLeg . LHip) (LLowerLeg . LUpperLeg) (LFoot . LLowerLeg) (RHip . Root) (RUpperLeg . RHip) (RLowerLeg . RUpperLeg) (RFoot . RLowerLeg) (Spine . Root) (LClavicle . Spine) (LUpperArm . LClavicle) (LLowerArm . LUpperArm) (LHand . LLowerArm) (RClavicle . Spine) (RUpperArm . RClavicle) (RLowerArm . RUpperArm) (RHand . RLowerArm) (Neck . Spine) (Head . Neck))これは確かに頻出問題っぽい気がします.
階層構造の表現として,汎用なツリーの他に,テーブル指向なストレージ向けのいくつかのツリー表現があって,今回の問題は汎用ツリーから「隣接リストモデル」への書き換えという感じでしょうか.
http://d.hatena.ne.jp/NyaRuRu/20080604/p1
なんか最近、こういうネタしか日記に書いてない気がするけど、とにかくScalaで解いてみた。まず、データ構造の定義。各ツリーは、名前と子ノードのリストを持っていると考えられるから、以下のようなクラス定義で。
case class Tree(name: Symbol, children: List[Tree])
ただ、これだけだと、ツリー構造を書き下すのが非常にしんどいので、簡単に書き下すためにimplicit conversionでメソッドを追加する。こういうときは、S式がリテラルとして書ける言語は便利でいいなあ、と思う。
class RichSymbol(sym: Symbol) { def apply(children: Any*) :Tree = { Tree( sym, children.toList.map{ case x:Symbol => Tree(x, Nil) case x:Tree => x } ) } } implicit def toRichSymbol(sym: Symbol) = new RichSymbol(sym)
このように、Symbolにapplyメソッドをimplicit conversionで追加すると、以下のような感じでツリー構造を表記できる。これでもS式で書き下すのに比べるとやや面倒だけど、だいぶマシになった。
val tree = 'Root( 'Spine( 'Neck ('Head), 'RClavicle ('RUpperArm ('RLowerArm ('RHand))), 'LClavicle ('LUpperArm ('LLowerArm ('LHand))) ), 'RHip('RUpperLeg ('RLowerLeg ('RFoot))), 'LHip('LUpperLeg ('LLowerLeg ('LFoot))))
で、本題のプログラムだけど、これはTreeを引数に取って、List[(Symbol, Symbol)]を返すメソッドとして実装した。単純に、子ノードを引数にして再帰的に呼び出した結果をflatMapで結合しているだけなので、特に難しいところは無いと思う。
def makeAssocList(tree: Tree): List[(Symbol, Symbol)] = { tree.children.map{c => (c.name, tree.name)} ++ tree.children.flatMap{makeAssocList(_)} } makeAssocList(tree).foreach(println)
実行結果。
('Spine,'Root) ('RHip,'Root) ('LHip,'Root) ('Neck,'Spine) ('RClavicle,'Spine) ('LClavicle,'Spine) ('Head,'Neck) ('RUpperArm,'RClavicle) ('RLowerArm,'RUpperArm) ('RHand,'RLowerArm) ('LUpperArm,'LClavicle) ('LLowerArm,'LUpperArm) ('LHand,'LLowerArm) ('RUpperLeg,'RHip) ('RLowerLeg,'RUpperLeg) ('RFoot,'RLowerLeg) ('LUpperLeg,'LHip) ('LLowerLeg,'LUpperLeg) ('LFoot,'LLowerLeg)
追記:
そういえば、解くのにかかった時間は測ってなかった。たぶん、makeAssocListの部分は5分くらいだったと思うけど、木構造を書き下すところで、どうすれば書きやすくなるかあれこれ悩んで、そこで30分くらい費やした気がする。最初、Tree('Root, ...)のようにして書いていたが、書きづらいし見づらいなあと思って、'Root --> ...のように中置演算子を使って書くようにしてみたんだけど、やっぱりこれでも見づらいなあ、ということで最終的に'Root(...)のような形式に落ち着いたのであった。その点、S式なら、と思わなくも無いんだけど、素のS式だと、プログラマの意図(ここでは、S式中のどの部分がノード名で、どの部分が子ノードか、などの情報)が抜け落ちてしまうのがあまり好きじゃなかったりする。