diff --git a/BTree/.classpath b/BTree/.classpath new file mode 100644 index 0000000..6361ddd --- /dev/null +++ b/BTree/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/BTree/.gitignore b/BTree/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/BTree/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/BTree/.project b/BTree/.project new file mode 100644 index 0000000..cc942bd --- /dev/null +++ b/BTree/.project @@ -0,0 +1,17 @@ + + + BTree + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/BTree/src/lab/B_Tree.java b/BTree/src/lab/B_Tree.java index 645c532..2113189 100644 --- a/BTree/src/lab/B_Tree.java +++ b/BTree/src/lab/B_Tree.java @@ -1,6 +1,11 @@ package lab; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; import java.util.ArrayList; +import java.util.stream.Collectors; import frame.*; @@ -17,75 +22,97 @@ import frame.*; public class B_Tree { + + private final int t; + private B_TreeNode root; /** * The constructor * * @param t minimum degree of the B-tree. - * t can not be changed once a new B Tree object is created. - * + * t can not be changed once a new B Tree object is created. + * */ public B_Tree(int t) { - /** - * Add your code here - */ - + this.t = t; + this.root = new B_TreeNode(t); } /** * This method takes as input the name of a file containing a sequence of - * entries that should be inserted to the B-Tree in the order they appear in - * the file. You cannot make any assumptions on the order of the entries nor - * is it allowed to change the order given in the file. You can assume that the - * file is located in the same directory as the executable program. The input - * file is similar to the input file for lab 1. The return value is the number of - * entries successfully inserted in the B-Tree. + * entries that should be inserted to the B-Tree in the order they appear in + * the file. You cannot make any assumptions on the order of the entries nor + * is it allowed to change the order given in the file. You can assume that the + * file is located in the same directory as the executable program. The input + * file is similar to the input file for lab 1. The return value is the number of + * entries successfully inserted in the B-Tree. * * @param filename name of the file containing the entries * @return returns the number of entries successfully inserted in the B-Tree. */ - - public int constructB_TreeFromFile (String filename) { - /** - * Add your code here - */ - return 0; + try (BufferedReader br = new BufferedReader(new FileReader(filename))) { + String line; + String[] parts; + int insertionCount = 0; + + // Read all lines + while ((line = br.readLine()) != null) { + // Create new entry from line parts separated by a semicolon + parts = line.split(";"); + Entry entry = new Entry(parts[0], parts[1], parts[2]); + + if(this.insert(entry)) + insertionCount += 1; + } + + return insertionCount; + } catch (FileNotFoundException e) { + System.out.println("File not found"); + return 0; + } catch (IOException e) { + System.out.println("IO Exception"); + return 0; + } } /** * This method inserts the entry insertEntry into the B-Tree. Note that you - * have to deal with overflows if you want to insert an entry into a leaf which - * already contains 2t - 1 entries. This method returns true if the insertion - * of the entry insertEntry is successful and false if the key of this entry - * already exists in the B-Tree. - * + * have to deal with overflows if you want to insert an entry into a leaf which + * already contains 2t - 1 entries. This method returns true if the insertion + * of the entry insertEntry is successful and false if the key of this entry + * already exists in the B-Tree. + * * @param insertEntry entry to insert into the B-Tree * @return returns true if the entry insertEntry is successfully inserted - * false if the entry already exists in the B-Tree + * false if the entry already exists in the B-Tree */ - - public boolean insert(Entry insertEntry) { - /** - * Add your code here - */ - return true; + + if(root.isFull()) { + B_TreeNode newRoot = new B_TreeNode(this.t); + newRoot.makeNonLeaf(); + newRoot.setPointer(0, root); + newRoot.splitChild(root); + this.root = newRoot; + } + + return root.insert(insertEntry); } /** * This method deletes the entry from the B-Tree structure, having deleteKey - * as key. In this method you have to distinguish between two cases: - * 1. The entry, having deleteKey as key, is located in an internal node. - * 2. The entry, having deleteKey as key, is located in a leaf. - * This method returns the entry, having deleteKey as key if the deletion is - * successful and null if the key deleteKey is not found in any entry of the - * B-Tree. - * + * as key. In this method you have to distinguish between two cases: + * 1. The entry, having deleteKey as key, is located in an internal node. + * 2. The entry, having deleteKey as key, is located in a leaf. + * This method returns the entry, having deleteKey as key if the deletion is + * successful and null if the key deleteKey is not found in any entry of the + * B-Tree. + * * @param deleteKey key of the entry to delete from the B-Tree * @return returns the deleted entry if the deletion ends successfully - * null if the entry is not found in the B-Tree + * null if the entry is not found in the B-Tree */ @@ -98,83 +125,65 @@ public class B_Tree { /** * This method searches in the B-Tree for the entry with key searchKey. It - * returns the entry, having searchKey as key if such an entry is found, null - * otherwise. - * + * returns the entry, having searchKey as key if such an entry is found, null + * otherwise. + * * @param searchKey key of the entry to find in the B-Tree * @return returns the entry having searchKey as key if such an entry exists - * null if the entry is not found in the B-Tree + * null if the entry is not found in the B-Tree */ - - public Entry find(String searchKey) { - /** - * Add your code here - */ - return new Entry(); + return root.find(searchKey); } /** * This method returns a ArrayList containing the output B-Tree. - * The output should be directly interpretable dot code. - * Each item in the ArrayList corresponds to one line of the output - * tree. The nodes of the output tree should only - * contain the keys of the entries and not the data. - * + * The output should be directly interpretable dot code. + * Each item in the ArrayList corresponds to one line of the output + * tree. The nodes of the output tree should only + * contain the keys of the entries and not the data. + * * @return returns the output B-Tree in directly interpretable dot code - */ - + */ public ArrayList getB_Tree() { - /** - * Add your code here - */ - return new ArrayList<>(); + ArrayList res = new ArrayList(); + res.add("digraph{"); + res.add("node[shape=record];"); + root.getDotCode(1, true, res); + res.add("}"); + return res; } /** * This method returns the height of the B-Tree - * If the B-Tree is empty this method should return 0. - * + * If the B-Tree is empty this method should return 0. + * * @return returns the height of the B-Tree */ - - public int getB_TreeHeight() { - /** - * Add your code here - */ - return 0; + return root.getHeight(); } /** * This method traverses the B-Tree in inorder and adds each entry to a - * ArrayList. The returned ArrayList contains the entries of the B-Tree - * in ascending order. - * + * ArrayList. The returned ArrayList contains the entries of the B-Tree + * in ascending order. + * * @return returns the entries stored in the B-Tree in ascending order */ - - public ArrayList getInorderTraversal() { - /** - * Add your code here - */ - return new ArrayList<>(); + ArrayList res = root.inorderTraversal(); + System.out.println(res.stream().map(Object::toString).collect(Collectors.joining(", "))); + return res; } /** * This method returns the number of entries in the B-Tree (not the number - * of nodes). - * - * + * of nodes). + * * @return returns the size of the B-Tree, i.e., the number of entries stored in the B-Tree */ - - public int getB_TreeSize() { - /** - * Add your code here - */ - return 0; + return root.getSize(); } } \ No newline at end of file diff --git a/BTree/src/lab/B_TreeNode.java b/BTree/src/lab/B_TreeNode.java index f335753..98f8db7 100644 --- a/BTree/src/lab/B_TreeNode.java +++ b/BTree/src/lab/B_TreeNode.java @@ -1,5 +1,9 @@ package lab; +import java.util.ArrayList; + +import frame.Entry; + /* * Implements a node of a B-Tree * @@ -9,25 +13,235 @@ package lab; */ - public class B_TreeNode { + + private ArrayList keys; + private B_TreeNode[] pointers; + private int currentLoad = 0; + private boolean isLeaf = true; + private final int t; /** * The constructor * * @param t minimum degree of the B-tree - * */ - public B_TreeNode(int t) { - /** - * Add your code here - */ + keys = new ArrayList(t*2-1); + pointers = new B_TreeNode[t*2]; + this.t = t; + } + + public Entry find(String searchKey) { + Entry dummy = new Entry(searchKey, "", ""); + + System.out.println("Searching for "+searchKey+" in "+this); + + int index = getIndexOf(dummy); + + if(index > -1) { + System.out.println("Is here!"); + return keys.get(index); + } + else if(isLeaf) { + System.out.println("Is not in leaf -> is not in tree"); + return null; + } + else { + System.out.println("Pointing to "+getPointerTowards(dummy)); + return getPointerTowards(dummy).find(searchKey); + } + } + + public int getIndexOf(Entry entry) { + for(int i = 0; i < this.currentLoad; i++) { + if(entry.compareTo(keys.get(i)) == 0) + return i; + } + return -1; + } + + public boolean insert(Entry entry) { + if(isLeaf) + return getIndexOf(entry) > 0 ? false : insertValue(entry); + + if(this.isFull()) + throw new RuntimeException("Insert expects non-full root node"); + + B_TreeNode pointer = getPointerTowards(entry); + System.out.println(pointer); + if(pointer.isFull()) { + this.splitChild(pointer); + pointer = getPointerTowards(entry); + } + return pointer.insert(entry); + } + + private boolean insertValue(Entry entry) { + if(!this.isLeaf) + throw new RuntimeException("Trying to insert value into non-leaf node"); + if(this.isFull()) + throw new RuntimeException("Trying to insert value into full leaf node"); + + int i = 0; + while(i < this.currentLoad && entry.compareTo(keys.get(i)) > 0) { + i += 1; + } + keys.add(i, entry); + this.currentLoad += 1; + + return true; + } + + private B_TreeNode getPointerTowards(Entry entry) { + if(this.isLeaf) + throw new RuntimeException("Trying to get pointer from leaf node"); + + int i = 0; + while(i < this.currentLoad && entry.compareTo(keys.get(i)) > 0) { + i += 1; + } + + if(pointers[i] == null) + throw new RuntimeException("Non-leaf node with capacity "+currentLoad+" does not have pointer "+i); + + return pointers[i]; + } + + public void splitChild(B_TreeNode child) { + + if(this.isFull()) + throw new RuntimeException("Trying to split child of full node"); + + System.out.println("SPLIT "+child); + + B_TreeNode newRight = new B_TreeNode(this.t); + for(int i = 0; i < t-1; i++) { + newRight.insert(child.getEntry(i+t)); + } + if(!child.isLeaf()) { + newRight.makeNonLeaf(); + for(int i = 0; i < t; i++) { + newRight.setPointer(i, child.getPointer(i+t)); + } + } + Entry median = child.getEntry(t-1); + child.setCurrentLoad(t-1); + int i = this.currentLoad; + while(pointers[i] != child) { + pointers[i+1] = pointers[i]; + i -= 1; + } + pointers[i+1] = newRight; + keys.add(i, median); + currentLoad += 1; + + System.out.println("New left node: "+child); + System.out.println("Median: "+median); + System.out.println("New right node: "+newRight); + } + + public ArrayList inorderTraversal(){ + ArrayList result = new ArrayList(); + + if(pointers[0] != null) { + result.addAll(pointers[0].inorderTraversal()); + } + + for(int i = 0; i < this.currentLoad; i++) { + result.add(keys.get(i)); + if(pointers[i+1] != null) { + result.addAll(pointers[i+1].inorderTraversal()); + } + } + + return result; + } + + public int getHeight() { + if(isLeaf) + return 0; + + int maxHeight = 0; + int cHeight; + for(int i = 0; i <= this.currentLoad; i++) { + if(pointers[i] != null) { + cHeight = pointers[i].getHeight(); + if(cHeight > maxHeight) + maxHeight = cHeight; + } + } + return maxHeight + 1; + } + + public int getSize() { + int res = this.currentLoad; + for(int i = 0; i <= this.currentLoad; i++) { + if(pointers[i] != null) + res += pointers[i].getSize(); + } + return res; + } + + public boolean isFull() { + return this.currentLoad >= this.t * 2 - 1; + } + + public boolean isLeaf() { + return this.isLeaf; + } + + public void makeNonLeaf() { + this.isLeaf = false; + } + + public Entry getEntry(int i) { + return keys.get(i); + } + + public B_TreeNode getPointer(int i) { + if(i > this.currentLoad) + throw new RuntimeException("Trying to get non-existent pointer"); + return this.pointers[i]; + } + + public void setPointer(int i, B_TreeNode pointer) { + if(i > this.currentLoad) + throw new RuntimeException("Trying to set non-existent pointer"); + if(isLeaf) + throw new RuntimeException("Trying to set pointer on leaf"); + this.pointers[i] = pointer; + } + + public void setCurrentLoad(int i) { + this.currentLoad = i; + } + + public String toString() { + String res = ""; + for(int i = 0; i < this.currentLoad; i++) { + res += keys.get(i).getKey() + ", "; + } + return "["+res+"]"; + } + + public int getDotCode(int index, boolean isRoot, ArrayList list){ + String name = (isRoot ? "root" : "node"+index); + String selfNode = name+"[label=\"*"; + for(int i = 0; i < this.currentLoad*2; i += 2) { + selfNode += "|"+keys.get(i/2).getKey()+"|*"; + } + selfNode += "\"];"; + list.add(selfNode); + + if(isLeaf) + return index; + + for(int i = 0; i <= this.currentLoad; i++) { + list.add(name+":f"+(i*2)+"->node"+(index+1)); + index = pointers[i].getDotCode(index+1, false, list); + } + + return index; } - - /** - * Add your code here - */ - - } \ No newline at end of file