V
、W
、X
、Y
、Z
の5種類のノードで構成されるツリーがあります。 V
はルートノードを表し、特定のツリーにはV
型の要素が1つだけあります。 V
には1つ以上のW
sが含まれ、W
には1つ以上のX
sが含まれ、X
には1つ以上のY
s、およびY
には1つ以上のZ
sが含まれます。
これを見て、私はこれを次のクラス構造でモデル化することにしました:
クラス構造には、各タイプのノードに固有のデータ要素と、それらのデータ要素にアクセスするためのメソッドは示されていません。ただし、各ノードには固有の属性と操作のセットがあります。
サイズの観点から、Z
レベルには最大で数千のノードが存在する可能性があります。 W
からW
への1:1:1:1マッピングのケースがほとんどないため、Z
レベルでははるかに小さくなります。
私の問題は、このツリーの構築方法を考えるときに発生します。
データは、ストリームと考えることができるものに入ってきます。 DataChunk
と呼ぶユニットがあります。 DataChunk
には、ツリーの1つ以上のレベルのデータが含まれています。 DataChunk
にZ
のデータが含まれている場合、Y
、X
、およびW
のデータも含まれています。ただし、DataChunk
のデータを含むX
には、W
のデータが含まれますが、必ずしもY
である必要はありません。
ストリームでは、DataChunk
の開始は sync Word でマークされます。データグラムのセットでは、各DataChunk
は単一のデータグラムです。ファイルのセットでは、DataChunk
は、1つのファイルが1つのDataChunk
であるか、1つのファイルが複数のDataChunk
sを含み、それぞれが同期ワードによってマークされているファイルのセットとして提示できます。 。順序は指定されていません。
ツリーを構築するには、DataChunk
を受け入れ、そこから情報を解析する必要があります。
1つのアプローチは、各ノードにDataChunk
オブジェクトを受け入れ、情報を解析し、独自の属性を設定してから、ノードに渡す一致する子があるかどうかを確認することです。対応する子が存在する場合は、それにデータチャンクを渡します。対応する子が存在しない場合は、作成してからデータチャンクを渡します。ただし、これにより変更可能なオブジェクトが残ります。作成後、任意のDataChunk
を任意のノードに渡し、ツリーを変更できます。各データノードは、それに関連付けられているデータだけでなく、ツリーの構築にも関与しているため、これもSRPの違反のようです。
別のアプローチは、ある種のビルダーアプローチを使用することですが、これは私のクラスを10に膨らませます。 Builderは基本的に、上記のツリー構造を返す変更可能なインターフェースを持つ2番目のツリーであるため、これはかなり非効率的であるようにも思われます。時間やメモリ消費の点でパフォーマンスが悪いようです。
今後の柔軟性にも少し関心があります。ツリーデータモデルは変更される可能性はほとんどありませんが、DataChunk
の形式が変更されるか、データを使用してツリーを構築するための追加の形式が追加される場合があります。これは、ビルダースタイルのアプローチのもう1つのポイントかもしれません。
私が見落としている、データを消費する/ツリーを構築するアプローチはありますか? SRPの違反のように見えるものを実行し、クライアントに可変データ構造を提示するか(DataChunk
のインスタンスがあるか、または構成されている場合)、またはクライアントに提示するより複雑なビルダーを使用することが望ましいでしょうか?よりすっきりしたインターフェースですが、コードベースはより複雑です。
わかりました、これが私が思いついたものです。これにはJava
が含まれているので、コーヒーを飲みましょう。アイルランド人。 (同じ理由で)
ある意味で、木は自己に似ています。結局のところ、ツリーはグラフです。各ノードは一定数の他のノードを認識しています。
Node
sと言えば:
_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);
}
}
_
そのことを前もって提供して申し訳ありませんが、それを回避する方法はありません。
Java
landでは、equals()
およびhashCode()
を使用していくつかの不正操作を行う必要があります。これをウェブから一緒にコピーしました。X
、Y
などの実際の実装がユースケースで何をするかに応じて、独自の実装を考え出す必要があります。同じタイプの2つのノードが同じ名前を持っている場合、それらは等しいです。HashMap<Child, Child> children;
_があります。これは、上記で話していた他の既知のNode
sのリストです。確かに、_HashSet<Child>
_も機能する可能性があります。必要なのは、その子を一意に保つデータ構造ですが、HashSet
から何かを取得するのは難しいため、追加する必要があります。より多くのものを。基本的に、_HashMap<Child, Child>
_は_HashSet<Child>
_と同じように使用されますが、より便利(かつ高速)です。add(Child child)
メソッドを介して入力されます。子がリストにない場合は、そのまま追加します。子が既にリストにある場合、これは、このexact子が必ずしもリストにあるとは限らないが、等しいと見なされる一部の子であるため、パラメーターとして受け取った着信子は、子にマージされるそれはすでにリストにあります。これを、名前が付いた2つのフォルダーを一緒に配置するようなものだと考えてください。これは、同じサブフォルダーがあることを意味するものではありません。これを行うには、大きなコードブロックをリストに統合できないため、以下に示す_ICanBeMergedWith<T>
_を作成する必要がありました(エディター?!)NullObject
インスタンスを作成することでした。したがって、すべての変更メソッド呼び出しは、クラス内のthis
またはNullObject
に委任できます。 static
とジェネリック型はうまく機能しないことがわかりました。汎用の内部NullObject
シングルトンを作成できませんでした。上記のように、内部データ構造へのアクセスを妨げる単純なフラグ変数を使用しました。そのためにICanBecomeImmutable
を作成しました。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
}
}
_
Z
はNode
には認識できますが、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:
_
DataChunk
sがどのように見えるかわからないので、これが私の部分的な答えの終わりです。次に、データをツリーに変換してから、ツリーをメインツリーとマージする必要があります。私の知る限り。すべてのDataChunk
は、いくつかのV
Node
から開始する必要があります。そうしないと、このサブツリーがどの親Node
に属しているかが不可能なためです。しかし、そうでなかったとしても、ツリーのすべてのレベルでNodes
を追加してマージすることができます。ストリームが終了したら、root V
Node
でpetrify()
を呼び出して、データがさらに変更されないようにします。
また、さらにいくつかのテストを行います。
おそらくfinal
になるはずです。何かがfinal
であれば、より専門的に見えます。真剣にではない、これはすべての細部が完全に考え抜かれたわけではないことを示すためです。これは、タイプと指定された要件から、これが可能であることを証明するためです。それ以上、それ以下。
免責事項:これまでのところ、ジェネリックの経験はあまりありません。おそらくこれが史上最悪の恐ろしいナンセンスです。私はこれを不可能だと考えようとしていたところ、最後に試してみると、作成するために「super」と入力して上限が設定され、すべての赤い線が消えました。マジック!
また、ええ、WTF、programmers.SEに実装を投稿するのはどうですか?重要なのは、ホワイトボードで確実に何かを実行できることですが、実装なしでは結果はありません。そして、最終的に選択した言語によってサポートされるものは、実際の実装を行うために必要な労力に大きな違いをもたらします。 「コンパイル」を押すことはできず、コンパイラは常に正しいです。それが私が具体的な実装に行った理由です。