この時点では少し太い気がします。私は頭に接尾辞ツリー構造を完全にラップすることを試みるのに何日も費やしました、しかし、私は数学的背景を持っていないので、それらが数学記号を過度に使い始めるので説明の多くは私を避けます。私が見つけた良い説明に最も近いのは接尾辞木を使った高速文字列検索ですが、彼はさまざまな点についてざっと目を通し、アルゴリズムのいくつかの側面は不明のままです。
ここでのスタックオーバーフローに関するこのアルゴリズムの段階的な説明は、私以外にも他の多くの人にとってかけがえのないものになるでしょう、と私は確信しています。
参考のために、ここでアルゴリズムに関するUkkonenの論文です: http://www.cs.helsinki.fi/u/ukkonen/SuffixT1withFigs.pdf
私の基本的な理解はこれまでのところ:
基本的なアルゴリズムはO(n2ほとんどの説明で指摘されているように、すべての接頭辞をステップスルーする必要があるので、次に各接頭辞の接尾辞をステップスルーする必要があります。 Ukkonenのアルゴリズムは、彼が使っている接尾辞ポインタ技術のおかげで明らかにユニークですが、私はthatが理解できないものだと思います。
私も理解できない
完成した C# ソースコードです。これは正しく機能するだけでなく、自動正規化をサポートし、出力のテキストグラフをより見栄え良くレンダリングします。ソースコードと出力例は次の場所にあります。
更新2017-11-04
何年にもわたって接尾辞ツリーの新しい使い方を見つけ、 JavaScript にアルゴリズムを実装しました。要旨は以下です。それはバグがないはずです。同じ場所からjsファイル(npm install chalk
)にダンプし、node.jsを実行してカラフルな出力を確認します。同じGistには、デバッグ用のコードを一切含まない、バージョンを簡略化したものがあります。
https://Gist.github.com/axefrog/c347bf0f5e0723cbd09b1aaed6ec6fc6
Jogojapanの回答に記載されているアプローチでサフィックスツリーを実装しようとしましたが、ルールに使用されている文言が原因で機能しない場合がありました。さらに、このアプローチを使用して絶対に正しい接尾辞ツリーを実装することはできなかったと述べました。以下に、ルールにいくつかの修正を加えたjogojapanの回答の「概要」を書きます。 important接尾辞リンクの作成を忘れた場合も説明します。
使用される追加の変数
内部ノードの概念を使用してみましょう-rootおよびleafsは内部ノードです。
観測1
挿入する必要のある最後のサフィックスがツリーに既に存在することが判明した場合、ツリー自体はまったく変更されません(active point
およびremainder
のみを更新します)。
観測2
ある時点でactive_length
が現在のEdge(Edge_length
)の長さ以上である場合、active point
がEdge_length
より厳密に大きくなるまでactive_length
を下に移動します。
それでは、ルールを再定義しましょう。
ルール1
active node= rootからの挿入後、active lengthが0より大きい場合、次のようになります。
- アクティブノードは変更されません
- アクティブな長さは減少します
- アクティブエッジは右にシフトされます(次のサフィックスの最初の文字に挿入する必要があります)
ルール2
新しい内部ノードまたはを作成する場合、内部ノードからインサーターを作成し、これは最初のSUCHinternal node現在のステップで、前のSUCHノードとTHISをサフィックスlinkを介してリンクします。
Rule 2
のこの定義はjogojapan 'とは異なります。ここでは、新しく作成された内部ノードだけでなく、挿入する内部ノードも考慮に入れています。
ルール
active nodeがrootノードではない)から挿入した後、接尾辞リンクをたどり、active nodeを指すノードに設定する必要があります。接尾辞がない場合リンク、アクティブノードをルートノードに設定します。いずれにしても、アクティブエッジおよびアクティブ長は変更されません。
Rule 3
のこの定義では、(スプリットノードだけでなく)リーフノードの挿入も考慮します。
そして最後に、観察3:
ツリーに追加するシンボルが既にEdgeにある場合、Observation 1
に従って、active point
とremainder
のみを更新し、ツリーは変更しないままにします。 BUT内部ノードがサフィックスリンクが必要とマークされている場合)は、サフィックスリンクを介してそのノードを現在のactive node
に接続する必要があります。
このような場合に接尾辞リンクを追加し、追加しない場合は、cdddcdcの接尾辞ツリーの例を見てみましょう。
DO N'Tがサフィックスリンクを介してノードを接続する場合:
DOがサフィックスリンクを介してノードを接続する場合:
有意な違いはないようです:2番目の場合、さらに2つのサフィックスリンクがあります。しかし、これらの接尾辞リンクは正しいであり、そのうちの1つ-青いノードから赤いノードまで-は、active pointを使用したアプローチでは非常にimportantです。問題はここに接尾辞リンクを置かない場合、後で新しい文字をツリーに追加するときに、Rule 3
のためにツリーにいくつかのノードを追加することを省略できます。 、その後、active_node
をルートに配置する必要があります。
ツリーに最後の文字を追加すると、青いノードから挿入する前に、赤いノードに既に存在するがありました(エッジは'c'とラベル付けされています)。青いノードから挿入があったので、接尾辞linkが必要です)とマークします。その後、active pointアプローチに基づいて、active node
が赤いノードに設定されました。 '' c 'がすでにEdgeにあるため、赤いノードから挿入しません。青いノードにはサフィックスリンクがないままにしておく必要があるということですか?いいえ、青いノードを接続する必要があります接尾辞リンクを介して赤いノードを持つノード。なぜ正しいのですか?active pointアプローチにより、正しい場所、つまり_の挿入を処理する必要がある次の場所に到達することが保証されるため短いサフィックス。
最後に、サフィックスツリーの実装を次に示します。
この「概要」とjogojapanの詳細な回答が、誰かが自分のサフィックスツリーを実装するのに役立つことを願っています。
@jogojapanによるよく説明されたチュートリアルをありがとう、Pythonでアルゴリズムを実装しました。
@jogojapanで言及されたいくつかの小さな問題は、予想以上に洗練されたであり、非常に慎重に扱う必要があることが判明しました。私の実装を取得するのに数日かかりました十分な堅牢性(私は思う)。問題と解決策は次のとおりです。
Remainder > 0
で終わるこの状況は、アルゴリズム全体の終わりだけでなく、展開手順中でも発生する可能性があることがわかります。その場合、残り、actnode、actedge、およびactlength変更なしを残し、現在の展開ステップを終了し、元の文字列の次の文字がオンかどうかに応じて折り畳みまたは展開を続けることで別のステップを開始できます現在のパスかどうか。
Leap Over Nodes:サフィックスリンクをたどると、アクティブポイントを更新し、そのactive_lengthコンポーネントが新しいactive_nodeでうまく機能しないことがわかります。 前に進む分割する適切な場所に移動するか、リーフを挿入する必要があります。このプロセスはそれほど単純ではありません移動中にactlengthとactedgeがずっと変化し続けるため、ルートノード、actedgeおよびactlengthは間違っているである可能性があります。その情報を保持するには、追加の変数が必要です。
他の2つの問題は、何らかの形で@managonovによって指摘されています。
分割は縮退する可能性がありますEdgeを分割しようとすると、ノード上で分割操作が正しく行われることがあります。その場合、そのノードに新しいリーフを追加するだけで、標準のエッジ分割操作として使用できます。つまり、サフィックスリンクがある場合は、それに応じて維持する必要があります。
隠しサフィックスリンクproblem 1およびproblem 2によって発生する別の特殊なケースがあります。時々、いくつかのノードを分割するために適切なポイントにホップする必要があります。残りの文字列とパスラベルを比較して移動する場合、surpass正しいポイントになる場合があります。その場合、接尾辞のリンクがあれば、意図せずに無視されます。これは、前進するときに正しい点を覚えておくによって回避できます。分割ノードが既に存在する場合、または展開手順中にproblem 1が発生する場合、接尾辞リンクを維持する必要があります。
最後に、Pythonの実装は次のとおりです。
ヒント:上記のコードにはナイーブツリー印刷関数が含まれていますが、これはデバッグ中に非常に重要です。それは私に多くの時間を節約し、特別なケースを見つけるのに便利です。
私の直感は次のとおりです。
メインループのk回の繰り返しの後、最初のk文字で始まる完全な文字列のすべての接尾辞を含む接尾辞ツリーを構築しました。
開始時には、これはサフィックスツリーが文字列全体を表す単一のルートノードを含むことを意味します(これが0から始まる唯一のサフィックスです)。
Len(文字列)を繰り返した後、すべての接尾辞を含む接尾辞ツリーができます。
ループの間、キーはアクティブポイントです。私の推測では、これは文字列の最初のk文字の固有の接尾辞に対応する接尾辞ツリーの最も深い位置を表しています。 (私は正しいとは、接尾辞が文字列全体にならないことを意味します。)
たとえば、 'abcabc'という文字を見たとします。アクティブポイントは、接尾辞 'abc'に対応するツリー内のポイントを表します。
アクティブポイントは(Origin、first、last)で表されます。つまり、あなたは現在、ノードOriginから始めてstring [first:last]の文字を入力することによって、あなたがたどり着くツリーの中にいるということです。
新しい文字を追加すると、アクティブポイントがまだ既存のツリー内にあるかどうかがわかります。それがそれならそれであなたは完了です。それ以外の場合は、アクティブポイントのサフィックスツリーに新しいノードを追加し、次に短い一致にフォールバックして、もう一度確認する必要があります。
注1:サフィックスポインタは、各ノードの次に短い一致へのリンクを示します。
注意2:新しいノードを追加してフォールバックすると、新しいノードに新しいサフィックスポインタが追加されます。この接尾辞ポインタの宛先は、短縮されたアクティブポイントのノードになります。このノードは既に存在するか、このフォールバックループの次の繰り返しで作成されます。
注3:正規化部分は単にアクティブポイントをチェックする時間を節約します。たとえば、常にOrigin = 0を使用し、最初と最後を変更したとします。アクティブポイントを確認するには、すべての中間ノードに沿って毎回サフィックスツリーをたどる必要があります。最後のノードからの距離だけを記録することによって、このパスをたどった結果をキャッシュすることは理にかなっています。
境界変数を「修正」することの意味のコード例を教えてください。
健康警告:私もこのアルゴリズムを理解するのが特に難しいと思ったので、この直感はすべての重要な詳細において不正確である可能性が高いことに注意してください。
@ jogojapanあなたは素晴らしい説明と視覚化をもたらしました。しかし@makagonovが述べたように、接尾辞リンクの設定に関するいくつかの規則が欠けています。 http://brenden.github.io/ukkonen-animation/ / Word 'aabaaabb'から順に進んでいくと、すてきに見えます。手順10から手順11に進むと、ノード5からノード2へのサフィックスリンクはありませんが、アクティブポイントはそこに突然移動します。
@makagonov私はJavaの世界に住んでいるので、STの構築ワークフローを把握するためにあなたの実装に従うことを試みましたが、それは私にとって困難でした:
それで、私はそのようなJavaでの実装に終わりました。それはすべてのステップをより明確に反映し、他のJavaユーザーの学習時間を短縮することを願っています。
import Java.util.Arrays;
import Java.util.HashMap;
import Java.util.Map;
public class ST {
public class Node {
private final int id;
private final Map<Character, Edge> edges;
private Node slink;
public Node(final int id) {
this.id = id;
this.edges = new HashMap<>();
}
public void setSlink(final Node slink) {
this.slink = slink;
}
public Map<Character, Edge> getEdges() {
return this.edges;
}
public Node getSlink() {
return this.slink;
}
public String toString(final String Word) {
return new StringBuilder()
.append("{")
.append("\"id\"")
.append(":")
.append(this.id)
.append(",")
.append("\"slink\"")
.append(":")
.append(this.slink != null ? this.slink.id : null)
.append(",")
.append("\"edges\"")
.append(":")
.append(edgesToString(Word))
.append("}")
.toString();
}
private StringBuilder edgesToString(final String Word) {
final StringBuilder edgesStringBuilder = new StringBuilder();
edgesStringBuilder.append("{");
for(final Map.Entry<Character, Edge> entry : this.edges.entrySet()) {
edgesStringBuilder.append("\"")
.append(entry.getKey())
.append("\"")
.append(":")
.append(entry.getValue().toString(Word))
.append(",");
}
if(!this.edges.isEmpty()) {
edgesStringBuilder.deleteCharAt(edgesStringBuilder.length() - 1);
}
edgesStringBuilder.append("}");
return edgesStringBuilder;
}
public boolean contains(final String Word, final String suffix) {
return !suffix.isEmpty()
&& this.edges.containsKey(suffix.charAt(0))
&& this.edges.get(suffix.charAt(0)).contains(Word, suffix);
}
}
public class Edge {
private final int from;
private final int to;
private final Node next;
public Edge(final int from, final int to, final Node next) {
this.from = from;
this.to = to;
this.next = next;
}
public int getFrom() {
return this.from;
}
public int getTo() {
return this.to;
}
public Node getNext() {
return this.next;
}
public int getLength() {
return this.to - this.from;
}
public String toString(final String Word) {
return new StringBuilder()
.append("{")
.append("\"content\"")
.append(":")
.append("\"")
.append(Word.substring(this.from, this.to))
.append("\"")
.append(",")
.append("\"next\"")
.append(":")
.append(this.next != null ? this.next.toString(Word) : null)
.append("}")
.toString();
}
public boolean contains(final String Word, final String suffix) {
if(this.next == null) {
return Word.substring(this.from, this.to).equals(suffix);
}
return suffix.startsWith(Word.substring(this.from,
this.to)) && this.next.contains(Word, suffix.substring(this.to - this.from));
}
}
public class ActivePoint {
private final Node activeNode;
private final Character activeEdgeFirstCharacter;
private final int activeLength;
public ActivePoint(final Node activeNode,
final Character activeEdgeFirstCharacter,
final int activeLength) {
this.activeNode = activeNode;
this.activeEdgeFirstCharacter = activeEdgeFirstCharacter;
this.activeLength = activeLength;
}
private Edge getActiveEdge() {
return this.activeNode.getEdges().get(this.activeEdgeFirstCharacter);
}
public boolean pointsToActiveNode() {
return this.activeLength == 0;
}
public boolean activeNodeIs(final Node node) {
return this.activeNode == node;
}
public boolean activeNodeHasEdgeStartingWith(final char character) {
return this.activeNode.getEdges().containsKey(character);
}
public boolean activeNodeHasSlink() {
return this.activeNode.getSlink() != null;
}
public boolean pointsToOnActiveEdge(final String Word, final char character) {
return Word.charAt(this.getActiveEdge().getFrom() + this.activeLength) == character;
}
public boolean pointsToTheEndOfActiveEdge() {
return this.getActiveEdge().getLength() == this.activeLength;
}
public boolean pointsAfterTheEndOfActiveEdge() {
return this.getActiveEdge().getLength() < this.activeLength;
}
public ActivePoint moveToEdgeStartingWithAndByOne(final char character) {
return new ActivePoint(this.activeNode, character, 1);
}
public ActivePoint moveToNextNodeOfActiveEdge() {
return new ActivePoint(this.getActiveEdge().getNext(), null, 0);
}
public ActivePoint moveToSlink() {
return new ActivePoint(this.activeNode.getSlink(),
this.activeEdgeFirstCharacter,
this.activeLength);
}
public ActivePoint moveTo(final Node node) {
return new ActivePoint(node, this.activeEdgeFirstCharacter, this.activeLength);
}
public ActivePoint moveByOneCharacter() {
return new ActivePoint(this.activeNode,
this.activeEdgeFirstCharacter,
this.activeLength + 1);
}
public ActivePoint moveToEdgeStartingWithAndByActiveLengthMinusOne(final Node node,
final char character) {
return new ActivePoint(node, character, this.activeLength - 1);
}
public ActivePoint moveToNextNodeOfActiveEdge(final String Word, final int index) {
return new ActivePoint(this.getActiveEdge().getNext(),
Word.charAt(index - this.activeLength + this.getActiveEdge().getLength()),
this.activeLength - this.getActiveEdge().getLength());
}
public void addEdgeToActiveNode(final char character, final Edge edge) {
this.activeNode.getEdges().put(character, Edge);
}
public void splitActiveEdge(final String Word,
final Node nodeToAdd,
final int index,
final char character) {
final Edge activeEdgeToSplit = this.getActiveEdge();
final Edge splittedEdge = new Edge(activeEdgeToSplit.getFrom(),
activeEdgeToSplit.getFrom() + this.activeLength,
nodeToAdd);
nodeToAdd.getEdges().put(Word.charAt(activeEdgeToSplit.getFrom() + this.activeLength),
new Edge(activeEdgeToSplit.getFrom() + this.activeLength,
activeEdgeToSplit.getTo(),
activeEdgeToSplit.getNext()));
nodeToAdd.getEdges().put(character, new Edge(index, Word.length(), null));
this.activeNode.getEdges().put(this.activeEdgeFirstCharacter, splittedEdge);
}
public Node setSlinkTo(final Node previouslyAddedNodeOrAddedEdgeNode,
final Node node) {
if(previouslyAddedNodeOrAddedEdgeNode != null) {
previouslyAddedNodeOrAddedEdgeNode.setSlink(node);
}
return node;
}
public Node setSlinkToActiveNode(final Node previouslyAddedNodeOrAddedEdgeNode) {
return setSlinkTo(previouslyAddedNodeOrAddedEdgeNode, this.activeNode);
}
}
private static int idGenerator;
private final String Word;
private final Node root;
private ActivePoint activePoint;
private int remainder;
public ST(final String Word) {
this.Word = Word;
this.root = new Node(idGenerator++);
this.activePoint = new ActivePoint(this.root, null, 0);
this.remainder = 0;
build();
}
private void build() {
for(int i = 0; i < this.Word.length(); i++) {
add(i, this.Word.charAt(i));
}
}
private void add(final int index, final char character) {
this.remainder++;
boolean characterFoundInTheTree = false;
Node previouslyAddedNodeOrAddedEdgeNode = null;
while(!characterFoundInTheTree && this.remainder > 0) {
if(this.activePoint.pointsToActiveNode()) {
if(this.activePoint.activeNodeHasEdgeStartingWith(character)) {
activeNodeHasEdgeStartingWithCharacter(character, previouslyAddedNodeOrAddedEdgeNode);
characterFoundInTheTree = true;
}
else {
if(this.activePoint.activeNodeIs(this.root)) {
rootNodeHasNotEdgeStartingWithCharacter(index, character);
}
else {
previouslyAddedNodeOrAddedEdgeNode = internalNodeHasNotEdgeStartingWithCharacter(index,
character, previouslyAddedNodeOrAddedEdgeNode);
}
}
}
else {
if(this.activePoint.pointsToOnActiveEdge(this.Word, character)) {
activeEdgeHasCharacter();
characterFoundInTheTree = true;
}
else {
if(this.activePoint.activeNodeIs(this.root)) {
previouslyAddedNodeOrAddedEdgeNode = edgeFromRootNodeHasNotCharacter(index,
character,
previouslyAddedNodeOrAddedEdgeNode);
}
else {
previouslyAddedNodeOrAddedEdgeNode = edgeFromInternalNodeHasNotCharacter(index,
character,
previouslyAddedNodeOrAddedEdgeNode);
}
}
}
}
}
private void activeNodeHasEdgeStartingWithCharacter(final char character,
final Node previouslyAddedNodeOrAddedEdgeNode) {
this.activePoint.setSlinkToActiveNode(previouslyAddedNodeOrAddedEdgeNode);
this.activePoint = this.activePoint.moveToEdgeStartingWithAndByOne(character);
if(this.activePoint.pointsToTheEndOfActiveEdge()) {
this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge();
}
}
private void rootNodeHasNotEdgeStartingWithCharacter(final int index, final char character) {
this.activePoint.addEdgeToActiveNode(character, new Edge(index, this.Word.length(), null));
this.activePoint = this.activePoint.moveTo(this.root);
this.remainder--;
assert this.remainder == 0;
}
private Node internalNodeHasNotEdgeStartingWithCharacter(final int index,
final char character,
Node previouslyAddedNodeOrAddedEdgeNode) {
this.activePoint.addEdgeToActiveNode(character, new Edge(index, this.Word.length(), null));
previouslyAddedNodeOrAddedEdgeNode = this.activePoint.setSlinkToActiveNode(previouslyAddedNodeOrAddedEdgeNode);
if(this.activePoint.activeNodeHasSlink()) {
this.activePoint = this.activePoint.moveToSlink();
}
else {
this.activePoint = this.activePoint.moveTo(this.root);
}
this.remainder--;
return previouslyAddedNodeOrAddedEdgeNode;
}
private void activeEdgeHasCharacter() {
this.activePoint = this.activePoint.moveByOneCharacter();
if(this.activePoint.pointsToTheEndOfActiveEdge()) {
this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge();
}
}
private Node edgeFromRootNodeHasNotCharacter(final int index,
final char character,
Node previouslyAddedNodeOrAddedEdgeNode) {
final Node newNode = new Node(idGenerator++);
this.activePoint.splitActiveEdge(this.Word, newNode, index, character);
previouslyAddedNodeOrAddedEdgeNode = this.activePoint.setSlinkTo(previouslyAddedNodeOrAddedEdgeNode, newNode);
this.activePoint = this.activePoint.moveToEdgeStartingWithAndByActiveLengthMinusOne(this.root,
this.Word.charAt(index - this.remainder + 2));
this.activePoint = walkDown(index);
this.remainder--;
return previouslyAddedNodeOrAddedEdgeNode;
}
private Node edgeFromInternalNodeHasNotCharacter(final int index,
final char character,
Node previouslyAddedNodeOrAddedEdgeNode) {
final Node newNode = new Node(idGenerator++);
this.activePoint.splitActiveEdge(this.Word, newNode, index, character);
previouslyAddedNodeOrAddedEdgeNode = this.activePoint.setSlinkTo(previouslyAddedNodeOrAddedEdgeNode, newNode);
if(this.activePoint.activeNodeHasSlink()) {
this.activePoint = this.activePoint.moveToSlink();
}
else {
this.activePoint = this.activePoint.moveTo(this.root);
}
this.activePoint = walkDown(index);
this.remainder--;
return previouslyAddedNodeOrAddedEdgeNode;
}
private ActivePoint walkDown(final int index) {
while(!this.activePoint.pointsToActiveNode()
&& (this.activePoint.pointsToTheEndOfActiveEdge() || this.activePoint.pointsAfterTheEndOfActiveEdge())) {
if(this.activePoint.pointsAfterTheEndOfActiveEdge()) {
this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge(this.Word, index);
}
else {
this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge();
}
}
return this.activePoint;
}
public String toString(final String Word) {
return this.root.toString(Word);
}
public boolean contains(final String suffix) {
return this.root.contains(this.Word, suffix);
}
public static void main(final String[] args) {
final String[] words = {
"abcabcabc$",
"abc$",
"abcabxabcd$",
"abcabxabda$",
"abcabxad$",
"aabaaabb$",
"aababcabcd$",
"ababcabcd$",
"abccba$",
"mississipi$",
"abacabadabacabae$",
"abcabcd$",
"00132220$"
};
Arrays.stream(words).forEach(Word -> {
System.out.println("Building suffix tree for Word: " + Word);
final ST suffixTree = new ST(Word);
System.out.println("Suffix tree: " + suffixTree.toString(Word));
for(int i = 0; i < Word.length() - 1; i++) {
assert suffixTree.contains(Word.substring(i)) : Word.substring(i);
}
});
}
}
こんにちは私はRubyで上記で説明された実装を実装しようとしました、それをチェックしてください。それはうまくいくようです。
実装の唯一の違いは、シンボルを使用する代わりにEdgeオブジェクトを使用しようとしたことです。
https://Gist.github.com/suchitpuri/9304856 にもあります。
require 'pry'
class Edge
attr_accessor :data , :edges , :suffix_link
def initialize data
@data = data
@edges = []
@suffix_link = nil
end
def find_Edge element
self.edges.each do |Edge|
return Edge if Edge.data.start_with? element
end
return nil
end
end
class SuffixTrees
attr_accessor :root , :active_point , :remainder , :pending_prefixes , :last_split_Edge , :remainder
def initialize
@root = Edge.new nil
@active_point = { active_node: @root , active_Edge: nil , active_length: 0}
@remainder = 0
@pending_prefixes = []
@last_split_Edge = nil
@remainder = 1
end
def build string
string.split("").each_with_index do |element , index|
add_to_edges @root , element
update_pending_prefix element
add_pending_elements_to_tree element
active_length = @active_point[:active_length]
# if(@active_point[:active_Edge] && @active_point[:active_Edge].data && @active_point[:active_Edge].data[0..active_length-1] == @active_point[:active_Edge].data[active_length..@active_point[:active_Edge].data.length-1])
# @active_point[:active_Edge].data = @active_point[:active_Edge].data[0..active_length-1]
# @active_point[:active_Edge].edges << Edge.new(@active_point[:active_Edge].data)
# end
if(@active_point[:active_Edge] && @active_point[:active_Edge].data && @active_point[:active_Edge].data.length == @active_point[:active_length] )
@active_point[:active_node] = @active_point[:active_Edge]
@active_point[:active_Edge] = @active_point[:active_node].find_Edge(element[0])
@active_point[:active_length] = 0
end
end
end
def add_pending_elements_to_tree element
to_be_deleted = []
update_active_length = false
# binding.pry
if( @active_point[:active_node].find_Edge(element[0]) != nil)
@active_point[:active_length] = @active_point[:active_length] + 1
@active_point[:active_Edge] = @active_point[:active_node].find_Edge(element[0]) if @active_point[:active_Edge] == nil
@remainder = @remainder + 1
return
end
@pending_prefixes.each_with_index do |pending_prefix , index|
# binding.pry
if @active_point[:active_Edge] == nil and @active_point[:active_node].find_Edge(element[0]) == nil
@active_point[:active_node].edges << Edge.new(element)
else
@active_point[:active_Edge] = node.find_Edge(element[0]) if @active_point[:active_Edge] == nil
data = @active_point[:active_Edge].data
data = data.split("")
location = @active_point[:active_length]
# binding.pry
if(data[0..location].join == pending_prefix or @active_point[:active_node].find_Edge(element) != nil )
else #tree split
split_Edge data , index , element
end
end
end
end
def update_pending_prefix element
if @active_point[:active_Edge] == nil
@pending_prefixes = [element]
return
end
@pending_prefixes = []
length = @active_point[:active_Edge].data.length
data = @active_point[:active_Edge].data
@remainder.times do |ctr|
@pending_prefixes << data[-(ctr+1)..data.length-1]
end
@pending_prefixes.reverse!
end
def split_Edge data , index , element
location = @active_point[:active_length]
old_edges = []
internal_node = (@active_point[:active_Edge].edges != nil)
if (internal_node)
old_edges = @active_point[:active_Edge].edges
@active_point[:active_Edge].edges = []
end
@active_point[:active_Edge].data = data[0..location-1].join
@active_point[:active_Edge].edges << Edge.new(data[location..data.size].join)
if internal_node
@active_point[:active_Edge].edges << Edge.new(element)
else
@active_point[:active_Edge].edges << Edge.new(data.last)
end
if internal_node
@active_point[:active_Edge].edges[0].edges = old_edges
end
#setup the suffix link
if @last_split_Edge != nil and @last_split_Edge.data.end_with?@active_point[:active_Edge].data
@last_split_Edge.suffix_link = @active_point[:active_Edge]
end
@last_split_Edge = @active_point[:active_Edge]
update_active_point index
end
def update_active_point index
if(@active_point[:active_node] == @root)
@active_point[:active_length] = @active_point[:active_length] - 1
@remainder = @remainder - 1
@active_point[:active_Edge] = @active_point[:active_node].find_Edge(@pending_prefixes.first[index+1])
else
if @active_point[:active_node].suffix_link != nil
@active_point[:active_node] = @active_point[:active_node].suffix_link
else
@active_point[:active_node] = @root
end
@active_point[:active_Edge] = @active_point[:active_node].find_Edge(@active_point[:active_Edge].data[0])
@remainder = @remainder - 1
end
end
def add_to_edges root , element
return if root == nil
root.data = root.data + element if(root.data and root.edges.size == 0)
root.edges.each do |Edge|
add_to_edges Edge , element
end
end
end
suffix_tree = SuffixTrees.new
suffix_tree.build("abcabxabcd")
binding.pry