From 06a5e1c525f24498c6283308caf0a8849040a77c Mon Sep 17 00:00:00 2001 From: Andrea Cavalli Date: Sun, 13 Dec 2020 22:17:16 +0100 Subject: [PATCH] Add StackSet --- .../warp/commonutils/type/AddStrategy.java | 10 ++ .../type/FastUtilStackSetWrapper.java | 63 ++++++++ .../warp/commonutils/type/HashStackSet.java | 14 ++ .../commonutils/type/JavaStackSetWrapper.java | 73 +++++++++ .../org/warp/commonutils/type/StackSet.java | 85 +++++++++++ .../warp/commonutils/type/TestStackSet.java | 144 ++++++++++++++++++ 6 files changed, 389 insertions(+) create mode 100644 src/main/java/org/warp/commonutils/type/AddStrategy.java create mode 100644 src/main/java/org/warp/commonutils/type/FastUtilStackSetWrapper.java create mode 100644 src/main/java/org/warp/commonutils/type/HashStackSet.java create mode 100644 src/main/java/org/warp/commonutils/type/JavaStackSetWrapper.java create mode 100644 src/main/java/org/warp/commonutils/type/StackSet.java create mode 100644 src/test/java/org/warp/commonutils/type/TestStackSet.java diff --git a/src/main/java/org/warp/commonutils/type/AddStrategy.java b/src/main/java/org/warp/commonutils/type/AddStrategy.java new file mode 100644 index 0000000..4506a53 --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/AddStrategy.java @@ -0,0 +1,10 @@ +package org.warp.commonutils.type; + +public enum AddStrategy { + OVERWRITE_POSITION, + KEEP_POSITION; + + public static AddStrategy getDefault() { + return KEEP_POSITION; + } +} diff --git a/src/main/java/org/warp/commonutils/type/FastUtilStackSetWrapper.java b/src/main/java/org/warp/commonutils/type/FastUtilStackSetWrapper.java new file mode 100644 index 0000000..d4e57da --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/FastUtilStackSetWrapper.java @@ -0,0 +1,63 @@ +package org.warp.commonutils.type; + +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; +import org.warp.commonutils.error.IndexOutOfBoundsException; + +public class FastUtilStackSetWrapper implements StackSet { + + private final AddStrategy addStrategy; + private final ObjectLinkedOpenHashSet linkedHashSet; + + public FastUtilStackSetWrapper(ObjectLinkedOpenHashSet linkedHashSet) { + this(linkedHashSet, AddStrategy.getDefault()); + } + + public FastUtilStackSetWrapper(ObjectLinkedOpenHashSet linkedHashSet, AddStrategy addStrategy) { + this.addStrategy = addStrategy; + this.linkedHashSet = linkedHashSet; + } + + @Override + public boolean push(T o) { + switch (addStrategy) { + case KEEP_POSITION: + return linkedHashSet.add(o); + case OVERWRITE_POSITION: + return linkedHashSet.addAndMoveToLast(o); + default: + throw new UnsupportedOperationException("Unsupported strategy type: " + addStrategy); + } + } + + @Override + public T pop() { + return linkedHashSet.removeLast(); + } + + @Override + public boolean isEmpty() { + return linkedHashSet.isEmpty(); + } + + @Override + public T top() { + return linkedHashSet.last(); + } + + @Override + public T peek(int i) { + var size = linkedHashSet.size(); + int positionFromBottom = size - 1 - i; + + if (positionFromBottom < 0 || positionFromBottom >= size) { + throw new IndexOutOfBoundsException(positionFromBottom, 0, size); + } + + var it = linkedHashSet.iterator(); + // Skip middle elements + if (positionFromBottom > 0) { + it.skip(positionFromBottom); + } + return it.next(); + } +} diff --git a/src/main/java/org/warp/commonutils/type/HashStackSet.java b/src/main/java/org/warp/commonutils/type/HashStackSet.java new file mode 100644 index 0000000..44c2d84 --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/HashStackSet.java @@ -0,0 +1,14 @@ +package org.warp.commonutils.type; + +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; + +public class HashStackSet extends FastUtilStackSetWrapper { + + public HashStackSet() { + super(new ObjectLinkedOpenHashSet<>()); + } + + public HashStackSet(AddStrategy addStrategy) { + super(new ObjectLinkedOpenHashSet<>(), addStrategy); + } +} diff --git a/src/main/java/org/warp/commonutils/type/JavaStackSetWrapper.java b/src/main/java/org/warp/commonutils/type/JavaStackSetWrapper.java new file mode 100644 index 0000000..d642f18 --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/JavaStackSetWrapper.java @@ -0,0 +1,73 @@ +package org.warp.commonutils.type; + +import java.util.LinkedHashSet; +import java.util.NoSuchElementException; +import org.warp.commonutils.error.IndexOutOfBoundsException; + +public class JavaStackSetWrapper implements StackSet { + + private final LinkedHashSet linkedHashSet; + + public JavaStackSetWrapper(LinkedHashSet linkedHashSet) { + this.linkedHashSet = linkedHashSet; + } + + @Override + public boolean push(T o) { + return linkedHashSet.add(o); + } + + @Override + public T pop() { + var it = linkedHashSet.iterator(); + if (!it.hasNext()) { + throw new NoSuchElementException(); + } + // Go to the last element + T lastValue; + do { + lastValue = it.next(); + } while (it.hasNext()); + // Remove the last element + it.remove(); + return lastValue; + } + + @Override + public boolean isEmpty() { + return linkedHashSet.isEmpty(); + } + + @Override + public T top() { + if (linkedHashSet.isEmpty()) { + throw new NoSuchElementException(); + } + + var it = linkedHashSet.iterator(); + T lastValue; + do { + lastValue = it.next(); + } while (it.hasNext()); + return lastValue; + } + + @Override + public T peek(int i) { + var size = linkedHashSet.size(); + int positionFromBottom = size - 1 - i; + + if (positionFromBottom < 0 || positionFromBottom >= size) { + throw new IndexOutOfBoundsException(positionFromBottom, 0, size); + } + + var it = linkedHashSet.iterator(); + // Skip middle elements + if (positionFromBottom > 0) { + for (int j = 0; j < positionFromBottom; j++) { + it.next(); + } + } + return it.next(); + } +} diff --git a/src/main/java/org/warp/commonutils/type/StackSet.java b/src/main/java/org/warp/commonutils/type/StackSet.java new file mode 100644 index 0000000..64a8009 --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/StackSet.java @@ -0,0 +1,85 @@ +package org.warp.commonutils.type; + +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; +import java.util.LinkedHashSet; +import java.util.NoSuchElementException; + +/** + * A stack but with the behavior of a Linked HashSet + * + *

A stack must provide the classical {@link #push(Object)} and + * {@link #pop()} operations, but may be also peekable + * to some extent: it may provide just the {@link #top()} function, + * or even a more powerful {@link #peek(int)} method that provides + * access to all elements on the stack (indexed from the top, which + * has index 0). + */ + +public interface StackSet { + + + /** Pushes the given object on the stack. + * + * @param o the object that will become the new top of the stack. + * @return true if added, false if already present + */ + + boolean push(K o); + + /** Pops the top off the stack. + * + * @return the top of the stack. + * @throws NoSuchElementException if the stack is empty. + */ + + K pop(); + + /** Checks whether the stack is empty. + * + * @return true if the stack is empty. + */ + + boolean isEmpty(); + + /** Peeks at the top of the stack (optional operation). + * + *

This default implementation returns {@link #peek(int) peek(0)}. + * + * @return the top of the stack. + * @throws NoSuchElementException if the stack is empty. + */ + + default K top() { + return peek(0); + } + + /** Peeks at an element on the stack (optional operation). + * + *

This default implementation just throws an {@link UnsupportedOperationException}. + * + * @param i an index from the stop of the stack (0 represents the top). + * @return the {@code i}-th element on the stack. + * @throws IndexOutOfBoundsException if the designated element does not exist.. + */ + + default K peek(int i) { + throw new UnsupportedOperationException(); + } + + static StackSet create() { + return new HashStackSet<>(); + } + + static StackSet wrap(LinkedHashSet linkedHashSet) { + return new JavaStackSetWrapper<>(linkedHashSet); + } + + static StackSet wrap(ObjectLinkedOpenHashSet linkedHashSet) { + return new FastUtilStackSetWrapper<>(linkedHashSet); + } + + static StackSet wrap(ObjectLinkedOpenHashSet linkedHashSet, AddStrategy addStrategy) { + return new FastUtilStackSetWrapper<>(linkedHashSet, addStrategy); + } + +} diff --git a/src/test/java/org/warp/commonutils/type/TestStackSet.java b/src/test/java/org/warp/commonutils/type/TestStackSet.java new file mode 100644 index 0000000..af183a1 --- /dev/null +++ b/src/test/java/org/warp/commonutils/type/TestStackSet.java @@ -0,0 +1,144 @@ +package org.warp.commonutils.type; + +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; +import java.util.LinkedHashSet; +import java.util.NoSuchElementException; +import java.util.Set; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.warp.commonutils.error.IndexOutOfBoundsException; + +public class TestStackSet { + + @Test + public void testStackSetEmptyTop() { + for (StackSet implementation : getImplementations()) { + Assertions.assertThrows(NoSuchElementException.class, implementation::top); + } + } + + @Test + public void testStackSetTop() { + for (StackSet implementation : getImplementations()) { + implementation.push("testBottom"); + implementation.push("testMiddle"); + implementation.push("testTop"); + Assertions.assertEquals("testTop", implementation.top()); + } + } + + @Test + public void testStackSetItemPeekBottom() { + for (StackSet implementation : getImplementations()) { + implementation.push("testBottom"); + implementation.push("testMiddle"); + implementation.push("testTop"); + Assertions.assertEquals("testBottom", implementation.peek(2)); + } + } + + @Test + public void testStackSetItemPeekMiddle() { + for (StackSet implementation : getImplementations()) { + implementation.push("testBottom"); + implementation.push("testMiddle"); + implementation.push("testTop"); + Assertions.assertEquals("testMiddle", implementation.peek(1)); + } + } + + @Test + public void testStackSetItemPeekTop() { + for (StackSet implementation : getImplementations()) { + implementation.push("testBottom"); + implementation.push("testMiddle"); + implementation.push("testTop"); + Assertions.assertEquals("testTop", implementation.peek(0)); + } + } + + @Test + public void testStackSetItemPeekTopSingle() { + for (StackSet implementation : getImplementations()) { + implementation.push("testTop"); + Assertions.assertEquals("testTop", implementation.peek(0)); + } + } + + @Test + public void testStackSetEmptyIsEmpty() { + for (StackSet implementation : getImplementations()) { + Assertions.assertTrue(implementation.isEmpty()); + } + } + + @Test + public void testStackSetFullIsEmpty() { + for (StackSet implementation : getImplementations()) { + implementation.push("testTop"); + Assertions.assertFalse(implementation.isEmpty()); + } + } + + @Test + public void testStackSetEmptyPeekTop() { + for (StackSet implementation : getImplementations()) { + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> implementation.peek(0)); + } + } + + @Test + public void testStackSetPeekOverRange() { + for (StackSet implementation : getImplementations()) { + implementation.push("testTop"); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> implementation.peek(10)); + } + } + + @Test + public void testStackSetPeekUnderRange() { + for (StackSet implementation : getImplementations()) { + implementation.push("testTop"); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> implementation.peek(-10)); + } + } + + @Test + public void testStackSetItemPop() { + for (StackSet implementation : getImplementations()) { + implementation.push("testBottom"); + implementation.push("testMiddle"); + implementation.push("testTop"); + implementation.push("testExtra"); + Assertions.assertEquals("testTop", implementation.peek(1)); + implementation.pop(); + Assertions.assertEquals("testTop", implementation.peek(0)); + Assertions.assertEquals("testTop", implementation.top()); + } + } + + @Test + public void testStackSetOneItemOnePop() { + for (StackSet implementation : getImplementations()) { + implementation.push("testExtra"); + implementation.pop(); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> implementation.peek(0)); + Assertions.assertThrows(NoSuchElementException.class, implementation::top); + Assertions.assertTrue(implementation.isEmpty()); + } + } + + @Test + public void testStackSetItemEmptyPop() { + for (StackSet implementation : getImplementations()) { + Assertions.assertThrows(NoSuchElementException.class, implementation::pop); + } + } + + private Set> getImplementations() { + return Set.of(new HashStackSet<>(), + new JavaStackSetWrapper<>(new LinkedHashSet<>()), + new FastUtilStackSetWrapper<>(new ObjectLinkedOpenHashSet<>()) + ); + } +}