web-dev-qa-db-ja.com

ツリーに含まれているデータを使用しているときに、ツリーにデータを入力するにはどの方法を使用すればよいですか?

VWXYZの5種類のノードで構成されるツリーがあります。 Vはルートノードを表し、特定のツリーにはV型の要素が1つだけあります。 Vには1つ以上のWsが含まれ、Wには1つ以上のXsが含まれ、Xには1つ以上のYs、およびYには1つ以上のZsが含まれます。

tree structure

これを見て、私はこれを次のクラス構造でモデル化することにしました:

class structure

クラス構造には、各タイプのノードに固有のデータ要素と、それらのデータ要素にアクセスするためのメソッドは示されていません。ただし、各ノードには固有の属性と操作のセットがあります。

サイズの観点から、Zレベルには最大で数千のノードが存在する可能性があります。 WからWへの1:1:1:1マッピングのケースがほとんどないため、Zレベルでははるかに小さくなります。

私の問題は、このツリーの構築方法を考えるときに発生します。

データは、ストリームと考えることができるものに入ってきます。 DataChunkと呼ぶユニットがあります。 DataChunkには、ツリーの1つ以上のレベルのデータが含まれています。 DataChunkZのデータが含まれている場合、YX、およびWのデータも含まれています。ただし、DataChunkのデータを含むXには、Wのデータが含まれますが、必ずしもYである必要はありません。

ストリームでは、DataChunkの開始は sync Word でマークされます。データグラムのセットでは、各DataChunkは単一のデータグラムです。ファイルのセットでは、DataChunkは、1つのファイルが1つのDataChunkであるか、1つのファイルが複数のDataChunksを含み、それぞれが同期ワードによってマークされているファイルのセットとして提示できます。 。順序は指定されていません。

ツリーを構築するには、DataChunkを受け入れ、そこから情報を解析する必要があります。

1つのアプローチは、各ノードにDataChunkオブジェクトを受け入れ、情報を解析し、独自の属性を設定してから、ノードに渡す一致する子があるかどうかを確認することです。対応する子が存在する場合は、それにデータチャンクを渡します。対応する子が存在しない場合は、作成してからデータチャンクを渡します。ただし、これにより変更可能なオブジェクトが残ります。作成後、任意のDataChunkを任意のノードに渡し、ツリーを変更できます。各データノードは、それに関連付けられているデータだけでなく、ツリーの構築にも関与しているため、これもSRPの違反のようです。

別のアプローチは、ある種のビルダーアプローチを使用することですが、これは私のクラスを10に膨らませます。 Builderは基本的に、上記のツリー構造を返す変更可能なインターフェースを持つ2番目のツリーであるため、これはかなり非効率的であるようにも思われます。時間やメモリ消費の点でパフォーマンスが悪いようです。

今後の柔軟性にも少し関心があります。ツリーデータモデルは変更される可能性はほとんどありませんが、DataChunkの形式が変更されるか、データを使用してツリーを構築するための追加の形式が追加される場合があります。これは、ビルダースタイルのアプローチのもう1つのポイントかもしれません。

私が見落としている、データを消費する/ツリーを構築するアプローチはありますか? SRPの違反のように見えるものを実行し、クライアントに可変データ構造を提示するか(DataChunkのインスタンスがあるか、または構成されている場合)、またはクライアントに提示するより複雑なビルダーを使用することが望ましいでしょうか?よりすっきりしたインターフェースですが、コードベースはより複雑です。

6
Thomas Owens

わかりました、これが私が思いついたものです。これにはJavaが含まれているので、コーヒーを飲みましょう。アイルランド人。 (同じ理由で)

ある意味で、木は自己に似ています。結局のところ、ツリーはグラフです。各ノードは一定数の他のノードを認識しています。

Nodesと言えば:

_import Java.util.HashMap;

public class Node <Child extends ICanBeMergedWith<? super Child> & ICanBecomeImmutable> implements ICanBecomeImmutable, ICanBeMergedWith<Node<Child>>
{
    private HashMap<Child, Child> children;
    private String name;
    private boolean mutable;

    public Node(String name)
    {
        this.name = name;
        children = new HashMap<Child,Child>();
        mutable = true;
    }

    public boolean add(Child child)
    {
        if (!mutable)
        {
            return false;
        }

        if(!children.containsKey(child))
        {
            children.put(child, child);
        }
        else
        {
            children.get(child).merge(child);
        }

        return true;
    }

    @Override
    public void merge(Node<Child> other)
    {
        if (!mutable || !equals(other))
        {
            return;
        }

        for(Child child : other.children.values())
        {
            add(child);
        }
    }

    @Override
    public String toString()
    {
        String s = name + ":";
        for (Child child : children.values())
        {
            s += System.getProperty("line.separator") + child.toString().replaceAll("(?m)^", "\t");
        }
        return s;
    }

    @Override
    public void petrify()
    {
        mutable = false;

        for (Child child : children.values())
        {
            child.petrify();
        }
    }

    @Override
    public int hashCode()
    {
        return name.hashCode();
    }

    @Override
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        if (name == null)
            if (((Node<Child>) obj).name != null)
                return false;
        return name.equals(((Node<Child>) obj).name);
    }
}
_

そのことを前もって提供して申し訳ありませんが、それを回避する方法はありません。

  1. 各Nodeは、それ自体を他のノードと比較できる必要があります。同じノードをモデル化するさまざまなオブジェクトを作成できるため(そして、着信データストリームに応じて)、これにより大きな利点が得られます。 、次にそれをすべて一緒に投げて、オブジェクトがマージする必要があるものをオブジェクトに認識させます。Javalandでは、equals()およびhashCode()を使用していくつかの不正操作を行う必要があります。これをウェブから一緒にコピーしました。XYなどの実際の実装がユースケースで何をするかに応じて、独自の実装を考え出す必要があります。同じタイプの2つのノードが同じ名前を持っている場合、それらは等しいです。
  2. _HashMap<Child, Child> children;_があります。これは、上記で話していた他の既知のNodesのリストです。確かに、_HashSet<Child>_も機能する可能性があります。必要なのは、その子を一意に保つデータ構造ですが、HashSetから何かを取得するのは難しいため、追加する必要があります。より多くのものを。基本的に、_HashMap<Child, Child>_は_HashSet<Child>_と同じように使用されますが、より便利(かつ高速)です。
  3. このリストはadd(Child child)メソッドを介して入力されます。子がリストにない場合は、そのまま追加します。子が既にリストにある場合、これは、このexact子が必ずしもリストにあるとは限らないが、等しいと見なされる一部の子であるため、パラメーターとして受け取った着信子は、子にマージされるそれはすでにリストにあります。これを、名前が付いた2つのフォルダーを一緒に配置するようなものだと考えてください。これは、同じサブフォルダーがあることを意味するものではありません。これを行うには、大きなコードブロックをリストに統合できないため、以下に示す_ICanBeMergedWith<T>_を作成する必要がありました(エディター?!)
  4. あなたが言ったように、これは変更可能なデータ構造をあなたに残します。これに対する私の最初の解決策は、クラスごとにNullObjectインスタンスを作成することでした。したがって、すべての変更メソッド呼び出しは、クラス内のthisまたはNullObjectに委任できます。 staticとジェネリック型はうまく機能しないことがわかりました。汎用の内部NullObjectシングルトンを作成できませんでした。上記のように、内部データ構造へのアクセスを妨げる単純なフラグ変数を使用しました。そのためにICanBecomeImmutableを作成しました。
  5. これらのインターフェースは、_Node<Child>_とChildを区別するための単なるマーカーインターフェースです。自己型があれば(つまり、クラスthisが何であっても何かを入力できるようになれば)、これはずっと簡単になると思いますが、それが欠けていて、スキルが足りないので、これが解決策です思い付いた。明示的にタイプすることもできますが、すぐに_Node<Node<Node<V>>>_で醜くなります。または、私はここで完全に間違っているのかもしれません。

Nodeはこれで終わりです。実際のクラスは少し簡単です、ここにそれらがあります:

V:

_public class V extends Node<W>
{
    public V(String name)
    {
        super(name);
    }
}
_

W:

_public class W extends Node<X>
{
    public W(String name)
    {
        super(name);
    }
}
_

バツ:

_public class X extends Node<Y>
{
    public X(String name)
    {
        super(name);
    }
}
_

Y:

_public class Y extends Node<Z>
{
    public Y(String name)
    {
        super(name);
    }
}
_

Z:

_public class Z implements ICanBecomeImmutable, ICanBeMergedWith <Z>
{
    @Override
    public String toString()
    {
        return "Z";
    }

    @Override
    public void petrify()
    {
        // nothing to do
    }

    @Override
    public void merge(Z other)
    {
        // whatever
    }
}
_

ZNodeには認識できますが、Node自体ではないため、少し異なります。 2つの空のメソッドは悪くないと思います。

最後に重要なことですが、上記のインターフェース:

マージのためのインターフェース:

_public interface ICanBeMergedWith<T>
{
    void merge(T other);
}
_

オブジェクトを不変にするためのインターフェース:

_public interface ICanBecomeImmutable
{
    void petrify();
}
_

小さなテストアプリケーションを作成しました。

_public class Main
{
    public static void main(String[] args)
    {
        // building main tree
        System.out.println("========= main tree ========= ");
        V v1 = new V("v1");

        W w1 = new W("w1");
        W w2 = new W("w2");

        X x1 = new X("x1");
        X x2 = new X("x2");

        Y y1 = new Y("y1");
        Y y2 = new Y("y2");

        v1.add(w1);
        // v1.petrify();
        v1.add(w2);

        w1.add(x1);
        w2.add(x2);

        x1.add(y1);
        // v1.petrify();
        x2.add(y2);

        y1.add(new Z());
        y1.add(new Z());
        y1.add(new Z());
        // v1.petrify();
        y1.add(new Z());
        y2.add(new Z());
        y2.add(new Z());

        System.out.println(v1);
        System.out.println("========= sub tree ========= ");

        // building second tree
        V v2 = new V("v1");  // !!! same name as v1 object, should be merged to one
        W w3 = new W("w2");  // !!! same name as w2 object, should be merged to one
        X x3 = new X("x2");  // !!! same name as x2 object, should be merged to one
        X x4 = new X("x4");
        Y y3 = new Y("y2");  // !!! same name as y2 object, should be merged to one

        v2.add(w3);

        w3.add(x3);
        w3.add(x4);

        x3.add(y3);

        y3.add(new Z());
        y3.add(new Z());
        y3.add(new Z());
        y3.add(new Z());
        y3.add(new Z());

        System.out.println(v2);

        System.out.println("========= merge tree ========= ");

        v1.merge(v2);
        System.out.println(v1);
    }
}
_

ツリーのさらなる拡張を停止するために、いくつかのv1.petrify();が散在しています。

出力は次のとおりです。

_========= main tree ========= 
v1:
    w1:
        x1:
            y1:
                Z
                Z
                Z
                Z
    w2:
        x2:
            y2:
                Z
                Z
========= sub tree ========= 
v1:
    w2:
        x2:
            y2:
                Z
                Z
                Z
                Z
                Z
        x4:
========= merge tree ========= 
v1:
    w1:
        x1:
            y1:
                Z
                Z
                Z
                Z
    w2:
        x2:
            y2:
                Z
                Z
                Z
                Z
                Z
                Z
                Z
        x4:
_

DataChunksがどのように見えるかわからないので、これが私の部分的な答えの終わりです。次に、データをツリーに変換してから、ツリーをメインツリーとマージする必要があります。私の知る限り。すべてのDataChunkは、いくつかのVNodeから開始する必要があります。そうしないと、このサブツリーがどの親Nodeに属しているかが不可能なためです。しかし、そうでなかったとしても、ツリーのすべてのレベルでNodesを追加してマージすることができます。ストリームが終了したら、root VNodepetrify()を呼び出して、データがさらに変更されないようにします。

また、さらにいくつかのテストを行います。

おそらくfinalになるはずです。何かがfinalであれば、より専門的に見えます。真剣にではない、これはすべての細部が完全に考え抜かれたわけではないことを示すためです。これは、タイプと指定された要件から、これが可能であることを証明するためです。それ以上、それ以下。


免責事項:これまでのところ、ジェネリックの経験はあまりありません。おそらくこれが史上最悪の恐ろしいナンセンスです。私はこれを不可能だと考えようとしていたところ、最後に試してみると、作成するために「super」と入力して上限が設定され、すべての赤い線が消えました。マジック!

また、ええ、WTF、programmers.SEに実装を投稿するのはどうですか?重要なのは、ホワイトボードで確実に何かを実行できることですが、実装なしでは結果はありません。そして、最終的に選択した言語によってサポートされるものは、実際の実装を行うために必要な労力に大きな違いをもたらします。 「コンパイル」を押すことはできず、コンパイラは常に正しいです。それが私が具体的な実装に行った理由です。

4
null