diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..11cc95b --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'java' +} + +group 'org.example' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.jetbrains:annotations:16.0.2' + implementation 'junit:junit:4.13.1' + implementation 'org.testng:testng:7.1.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' +} + +test { + useTestNG() +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..edc7c28 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Aug 29 18:05:53 MSK 2022 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..d1d1740 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'algorithms-project-2022' diff --git a/src/main/java/Node.java b/src/main/java/Node.java new file mode 100644 index 0000000..45b4c95 --- /dev/null +++ b/src/main/java/Node.java @@ -0,0 +1,102 @@ +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; + +public class Node +{ + + static void wire(@NotNull Node from, @Nullable Node to, int atLevel) + { + if (to == null) + { + from.childAtLevel.remove(atLevel); + } + else + { + // from -> to + if (from.childAtLevel.size() > atLevel) + from.childAtLevel.set(atLevel, to); + else + from.childAtLevel.add(atLevel, to); + // from <- to + if (to.parentAtLevel.size() > atLevel) + to.parentAtLevel.set(atLevel, from); + else + to.parentAtLevel.add(atLevel, from); + } + } + + private final @NotNull ArrayList> childAtLevel; + private final @NotNull ArrayList> parentAtLevel; + private final @NotNull T key; + + Node(@NotNull T key) { + this.key = key; + this.childAtLevel = new ArrayList<>(); + this.parentAtLevel = new ArrayList<>(); + } + + @NotNull T key() { + return this.key; + } + + @NotNull Node next(int atLevel) { + return childAtLevel.get(atLevel); + } + + @Nullable Node child(int atLevel) + { + return (hasNext(atLevel)) ? next(atLevel) : null; + } + + boolean hasNext(int atLevel) + { + return atLevel < childAtLevel.size(); + } + + boolean hasPrev(int atLevel) + { + return parentAtLevel.size() > atLevel; + } + + @NotNull Node prev(int atLevel) + { + return parentAtLevel.get(atLevel); + } + + @Nullable Node parent(int atLevel) + { + return (hasPrev(atLevel)) ? prev(atLevel) : null; + } + + @NotNull Node nextLowest() { + return next(0); + } + + void connect(@NotNull Node child, int atLevel) + { + if (atLevel > childAtLevel.size()) + throw new RuntimeException("Semantic error: " + this + " to " + child + " at level " + atLevel); + final @Nullable Node hisParent = child.parent(atLevel); + if (hisParent != null) + wire(hisParent, this, atLevel); + wire(this, child, atLevel); + } + + int childLevelsSize() + { + return childAtLevel.size(); + } + + int parentLevelSize() + { + return parentAtLevel.size(); + } + + @Override + public String toString() + { + return key.toString(); + } +} diff --git a/src/main/java/SkipList.java b/src/main/java/SkipList.java new file mode 100644 index 0000000..2249804 --- /dev/null +++ b/src/main/java/SkipList.java @@ -0,0 +1,245 @@ +import com.google.common.collect.Iterators; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.AbstractSet; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.SortedSet; +import java.util.Stack; +import java.util.TreeSet; + +public class SkipList> extends AbstractSet implements SortedSet { + + private static Random entropy = new Random(); + + private static boolean heads() { return entropy.nextBoolean(); } + + private int size; + + // tail всегда null; + private @Nullable Node head; + + SkipList() { } + + SkipList(T firstKey) { + this.head = new Node<>(firstKey); + } + + void insert(T key) + { + final Node born = new Node<>(key); + if (head == null) // so, no head? + head = born; + else + { + final @NotNull Stack> trace = new Stack<>(); + final @Nullable Node leftmost = leftmost(head, key, nOfLevels() - 1, trace); + if (leftmost == null) // new head + { + for (int level = 0; level < nOfLevels(); level++) + born.connect(head, level); + this.head = born; + } + else + { + trace.push(leftmost); + bubbleUp(born, trace); + } + } + size++; + } + + private void bubbleUp(final @NotNull Node node, final @NotNull Stack> trace) + { + int atLevel = 0; + do { + final Node parent = (trace.empty()) ? head : trace.pop(); + parent.connect(node, atLevel++); + } while (heads()); + } + + boolean delete(T key) + { + if (head == null) + return false; + if (equalTo(head.key(), key)) + { + head = (head.hasNext(0)) ? head.child(0) : null; + size--; + return true; + } + final @Nullable Node leftmost = leftmost(key); + if (leftmost == null || !equalTo(leftmost.key(), key)) + return false; + for (int level = 0; level < leftmost.parentLevelSize(); level++) + Node.wire(leftmost.parent(level), leftmost.child(level), level); + size--; + return true; + } + + List keys() + { + final List keys = new LinkedList<>(); + Iterators.addAll(keys, iterator()); + return keys; + } + + boolean contains(T key) + { + Node leftmost = leftmost(key); + return (leftmost != null) && (equalTo(leftmost.key(), key)); + } + + private @Nullable Node leftmost(T key) { + return (head != null) ? leftmost(head, key, nOfLevels() - 1, null) : null; + } + + // find closest to "key" element from the left. Null if key should become a new head. + private @Nullable Node leftmost(@NotNull Node current, T key, int atLevel, + @Nullable Stack> trace) + { + if (equalTo(current.key(), key)) + return current; + else if (lessThan(current.key(), key)) + { + // find first node >= key or reach the end of level + @NotNull Node leftmostOnCurrentLevel = current; + while (leftmostOnCurrentLevel.hasNext(atLevel) && lessOrEqual(leftmostOnCurrentLevel.next(atLevel).key(), key)) + leftmostOnCurrentLevel = leftmostOnCurrentLevel.next(atLevel); + return (atLevel > 0) ? + leftmost(leftmostOnCurrentLevel, key, atLevel - 1, push(leftmostOnCurrentLevel, trace)) : + leftmostOnCurrentLevel; + } + else // current > key + return null; + } + + private Stack> push(@NotNull Node node, @Nullable Stack> stack) + { + if (stack == null) + return null; + stack.push(node); + return stack; + } + + private int nOfLevels() { + return (head == null) ? 0 : + (head.childLevelsSize() == 0) ? 1 : + head.childLevelsSize(); + } + + @NotNull + @Override + public Iterator iterator() { + return new SkipIterator(0); + } + + @Override + public int size() { + return this.size; + } + + @Override + public Comparator comparator() { + return Comparator.naturalOrder(); + } + + @NotNull + @Override + public SortedSet subSet(T fromElement, T toElement) { + final TreeSet result = new TreeSet<>(); + iterator().forEachRemaining(result::add); + return result.subSet(fromElement, toElement); + } + + @NotNull + @Override + public SortedSet headSet(T toElement) { + final TreeSet result = new TreeSet<>(); + iterator().forEachRemaining(result::add); + return result.headSet(toElement); + } + + @NotNull + @Override + public SortedSet tailSet(T fromElement) { + final TreeSet result = new TreeSet<>(); + iterator().forEachRemaining(result::add); + return result.tailSet(fromElement); + } + + @Override + public T first() { + if (head == null) + throw new NoSuchElementException(); + return head.key(); + } + + @Override + public T last() { + return Iterators.getLast(iterator()); + } + + private class SkipIterator implements Iterator { + + private Node current; + private int level; + private boolean firstTime = true; + + SkipIterator(int level) { + this.current = head; + this.level = level; + } + + @Override + public boolean hasNext() + { + return current.hasNext(level); + } + + @Override + public T next() + { + if (firstTime) + { + firstTime = false; + return current.key(); + } + current = current.next(level); + return current.key(); + } + } + + private boolean equalTo(T x, T y) { + return x.compareTo(y) == 0; + } + + private boolean lessThan(T x, T y) { + return x.compareTo(y) < 0; + } + + private boolean lessOrEqual(T x, T y) + { + return (lessThan(x, y) || equalTo(x, y)); + } + + @Override + public String toString() + { + final StringBuilder result = new StringBuilder(); + for (int level = 0; level < nOfLevels(); level++) + { + final List elementsAtLevel = new LinkedList<>(); + new SkipIterator(level) + .forEachRemaining(el -> elementsAtLevel.add(el.toString())); + result.append(String.join(" <-> ", elementsAtLevel)); + result.append("\n"); + } + return result.toString(); + } +} \ No newline at end of file diff --git a/src/test/java/Tests.java b/src/test/java/Tests.java new file mode 100644 index 0000000..6146905 --- /dev/null +++ b/src/test/java/Tests.java @@ -0,0 +1,121 @@ +import org.testng.annotations.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class Tests { + + @Test + public void insert0() { + final SkipList patient = new SkipList<>(); + assertEquals(0, patient.size()); + } + + @Test + public void insert1() { + final SkipList patient = new SkipList<>(); + patient.insert(1); + assertTrue(patient.contains(1)); + assertEquals(1, patient.size()); + } + + @Test + public void insert2() { + final SkipList patient = new SkipList<>(); + patient.insert(1); + patient.insert(2); + assertTrue(patient.contains(1)); + assertTrue(patient.contains(2)); + assertEquals(2, patient.size()); + } + + @Test + public void insert3() { + final SkipList patient = new SkipList<>(); + patient.insert(1); + patient.insert(2); + patient.insert(3); + assertTrue(patient.contains(1)); + assertTrue(patient.contains(2)); + assertTrue(patient.contains(3)); + assertEquals(3, patient.size()); + } + + @Test + public void insert4() { + final SkipList patient = new SkipList<>(); + patient.insert(1); + patient.insert(2); + patient.insert(3); + patient.insert(4); + assertTrue(patient.contains(1)); + assertTrue(patient.contains(2)); + assertTrue(patient.contains(3)); + assertTrue(patient.contains(4)); + assertEquals(4, patient.size()); + } + + @Test + public void insert5() { + final SkipList patient = new SkipList<>(); + patient.insert(1); + patient.insert(2); + patient.insert(3); + patient.insert(4); + patient.insert(5); + assertTrue(patient.contains(1)); + assertTrue(patient.contains(2)); + assertTrue(patient.contains(3)); + assertTrue(patient.contains(4)); + assertTrue(patient.contains(5)); + assertEquals(5, patient.size()); + } + + @Test + public void insert10() { + final SkipList patient = new SkipList<>(); + patient.insert(10); + patient.insert(9); + patient.insert(8); + patient.insert(7); + patient.insert(6); + patient.insert(5); + patient.insert(4); + patient.insert(3); + patient.insert(2); + patient.insert(1); + assertEquals("1 <-> 2 <-> 3 <-> 4 <-> 5 <-> 6 <-> 7 <-> 8 <-> 9 <-> 10\n", patient.toString()); + } + + @Test + public void delete1() { + final SkipList patient = new SkipList<>(); + patient.insert(1); + assertTrue(patient.delete(1)); + assertFalse(patient.contains(1)); + assertEquals(0, patient.size()); + } + + @Test + public void delete2() { + final SkipList patient = new SkipList<>(); + patient.insert(1); + assertFalse(patient.delete(10)); + assertTrue(patient.contains(1)); + assertEquals(1, patient.size()); + } + + @Test + public void delete3() { + final SkipList patient = new SkipList<>(); + patient.insert(1); + patient.insert(2); + assertTrue(patient.delete(1)); + assertFalse(patient.contains(1)); + assertEquals(2, patient.first()); + assertEquals(1, patient.size()); + } + +}