web-dev-qa-db-ja.com

リレーショナルデータに基づいて階層構造を生成するプロセス?

私は従業員のID、名前、直属の上司のIDを含む参照列のcsvを持っています。

 emp_id, emp_name, mgr_id
1,The Boss,,
2,Manager Joe,1
3,Manager Sally,1
4,Peon Frank,2
5,Peon Jill,2
6,Peon Rodger,3
7,Peon Ralph,3

この構造を表す(json)オブジェクトを生成できるようにしたいのですが、

DATA = {
  "parent": "The Boss",
  "children: [
    {
      "parent": "Manager Joe",
      "children": [ {"parent": "Peon Frank"}, {"parent": "Peon Jill"} ]
    },
    {
      "parent": "Manager Sally",
      "children": [ {"parent": "Peon Rodger"}, {"parent": "Peon Ralph" } ]
    }]
}

したがって、データから、mgr_idのないエントリはCEOまたはLeaderのようなものを表します。

したがって、いくつかの考えを収集するだけです。これは、ツリートラバーサルが正しい出力を生成するツリーデータ構造で表すことができることを知っています。パーサーは、ツリーへの挿入を担当する必要があります。たぶん子供の数はあなたに重みを与えるでしょうか?考えを集めるだけです。子供で複数のオブジェクトを構築できるという事実を中心にどのようにピボットするかわかりません。

私が考えていないこの構造を解析できるアルゴリズムが定義されていますか?子供たちに降りるのは比較的簡単なようです。私はこれを頭の中で見ていません。助けが必要かもしれません。ありがとうございました

8
darethas

フラットランドから構造を作成する方法はいくつかあります。再帰は最高の1つです。このプログラムはそれを使用し、どのアイテムが親であるかを把握するための予備ステップを備えています。

戦略は言語に依存しませんが、すべての言語にはデータ構造とビルディングブロックの詳細があります。このアプローチをPythonでレンダリングしました。

注:このコードの約半分は表示とデモンストレーションを目的としているため、これをたどってアルゴリズムがどのように機能しているかを確認できます。製造については、これらのパーツを削除するための料金は無料です。

ああ...それからラベルを「親」から「名前」に、「子供」を「レポート」に変更しました。もちろん、好きなものを選ぶことができます。

from pprint import pprint
from random import shuffle
from collections import defaultdict
import json

def show_val(title, val):
    """
    Debugging print helpler.
    """
    sep = '-' * len(title)
    print "\n{0}\n{1}\n{2}\n".format(sep, title, sep)
    pprint(val)


# ORIGINAL NAMES:   emp_id, emp_name, mgr_id
# SIMPLIFIED NAMES: eid,    name,     mid
text = """
323,The Boss,
4444,Manager Joe,323
3,Manager Sally,323
4,Peon Frank,4444
33,Peon Dave,3
5,Peon Jill,4444
6,Peon Rodger,3
7,Peon Ralph,3
233,Clerk Jane,99
99,Supervisor Henri,3
"""

# parse text into lines
lines = [ l.strip() for l in text.strip().splitlines() ]

# construct list of people tuples
people = [ Tuple(l.split(',')) for l in lines ]

# for demonstration and testing only, shuffle the results
shuffle(people)
show_val("randomized people", people)

# contstruct list of parents
parents = defaultdict(list)
for p in people:
    parents[p[2]].append(p)
show_val("parents", parents)

def buildtree(t=None, parent_eid=''):
    """
    Given a parents lookup structure, construct
    a data hierarchy.
    """
    parent = parents.get(parent_eid, None)
    if parent is None:
        return t
    for eid, name, mid in parent:
        report = { 'name': name }
        if t is None:
            t = report
        else:
            reports = t.setdefault('reports', [])
            reports.append(report)
        buildtree(report, eid)
    return t

data = buildtree()
show_val("data", data)

show_val("JSON", json.dumps(data))

これを実行すると、次の出力が表示されます。

-----------------
randomized people
-----------------

[('233', 'Clerk Jane', '99'),
 ('4444', 'Manager Joe', '323'),
 ('33', 'Peon Dave', '3'),
 ('6', 'Peon Rodger', '3'),
 ('99', 'Supervisor Henri', '3'),
 ('3', 'Manager Sally', '323'),
 ('5', 'Peon Jill', '4444'),
 ('323', 'The Boss', ''),
 ('4', 'Peon Frank', '4444'),
 ('7', 'Peon Ralph', '3')]

-------
parents
-------

defaultdict(<type 'list'>, {'99': [('233', 'Clerk Jane', '99')], '323': [('4444', 'Manager Joe', '323'), ('3', 'Manager Sally', '323')], '3': [('33', 'Peon Dave', '3'), ('6', 'Peon Rodger', '3'), ('99', 'Supervisor Henri', '3'), ('7', 'Peon Ralph', '3')], '4444': [('5', 'Peon Jill', '4444'), ('4', 'Peon Frank', '4444')], '': [('323', 'The Boss', '')]})

----
data
----

{'name': 'The Boss',
 'reports': [{'name': 'Manager Joe',
              'reports': [{'name': 'Peon Jill'}, {'name': 'Peon Frank'}]},
             {'name': 'Manager Sally',
              'reports': [{'name': 'Peon Dave'},
                          {'name': 'Peon Rodger'},
                          {'name': 'Supervisor Henri',
                           'reports': [{'name': 'Clerk Jane'}]},
                          {'name': 'Peon Ralph'}]}]}

----
JSON
----

'{"name": "The Boss", "reports": [{"name": "Manager Joe", "reports": [{"name": "Peon Jill"}, {"name": "Peon Frank"}]}, {"name": "Manager Sally", "reports": [{"name": "Peon Dave"}, {"name": "Peon Rodger"}, {"name": "Supervisor Henri", "reports": [{"name": "Clerk Jane"}]}, {"name": "Peon Ralph"}]}]}'

いくつかの予備:ネストされたデータ構造を表示するためにprintも使用します。通常、データベース接続を介してデータを取得します。ここでは、静的テキストから解析するだけです。最後に、あなたが提示したデータは美しく順序付けられており、ボスが一番上にあり、従業員ID番号が最も小さい(単純化する)問題がありましたが、コードがどの順序でも機能することを確認したいと思います。そのため、非順次割り当てを反映するようにいくつかのID番号を変更し、random.shuffleデータの順序をランダム化します。これは本番環境では行いませんが、テストの一環として、ロジックが偶然ではなく設計どおりに機能していることの信頼性を高めます。

4
Jonathan Eunice

永続化の形式としてCSVファイルがあります。 SQLite のような他のアプローチがあります。構造を照会するためのもう少しの機能を提供するために調査する必要があるかもしれませんが、完全なツリー構造を構築してシリアル化するという問題が依然としてあります。完全なツリー構造にする必要があります。

CSVを使用する場合は、すべての行を読み取り、対応するツリー構造を構築してから、おそらくjsonシリアル化用のライブラリを使用して、それを書き戻す必要があります。あなたがいくつかのRubyビットオーバーSOを持っていることに注意してください、それは RubyオブジェクトとJSONシリアライゼーション(Railsなし) の答えのように見えるかもしれません。 [〜#〜] csv [〜#〜] gem。これは単なる例ですが、このようなCSVパーサーとJSONシリアル化ターゲットは、リモートで実用的な値のすべての言語に存在します。

それは本当に結局のところです。次のようなものになる可能性があります。

_1,The Boss,,
2,Founder Dev,7
3,Manager Joe,1
4,Peon Frank,3
5,Peon Jill,3
6,Peon Rodger,7
7,Manager Sally,1
_

したがって、シリアル化するjsonを作成する前に、それを読む必要がありますall。それを回避する方法はありません。


これで、csvに別の構造が存在する可能性があります。これにより、3つの構造全体を構築してから作業する必要なく、より便利なクエリをすばやく実行できます。

ネストされたセット変更されたプレオーダーツリー としても参照でき、便利です。

ツリーは次のようになります。

_                1 Boss 14                   

2 Manager Sally 7       8 Manager Joe 13     

3 Dev 4 | 5 Rodger 6    9 Frank 10 | 11 Jill 12
_

そして、対応するファイルは次のように書き出されます。

_# name  L   R
Boss,   1,  14
Sally,  2,  7
Dev,    3,  4
Rodger, 5,  6
Joe,    8,  13
Frank,  9,  10
Jill,   11, 12
_

このツリーでは、_R-L > 1_であるすべての行を見つけることで、「すべてのマネージャーを見つける」などのことができます。または、_L > 2 and R < 7_であるすべての行を検索して、「サリーに報告している全員を見つける」。特定の人物に報告する人数は_(R-L-1)/2_です。これにより、ツリーに対する特定の質問を(ツリー自体を構築する必要なく)より速く読むことができます。

変更されたプレオーダーツリーには、組織ツリーへの 隣接リスト アプローチと比較して、ツリーへの更新が多数の行に影響を与えるという費用が伴います。挿入すると、場所全体で数値がシフトします。たとえば、サリーが別の人に報告している場合、その人の値は_7,8_になり、全員のif(L > 7) L = L + 2if (R > 7) R = R + 2を変更する必要があります。

2
user40980

Jonathan Euniceによる解決策は、非常にクリーンでわかりやすいものです。親のリストを作成するときは、親ディクショナリの値側から親情報を切り捨てます。これの代わりに:

# contstruct list of parents
parents = defaultdict(list)
for p in people:
    parents[p[2]].append(p)
show_val("parents", parents)

私はこれを持っています:

# contstruct list of parents
parents = defaultdict(list)
for p in people:
    parents[p[2]].append(p[:-1])
show_val("parents", parents)

そのマネージャIDを削除しているので、それに応じてbuidtree関数を調整します。

(私がしているのは、forループ内にmid変数をドロップすることだけです。もっと読みやすいと思います。そうしなかった場合もエラーが発生します)

def buildtree(t=None, parent_eid=''):
    """
    Given a parents lookup structure, construct
    a data hierarchy.
    """
    parent = parents.get(parent_eid, None)
    if parent is None:
        return t
    for eid, name in parent:
        report = {'name': name}
        if t is None:
            t = report
        else:
            reports = t.setdefault('reports', [])
            reports.append(report)
        buildtree(report, eid)
    return t

したがって、スクリプトを再度実行すると、次の出力が得られます。

-----------------
randomized people
-----------------

[('6', 'Peon Rodger', '3'),
 ('323', 'The Boss', ''),
 ('33', 'Peon Dave', '3'),
 ('4444', 'Manager Joe', '323'),
 ('7', 'Peon Ralph', '3'),
 ('4', 'Peon Frank', '4444'),
 ('233', 'Clerk Jane', '99'),
 ('99', 'Supervisor Henri', '3'),
 ('5', 'Peon Jill', '4444'),
 ('3', 'Manager Sally', '323')]

-------
parents
-------

defaultdict(<class 'list'>,
            {'': [('323', 'The Boss')],
             '3': [('6', 'Peon Rodger'),
                   ('33', 'Peon Dave'),
                   ('7', 'Peon Ralph'),
                   ('99', 'Supervisor Henri')],
             '323': [('4444', 'Manager Joe'), ('3', 'Manager Sally')],
             '4444': [('4', 'Peon Frank'), ('5', 'Peon Jill')],
             '99': [('233', 'Clerk Jane')]})

----
data
----

{'name': 'The Boss',
 'reports': [{'name': 'Manager Joe',
              'reports': [{'name': 'Peon Frank'}, {'name': 'Peon Jill'}]},
             {'name': 'Manager Sally',
              'reports': [{'name': 'Peon Rodger'},
                          {'name': 'Peon Dave'},
                          {'name': 'Peon Ralph'},
                          {'name': 'Supervisor Henri',
                           'reports': [{'name': 'Clerk Jane'}]}]}]}

----
JSON
----

('{"name": "The Boss", "reports": [{"name": "Manager Joe", "reports": '
 '[{"name": "Peon Frank"}, {"name": "Peon Jill"}]}, {"name": "Manager Sally", '
 '"reports": [{"name": "Peon Rodger"}, {"name": "Peon Dave"}, {"name": "Peon '
 'Ralph"}, {"name": "Supervisor Henri", "reports": [{"name": "Clerk '
 'Jane"}]}]}]}')
1
Rudy