diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/hw3_Trie/.idea/compiler.xml b/hw3_Trie/.idea/compiler.xml new file mode 100644 index 0000000..4d5c80c --- /dev/null +++ b/hw3_Trie/.idea/compiler.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/hw3_Trie/.idea/gradle.xml b/hw3_Trie/.idea/gradle.xml new file mode 100644 index 0000000..a5c3ae1 --- /dev/null +++ b/hw3_Trie/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/hw3_Trie/.idea/misc.xml b/hw3_Trie/.idea/misc.xml new file mode 100644 index 0000000..e208459 --- /dev/null +++ b/hw3_Trie/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/hw3_Trie/.idea/modules.xml b/hw3_Trie/.idea/modules.xml new file mode 100644 index 0000000..6e5f594 --- /dev/null +++ b/hw3_Trie/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/hw3_Trie/.idea/modules/bor.iml b/hw3_Trie/.idea/modules/bor.iml new file mode 100644 index 0000000..51b3933 --- /dev/null +++ b/hw3_Trie/.idea/modules/bor.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/hw3_Trie/.idea/modules/bor_main.iml b/hw3_Trie/.idea/modules/bor_main.iml new file mode 100644 index 0000000..cb10f7b --- /dev/null +++ b/hw3_Trie/.idea/modules/bor_main.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/hw3_Trie/.idea/modules/bor_test.iml b/hw3_Trie/.idea/modules/bor_test.iml new file mode 100644 index 0000000..f59354e --- /dev/null +++ b/hw3_Trie/.idea/modules/bor_test.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hw3_Trie/build.gradle b/hw3_Trie/build.gradle new file mode 100644 index 0000000..7eb3763 --- /dev/null +++ b/hw3_Trie/build.gradle @@ -0,0 +1,14 @@ +group 'ru.spbau' +version '1.0-SNAPSHOT' + +apply plugin: 'java' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' +} diff --git a/hw3_Trie/gradle/wrapper/gradle-wrapper.jar b/hw3_Trie/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5d7d246 Binary files /dev/null and b/hw3_Trie/gradle/wrapper/gradle-wrapper.jar differ diff --git a/hw3_Trie/gradle/wrapper/gradle-wrapper.properties b/hw3_Trie/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..4d36e15 --- /dev/null +++ b/hw3_Trie/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Oct 02 23:10:25 MSK 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-rc-2-bin.zip diff --git a/hw3_Trie/settings.gradle b/hw3_Trie/settings.gradle new file mode 100644 index 0000000..7a2e130 --- /dev/null +++ b/hw3_Trie/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'bor' + diff --git a/hw3_Trie/src/main/java/ru/spbau/solikov/src/Trie.java b/hw3_Trie/src/main/java/ru/spbau/solikov/src/Trie.java new file mode 100644 index 0000000..3bf4cf9 --- /dev/null +++ b/hw3_Trie/src/main/java/ru/spbau/solikov/src/Trie.java @@ -0,0 +1,245 @@ +package ru.spbau.solikov.src; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +/** + * Implementation of data structure "trie" that can store strings over some finite alphabet + */ +public class Trie implements Serializable { + + private static final int ALPHABET_SIZE = 26; + + /** + * Function to obtain the letter in String from integer + * + * @param i number of letter + * @return string + */ + private static String getCharForNumber(int i) { + return i > 0 && i < 27 ? String.valueOf((char) (i + 64)) : null; + } + + private static final Map alphabet; + + static { + alphabet = new HashMap(); + for (int i = 0; i < ALPHABET_SIZE; i++) { + alphabet.put(getCharForNumber(i + 1), i); + } + } + + /** + * Implementation of trie's node with static array + */ + private class TrieNode implements Serializable { + private boolean isTerminal = false; + private int size = 0; + private TrieNode nodes[] = new TrieNode[ALPHABET_SIZE]; + } + + private TrieNode root; + + public Trie() { + root = new TrieNode(); + } + + /** + * Function that adds string from letters of alphabet to the trie. + * Returns true if the trie didn't have that string before. + * False otherwise. + * Supports size. + * + * @param element string to be added + * @return true if element already in trie, false otherwise + */ + public boolean add(String element) { + + TrieNode current = root; + int index = -1; + int indexLastElement = 0; + + for (int i = 0; i < element.length(); i++) { + indexLastElement = i; + char c = element.charAt(i); + index = alphabet.get(Character.toString(c)); + if (current.nodes[index] != null) { + current = current.nodes[index]; + } else { + current.nodes[index] = new TrieNode(); + current = current.nodes[index]; + } + } + + if (current.isTerminal) { + return false; + } + + current.isTerminal = true; + + + current.size++; + current = root; + for (int i = 0; i <= indexLastElement; i++) { + char c = element.charAt(i); + int indexCurrent = alphabet.get(Character.toString(c)); + current.size++; + current = current.nodes[indexCurrent]; + } + return true; + + } + + /** + * Checks if element is in trie. + * + * @param element to be checked + * @return true if element is in trie, false otherwise + */ + public boolean contains(String element) { + + TrieNode current = root; + + for (int i = 0; i < element.length(); i++) { + char c = element.charAt(i); + int index = alphabet.get(Character.toString(c)); + if (current.nodes[index] != null) { + current = current.nodes[index]; + } else { + return false; + } + } + + return current.isTerminal; + } + + /** + * Removes element from trie. If that element is not prefix of any other element in trie, + * then deletes all nodes till last terminal vertex from up to down. + * + * @param element to be removed + * @return true if element was in trie, false otherwise + */ + public boolean remove(String element) { + + TrieNode current = root; + TrieNode lastElement = root; + int indexLastElement = 0; + int index = -1; + + for (int i = 0; i < element.length(); i++) { + char c = element.charAt(i); + index = alphabet.get(Character.toString(c)); + + if (current.isTerminal) { + lastElement = current; + indexLastElement = i; + } + + if (current.nodes[index] != null) { + current = current.nodes[index]; + } else { + return false; + } + } + + if (current.isTerminal) { + for (int i = 0; i < ALPHABET_SIZE; i++) { + if (current.nodes[i] != null) { + current.isTerminal = false; + current = root; + for (int j = 0; j <= indexLastElement; j++) { + char c = element.charAt(j); + int indexCurrent = alphabet.get(Character.toString(c)); + current.size--; + current = current.nodes[indexCurrent]; + } + return true; + } + } + + removePath(lastElement, element.substring(indexLastElement)); + + current = root; + for (int i = 0; i <= indexLastElement; i++) { + char c = element.charAt(i); + int indexCurrent = alphabet.get(Character.toString(c)); + current.size--; + current = current.nodes[indexCurrent]; + } + return true; + } + return false; + } + + private void removePath(TrieNode node, String element) { + + if (element.length() == 0) { + return; + } + + removePath(node.nodes[alphabet.get(Character.toString(element.charAt(0)))], element.substring(1)); + node.nodes[alphabet.get(Character.toString(element.charAt(0)))] = null; + + } + + /** + * Returns number of strings stored in trie. Is equal to number of terminal vertices. + * + * @return size + */ + public int size() { + return (root != null) ? root.size : 0; + } + + /** + * Returns number of strings started with prefix stored in trie. + * Is equal to number of terminal vertices in subtrie of last prefix letter's node. + * + * @param prefix to be checked for size + * @return size + */ + public int howManyStartsWithPrefix(String prefix) { + + TrieNode current = root; + + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + int index = alphabet.get(Character.toString(c)); + if (current.nodes[index] != null) { + current = current.nodes[index]; + } else { + return 0; + } + } + + return current.size; + } + + /** + * Takes output stream and serializes class Trie to that stream with standard Java method + * + * @param out stream in what it will write Trie + * @throws IOException exceptions that could appear while writing + */ + public void serialize(OutputStream out) throws IOException { + ObjectOutputStream oos = new ObjectOutputStream(out); + oos.writeObject(this); + oos.flush(); + oos.close(); + } + + /** + * Takes input stream and reads class from that stream with standard Java method + * + * @param in stream from what it will read information about Trie + * @throws IOException exceptions that could appear while reading + * @throws ClassNotFoundException exception if there's no such file + */ + public void deserialize(InputStream in) throws IOException, ClassNotFoundException { + ObjectInputStream oin = new ObjectInputStream(in); + Trie other = (Trie) oin.readObject(); + this.root = other.root; + } +} diff --git a/hw3_Trie/src/test/java/ru/spbau/solikov/test/Main.java b/hw3_Trie/src/test/java/ru/spbau/solikov/test/Main.java new file mode 100644 index 0000000..cf00f5e --- /dev/null +++ b/hw3_Trie/src/test/java/ru/spbau/solikov/test/Main.java @@ -0,0 +1,238 @@ +package ru.spbau.solikov.test; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import ru.spbau.solikov.src.Trie; + +import static org.junit.Assert.*; + +import java.io.*; +import java.nio.file.*; + +/** + * Class for testing methods from "Trie" class. Creates a file for testing serialize and deserialize and deletes it after tests. + */ +public class Main { + + private Trie trie; + + @Before + public void setUp() { + trie = new Trie(); + } + + @Test + public void testIfAddOneElement() { + assertEquals(true, trie.add("A")); + } + + @Test + public void testIfAddOneSizeReturnsOne() { + trie.add("A"); + assertEquals(1, trie.size()); + } + + @Test + public void testContainsAfterAddOne() { + trie.add("ASDFGTYMVNCXMKENRBVEK"); + assertEquals(true, trie.contains("ASDFGTYMVNCXMKENRBVEK")); + } + + @Test + public void testAddTheSameOneElement() { + trie.add("A"); + assertEquals(false, trie.add("A")); + } + + @Test + public void testSizeAfterAddingTheSameOneElement() { + trie.add("A"); + trie.add("A"); + assertEquals(1, trie.size()); + } + + @Test + public void testAddLotsElements() { + trie.add("A"); + assertEquals(true, trie.add("B")); + } + + @Test + public void testSizeAfterAddLotsElements() { + trie.add("A"); + trie.add("B"); + trie.add("SPBAU"); + assertEquals(3, trie.size()); + } + + @Test + public void testContainsAfterAddLotsElements() { + trie.add("A"); + trie.add("B"); + trie.add("SPBAU"); + assertEquals(true, trie.contains("SPBAU")); + } + + @Test + public void testDoesNotContainAfterAddLotsElements() { + trie.add("A"); + trie.add("B"); + trie.add("SPBAU"); + assertEquals(false, trie.contains("SPBSU")); + } + + @Test + public void testRemoveFromOneElementTrie() { + trie.add("A"); + assertEquals(true, trie.remove("A")); + } + + @Test + public void testFalseRemoveFromOneElementTrie() { + trie.add("A"); + assertEquals(false, trie.remove("ABBA")); + } + + @Test + public void testRemoveFromLotsElementTrie() { + for (int i = 1; i < 11; i++) { + trie.add(String.valueOf((char) (i + 64))); + } + for (int i = 1; i < 11; i++) { + assertEquals(true, (trie.remove(String.valueOf((char) (i + 64))))); + } + } + + @Test + public void testSizeAfterLotsOfAdds() { + for (int i = 1; i < 11; i++) { + trie.add(String.valueOf((char) (i + 64))); + } + assertEquals(10, trie.size()); + } + + @Test + public void testSizeAfterRemovingFromLotsElementTrie() { + for (int i = 1; i < 11; i++) { + trie.add(String.valueOf((char) (i + 64))); + } + trie.remove("B"); + assertEquals(9, trie.size()); + } + + @Test + public void testSizeOfZeroTrie() { + assertEquals(0, trie.size()); + } + + @Test + public void testHowManyStartsWithPrefixOfZeroTable() { + assertEquals(0, trie.howManyStartsWithPrefix("")); + } + + @Test + public void testHowManyStartsWithPrefixOfOneElementTable() { + trie.add("A"); + assertEquals(1, trie.howManyStartsWithPrefix("A")); + } + + @Test + public void testFalseHowManyStartsWithPrefixOfOneElementTable() { + trie.add("A"); + assertEquals(0, trie.howManyStartsWithPrefix("B")); + } + + @Test + public void testHowManyStartsWithPrefixOfLotsElementTable() { + for (int i = 1; i < 11; i++) { + trie.add(String.valueOf((char) (i + 64))); + } + assertEquals(1, trie.howManyStartsWithPrefix("A")); + } + + @Test + public void testManyStartsWithPrefix() { + trie.add("A"); + trie.add("AB"); + trie.add("BA"); + trie.add("ABC"); + trie.add("ABCD"); + trie.add("ABCDE"); + assertEquals(5, trie.howManyStartsWithPrefix("A")); + } + + @Test + public void testSerializeEmpty() throws IOException { + FileOutputStream fos = new FileOutputStream("test.trie"); + trie.serialize(fos); + } + + @Test + public void testDeserializeEmpty() throws IOException, ClassNotFoundException { + FileOutputStream fos = new FileOutputStream("test.trie"); + FileInputStream fis = new FileInputStream("test.trie"); + trie.serialize(fos); + trie.deserialize(fis); + } + + @Test + public void testSerializeLotsElements() throws IOException { + FileOutputStream fos = new FileOutputStream("test.trie"); + for (int i = 1; i < 11; i++) { + trie.add(String.valueOf((char) (i + 64))); + } + trie.serialize(fos); + } + + @Test + public void testDeserializeLotsElements() throws IOException, ClassNotFoundException { + FileOutputStream fos = new FileOutputStream("test.trie"); + FileInputStream fis = new FileInputStream("test.trie"); + for (int i = 1; i < 11; i++) { + trie.add(String.valueOf((char) (i + 64))); + } + trie.serialize(fos); + trie.deserialize(fis); + } + + @Test + public void testSizeAfterDeserializeLotsElements() throws IOException, ClassNotFoundException { + FileOutputStream fos = new FileOutputStream("test.trie"); + FileInputStream fis = new FileInputStream("test.trie"); + for (int i = 1; i < 11; i++) { + trie.add(String.valueOf((char) (i + 64))); + } + trie.serialize(fos); + Trie other = new Trie(); + other.deserialize(fis); + assertEquals(true, other.size() == trie.size()); + } + + @Test + public void testContaintsAfterDeserializeLotsElements() throws IOException, ClassNotFoundException { + FileOutputStream fos = new FileOutputStream("test.trie"); + FileInputStream fis = new FileInputStream("test.trie"); + for (int i = 1; i < 11; i++) { + trie.add(String.valueOf((char) (i + 64))); + } + trie.serialize(fos); + Trie other = new Trie(); + other.deserialize(fis); + assertEquals(true, other.contains("A")); + } + + @After + public void rmDir() { + try { + Path path = FileSystems.getDefault().getPath("test.trie"); + Files.delete(path); + } catch (NoSuchFileException ignored) { + } catch (DirectoryNotEmptyException x) { + System.err.format("%s not empty%n", "test.trie"); + } catch (IOException x) { + System.err.println(x); + } + } + +}