Skip to content
Open
4 changes: 2 additions & 2 deletions src/main/java/ru/spbau/mit/StreamSerializable.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ public interface StreamSerializable {
/**
* @throws SerializationException in case of IOException during serialization
*/
void serialize(OutputStream out);
void serialize(OutputStream out) throws SerializationException;

/**
* Replace current state with data from input stream containing serialized data
* @throws SerializationException in case of IOException during deserialization
*/
void deserialize(InputStream in);
void deserialize(InputStream in) throws SerializationException;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

особого смысла в отдельном исключении нет, по желанию -- можно делать своё, можно IO

}
224 changes: 224 additions & 0 deletions src/main/java/ru/spbau/mit/StringSetImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package ru.spbau.mit;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class StringSetImpl implements StringSet, StreamSerializable {
private static final int BYTES_IN_INT = 4;
private static final int BITS_IN_BYTE = 8;
private Vertex root;

public StringSetImpl() {
root = null;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

этот конструктор можно убрать:

  • если поле ссылочного типа нигде не инициализируется (ни в конструкторе, ни в объявлении), то ему автоматически присвоится null
  • если в классе нет конструкторов, то будет создан конструктор по умолчанию (public, без параметров)

но можно и оставить, это по желанию


public boolean add(String element) {
Vertex current = traverseWord(element, true);
if (current.isTerminal) {
return false;
} else {
current.isTerminal = true;
while (current != null) {
current.subTreeSize++;
current = current.parent;
}
return true;
}
}

public boolean contains(String element) {
Vertex current = traverseWord(element, false);
return current != null && current.isTerminal;
}

public boolean remove(String element) {
Vertex current = traverseWord(element, false);

if (current == null || !current.isTerminal) {
return false;
}

current.isTerminal = false;
for (int i = element.length() - 1; i >= 0; --i) {
removeOnEmpty(current, element.charAt(i));
current = current.parent;
}

root.subTreeSize--;
if (root.subTreeSize == 0) {
root = null;
}

return true;
}

public int size() {
return root == null ? 0 : root.subTreeSize;
}

public int howManyStartsWithPrefix(String prefix) {
Vertex current = traverseWord(prefix, false);
return current == null ? 0 : current.subTreeSize;
}

public void serialize(OutputStream out) throws SerializationException {
Vertex.serializeVertex(root, out);
}

public void deserialize(InputStream in) throws SerializationException {
root = Vertex.deserializeVertex(in, null);
}

private Vertex traverseWord(String element, boolean addIfNotExists) {
if (root == null) {
if (addIfNotExists) {
root = new Vertex(null); // root only.
} else {
return null;
}
}

Vertex current = root;

for (int i = 0; i < element.length(); ++i) {
char c = element.charAt(i);
int stepCharIndex = Vertex.stepCharIndex(c);
if (current.next[stepCharIndex] == null) {
if (addIfNotExists) {
current.next[stepCharIndex] = new Vertex(current);
} else {
return null;
}
}
current = current.next[stepCharIndex];
}
return current;
}

private void removeOnEmpty(Vertex current, char stepChar) {
current.subTreeSize--;
if (current.subTreeSize == 0 && current.parent != null) {
int stepCharIndex = Vertex.stepCharIndex(stepChar);
current.parent.next[stepCharIndex] = null;
}
}

private static void booleanSerialize(boolean b, OutputStream out) throws SerializationException {
try {
if (b) {
out.write(1);
} else {
out.write(0);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

можно тернарный оператор использовать out.write(b ? 1 : 0);

} catch (IOException e) {
throw new SerializationException();
}
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

про примитивы:

  • в этих методах можно было бросать IO например, а потом выше преобразовывать или оборачивать в SerializationException
  • можно было заиспользовать DataInput/DataOutputStream, тогда этих методов бы не было, но оборачивать в SerializationException всё равно пришлось бы

private static void intSerialize(int num, OutputStream out) throws SerializationException {
final int byteMask = 0xFF;
try {
for (int i = 0; i < BYTES_IN_INT; ++i) {
out.write(num & byteMask);
num >>= BITS_IN_BYTE;
}
} catch (IOException e) {
throw new SerializationException();
}
}

private static boolean booleanDeserialize(InputStream in) throws SerializationException {
try {
int i = in.read();
if (i != 1 && i != 0) {
throw new SerializationException();
}
return i == 1;
} catch (IOException e) {
throw new SerializationException();
}
}

private static int intDeserialize(InputStream in) throws SerializationException {
try {
int num = 0;
for (int i = 0; i < BYTES_IN_INT; ++i) {
final int readNum = in.read();
num |= readNum << (i * BITS_IN_BYTE);
}
return num;
} catch (IOException e) {
throw new SerializationException();
}
}

private static class Vertex implements StreamSerializable {
private static final int CHAR_POWER = 2 * 26;
private static final int VERTEX_MAGIC = 0xAABBCCDD;
private static final int EMPTY_VERTEX_MAGIC = 0xDDCCBBAA;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

появление слова magic вносит неразбериху, и другому разработчику может показаться, что автор не очень понимает то, что сам написал)) в данном случае лучше использовать EMPTY_VERTEX_FLAG, NON_EMPTY_VERTEX_FLAG. Значения не очень важны, хоть 0 и 1.

private final Vertex[] next;
private boolean isTerminal;
private Vertex parent;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final

private int subTreeSize;

Vertex(Vertex parent) {
isTerminal = false;
next = new Vertex[CHAR_POWER];
this.parent = parent;
subTreeSize = 0;
}

public static void serializeVertex(Vertex v, OutputStream out) throws SerializationException {
if (v == null) {
intSerialize(EMPTY_VERTEX_MAGIC, out);
} else {
v.serialize(out);
}
}

// Question: constructor would be better?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

отсылаю к практике, когда обсуждались конструкторы и статик методы. мне текущий вариант больше нравится, он симметричен к методу выше

public static Vertex deserializeVertex(InputStream in, Vertex parent) throws SerializationException {
final int magic = intDeserialize(in);
if (magic == EMPTY_VERTEX_MAGIC) {
return null;
} else if (magic == VERTEX_MAGIC) {
Vertex v = new Vertex(parent);
v.deserialize(in);
return v;
} else {
throw new SerializationException();
}
}

public static int stepCharIndex(char stepChar) {
if (Character.isLowerCase(stepChar)) {
return (int) stepChar - 'a';
} else {
return (CHAR_POWER / 2) + (int) (stepChar - 'A');
}
}

public void serialize(OutputStream out) throws SerializationException {
intSerialize(VERTEX_MAGIC, out);
for (int i = 0; i < next.length; ++i) {
if (next[i] == null) {
intSerialize(EMPTY_VERTEX_MAGIC, out);
} else {
next[i].serialize(out);
}
}
booleanSerialize(isTerminal, out);
intSerialize(subTreeSize, out);
}

public void deserialize(InputStream in) throws SerializationException {
for (int i = 0; i < next.length; ++i) {
next[i] = deserializeVertex(in, this);
}

isTerminal = booleanDeserialize(in);
subTreeSize = intDeserialize(in);
}
}
}
74 changes: 74 additions & 0 deletions src/test/java/ru/spbau/mit/StreamSerializableTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package ru.spbau.mit;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тесты сильно усложнять не всегда имеет смысл, лучше написать побольше простых случаев, включая какие-то крайние


import org.junit.Test;

import java.io.*;

import static org.junit.Assert.*;

public class StreamSerializableTest {
@Test(expected = SerializationException.class)
public void testFailEmpty() {
testDeserialization(new ByteArrayOutputStream());
}

@Test(expected = SerializationException.class)
public void testFailDummyHeader() {
final int dummyHeader = 0xBABEFAFA;
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeInteger(dummyHeader, out);
testDeserialization(out);
}

@Test
public void testEmpty() {
StringSetImpl s = new StringSetImpl();
assertEmptyStringSetImpl(s); // just for sureness.
ByteArrayOutputStream out = new ByteArrayOutputStream();
s.serialize(out);
StringSetImpl s1 = testDeserialization(out);
assertEmptyStringSetImpl(s1);

String[] arr = {"abc", "cde", ""};
for (String str : arr) {
assertTrue(s1.add(str));
}

for (String str : arr) {
assertTrue(s1.remove(str));
}

out.reset();
s1.serialize(out);

StringSetImpl s2 = testDeserialization(out);
assertEmptyStringSetImpl(s2);
}

private static void writeInteger(int num, OutputStream out) {
final int bytesInInteger = 4;
final int bitsInByte = 8;
try {
for (int i = 0; i < bytesInInteger; ++i) {
final int lowestByte = num & ((1 << bitsInByte) - 1);
num >>= bitsInByte;
out.write(lowestByte);
}
} catch (IOException e) {
fail();
}
}

private static StringSetImpl testDeserialization(ByteArrayOutputStream out) throws SerializationException {
StringSetImpl stringSet = new StringSetImpl();
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
((StreamSerializable) stringSet).deserialize(in);
return stringSet;
}

private static void assertEmptyStringSetImpl(StringSetImpl s) {
assertNotNull(s);
assertEquals(0, s.size());
assertEquals(0, s.howManyStartsWithPrefix(""));
}
}
Loading