[topic] 親子のペアからツリーを構築

親子ペア定義群からツリー構造をつくります

・親子ペア定義
※親->子
A->B
B->C
C->D
C->E
A->F
D->X
Y->Z
Z->C

・ツリー
A
->B
  ->C
    ->D
      ->X
    ->E
->F
Y
->Z
  ->C
    ->D
      ->X
     ->E
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
; 親子ペア定義
(setf pair-lst '((a b)
                 (b c)
                 (c d)
                 (c e)
                 (a f)
                 (d x)
                 (y z)
                 (z c)
                 ))

;; 同一の親を持つペアをマージする
;; ex) ((p1 c1) (p1 c2) (p2 c3) (p2 c4)) -> ((p1 c1 c2) (p2 c3 c4))
(defun gen-fmly-lst (plst)
  (defun merge-lst (frst rst)
    (if (null rst)
      frst
      (if (eq (car frst) (caar rst))
        (merge-lst (append frst (cdar rst)) (cdr rst))
        (merge-lst frst (cdr rst)))))
  (labels ((rec (lst acc)
                (if (null lst)
                  acc
                  (if (member (caar lst) acc :key #'car)
                    (rec (cdr lst) acc)
                    (rec (cdr lst) (cons (merge-lst (car lst) (cdr lst)) acc))))))
    (rec plst nil)))

;; fmlyのリストからツリーを生成する
(defun gen-fmly-tr (fmly-lst)
  (labels ((rec (flst acc)
                (if (null flst)
                  acc
                  ; hook前のfmlyをhook後のfmly(n次元リスト)に置き換えて処理をする
                  (let* ((nD-fmly (car (member (caar flst) acc :key #'car)))
                         (othr (other nD-fmly acc)))
                    ; fmlyをhook処理し、一箇所もhookできなかったらfmlyをそのままaccに加える
                    (if (equal othr (hook nD-fmly othr))
                      (rec (cdr flst) (cons nD-fmly othr))
                      (rec (cdr flst) (hook nD-fmly othr)))))))
    (rec fmly-lst fmly-lst)))

;; リストからobjを除いたリストを取得する
(defmacro other (obj lst)
  `(remove-if #'(lambda (x) (equal ,obj x)) ,lst))

;; リストのcarと一致するatomをツリーの中で見つけた場合、該当atomをリストで置き換える
;; ex) (hook '(a x) '(a (b a) (c (d a)))) -> ((a x) (b (a x)) (c (d (a x))))
(defun hook (lst tree)
  (cond ((eq (car lst) tree) lst)
        ((atom tree) tree)
        (t (cons (hook lst (car tree))
                 (hook lst (cdr tree))))))

(defun main ()
  (gen-fmly-tr (gen-fmly-lst pair-lst)))
; 実行すると、下記の構造が得られる
; ((A (B (C (D X) E)) F) (Y (Z (C (D X) E))))
; インデントすると
;((A
;   (B
;     (C
;       (D
;         X)
;       E))
;   F)
; (Y
;   (Z
;     (C
;       (D
;         X)
;        E)))
; )

Posted feedbacks - Flatten

Nested Hidden

Pythonだとこんな感じでしょうか。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Node:
    def __init__(self, name):
        self.name = name
        self.parent = None
        self.children = []

def pairs_to_trees(pairs):
    nodes = {}
    for parent_name, child_name in pairs:
        parent = nodes.setdefault(parent_name, Node(parent_name))
        child  = nodes.setdefault(child_name,  Node(child_name))
        child.parent = parent
        parent.children.append(child)
    return [node for node in nodes.itervalues() if node.parent is None]

def show(node, depth):
    if depth == 0:
        print node.name
    else:
        print "  " * (depth - 1) + "->" + node.name
    for child in node.children:
        show(child, depth + 1)

def main():
    pairs = [
        ('A', 'B'),
        ('B', 'C'),
        ('C', 'D'),
        ('C', 'E'),
        ('A', 'F'),
        ('D', 'X'),
        ('Y', 'Z'),
        ('Z', 'C'),
    ]

    for tree in pairs_to_trees(pairs):
        show(tree, 0)

if __name__ == '__main__':
    main()

ツリーにおいて、ノードの親は多くても一つです。
ですから、この例のB->CのCとZ->CのCは別のものなのでしょう。
そうすると、なぜ、A->BのAとA->FのAは同じものだと考えるのでしょうか?

>(setf env (gen-family-relation pair-lst '()))
((Z C)(Y Z)(D X)(C D E)(B C)(A B F))
>(query 'A env)
(A (B (C (D X) E)) F)
>(query 'Y env)
(Y (Z (C (D X) E)))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(setf pair-lst '((a  b)
                 (b  c)
                 (c  d)
                 (c  e)
                 (a  f)
                 (d  x)
                 (y  z)
                 (z  c) ))

(defun gen-family-relation (list env)
  (let ( node result )
    (dolist (x list result)
        (setf node (assoc (car x) env))
        (if node
            (setf (cdr node) (append (cdr node) (cdr x)))
            (push x env) )
        (setf result env) )))

(defun query (s env)
  (let ((node (assoc s env)))
    (if node
        (cons (car node)(mapcar #'(lambda (x)(query x env)) (cdr node)))
        s)))

ご指摘ありがとうございます。
設定の説明が至らず申し訳ありません。

このコードは、RDBテーブル間の外部参照関係を整理するために書きました。

従って、一つのノードは一つのテーブルを表しており、
一つのテーブルは複数のテーブルを参照する(親に持つ)ことができます。

ご指摘いただいた"C"はそのケースで、ツリーの中で複数回現れますが、
(意味的には)同一のテーブルを表し、"C"以下の親子関係の構造は一致している必要があります。

「ツリーにおいてノードの親」というより、 血脈としての親子関係だと私は理解しました。(そしてその血脈をツリーとして表す) なので、子供には、女親と男親の2つがあっても良いと思います。

この場合、データ構造として、「グラフ」を使うのがふつうだと思うのですが、
あえて、「木」を使うのには、何か特別な理由があるのでしょうか?
(最初に実装したのがLispだから?)

コメントありがとうございます。

以下のように、テーブルの参照関係をインデントを使ってプレーンテキストに出力するイメージでいた為です。
しかしご指摘の通り出力前のデータとしては、グラフ構造であるべきという気がします。

-------------------------------------------------------
テーブルA
  テーブルB
  テーブルC
    テーブルD
テーブルX
  テーブルC
…

そうですね。
「このテーブルの親は。。さらにその親は。。」と辿っていける構造を作りたかったので、
血脈という言葉がしっくりきます。

ありがとうございました。

とりあえず構造体とハッシュをつかって。treeのユーティリティは
ANSI Common Lispにとりあげられていたよ。

DOKAKU178> (maphash #'(lambda(k v)
			(print-tree k)) *population*)
NIL => A => (F B) 
(A) => B => (C) 
(Z B) => C => (E D) 
(C) => D => (X) 
(C) => E => NIL 
(A) => F => NIL 
(D) => X => NIL 
NIL => Y => (Z) 
(Y) => Z => (C) 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
(defpackage :dokaku178
  (:use :common-lisp))
(in-package :dokaku178)

(defvar *population* (make-hash-table))
(defstruct tree
   id children parents)

(defun get-node (key)
  (multiple-value-bind (hash-value flag) (gethash key *population*)
    (if (null flag)
    (setf (gethash key *population*) (make-tree :id key
                          :parents nil
                          :children nil))
    hash-value)))

(defun set-node (pair-list)
  (let* ((parent (car pair-list))
     (child (cdr pair-list))
     (parent-node (get-node parent))
     (child-node (get-node child)))
    (pushnew parent (tree-parents child-node))
    (pushnew child (tree-children parent-node))))

(defun get-origins ()
  (let ((origins nil))
    (maphash #'(lambda(k v)
         (if (null (tree-parents v))
             (pushnew k origins))) *population*)
    origins))

(defun print-tree (id)
  (let ((id (gethash id *population*)))
    (format t "~a => ~a => ~a ~%"  (tree-parents id)
                               (tree-id id)
                   (tree-children id))))

(defun main()     
  (let ((pair-lst
     '((a . b)(b . c)(c . d)(c . e)
       (a . f)(d . x)(y . z)(z . c))))
    (loop for n in pair-lst do
     (set-node n))))

ふと。

有向辺のリストを与えて、グラフを作るというお題なんだと思うんだけど、何ができれば、グラフ構造ができたことになるんだろう? あるいはどのようなものが表示されれば、グラフ構造ができてるなと確認できるのだろう?

極端な言い分だと、親子関係のペアのリストがあればそれがグラフだとか木だと主張することもできるわけですよね。


JavaScript でプロパティでツリーを表現してみました。
実行例:
add("A", "B");
add("B", "C");
add("C", "D");
add("C", "E");
add("A", "F");
add("D", "X");
add("Y", "Z");
add("Z", "C");
print(family_toString(family));
/* 出力 */
A
->F
->B
  ->C
    ->D
      ->X
    ->E
Y
->Z
  ->C
    ->D
      ->X
    ->E
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var cache = {};
var family = {};

function add(parent, child){
    var p,c;
    if(parent in cache){
        p = cache[parent];
    } else { //初めての名前
        p = family[parent] = { name : parent };
        cache[parent] = p;
    }
    if(child in cache){
        p[child] = cache[child];
    } else { //初めての名前
        c = p[child] = { name : child };
        cache[child] = c;
    }
}

family_toString.tab = "  ";

function family_toString(f, tab){
    var result = "";

    if(tab == null) tab = -1;

    var indent = arguments.callee.tab.x(tab);

    for( x in f){
        if(x == "name") continue;
        else{
            result += indent + (tab < 0 ? "" : "->") + f[x].name + "\n";
            result += family_toString(f[x], tab+1);
        }
    }
    return result;
}

if(!String.prototype.x){
    String.prototype.x = function(n){
        var result="", i;
        for(i=0;i<n;i++){
            result += this;
        }
        return result;
    }
}

既存の子を追加する時、
親がいなかったノードが後から親を指定された時ルート参照を削除した方がよい
1
2
3
if(child in family){
    delete family[child];
}

グラフの数学的な定義は、G=(V;E)ですから、頂点のリストと辺のリストがあれば、グラフを作ったことになるんじゃないですかね。 問題については、有向辺のリストから頂点のリストを推定せよ、ってことじゃないかと。


Index

Feed

Other

Link

Pathtraq

loading...