Add StackSet

This commit is contained in:
Andrea Cavalli 2020-12-13 22:17:16 +01:00
parent 52b09474ba
commit 06a5e1c525
6 changed files with 389 additions and 0 deletions

View File

@ -0,0 +1,10 @@
package org.warp.commonutils.type;
public enum AddStrategy {
OVERWRITE_POSITION,
KEEP_POSITION;
public static AddStrategy getDefault() {
return KEEP_POSITION;
}
}

View File

@ -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<T> implements StackSet<T> {
private final AddStrategy addStrategy;
private final ObjectLinkedOpenHashSet<T> linkedHashSet;
public FastUtilStackSetWrapper(ObjectLinkedOpenHashSet<T> linkedHashSet) {
this(linkedHashSet, AddStrategy.getDefault());
}
public FastUtilStackSetWrapper(ObjectLinkedOpenHashSet<T> 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();
}
}

View File

@ -0,0 +1,14 @@
package org.warp.commonutils.type;
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
public class HashStackSet<T> extends FastUtilStackSetWrapper<T> {
public HashStackSet() {
super(new ObjectLinkedOpenHashSet<>());
}
public HashStackSet(AddStrategy addStrategy) {
super(new ObjectLinkedOpenHashSet<>(), addStrategy);
}
}

View File

@ -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<T> implements StackSet<T> {
private final LinkedHashSet<T> linkedHashSet;
public JavaStackSetWrapper(LinkedHashSet<T> 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();
}
}

View File

@ -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
*
* <p>A stack must provide the classical {@link #push(Object)} and
* {@link #pop()} operations, but may be also <em>peekable</em>
* 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<K> {
/** 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).
*
* <p>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).
*
* <p>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 <T> StackSet<T> create() {
return new HashStackSet<>();
}
static <T> StackSet<T> wrap(LinkedHashSet<T> linkedHashSet) {
return new JavaStackSetWrapper<>(linkedHashSet);
}
static <T> StackSet<T> wrap(ObjectLinkedOpenHashSet<T> linkedHashSet) {
return new FastUtilStackSetWrapper<>(linkedHashSet);
}
static <T> StackSet<T> wrap(ObjectLinkedOpenHashSet<T> linkedHashSet, AddStrategy addStrategy) {
return new FastUtilStackSetWrapper<>(linkedHashSet, addStrategy);
}
}

View File

@ -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<String> implementation : getImplementations()) {
Assertions.assertThrows(NoSuchElementException.class, implementation::top);
}
}
@Test
public void testStackSetTop() {
for (StackSet<String> implementation : getImplementations()) {
implementation.push("testBottom");
implementation.push("testMiddle");
implementation.push("testTop");
Assertions.assertEquals("testTop", implementation.top());
}
}
@Test
public void testStackSetItemPeekBottom() {
for (StackSet<String> implementation : getImplementations()) {
implementation.push("testBottom");
implementation.push("testMiddle");
implementation.push("testTop");
Assertions.assertEquals("testBottom", implementation.peek(2));
}
}
@Test
public void testStackSetItemPeekMiddle() {
for (StackSet<String> implementation : getImplementations()) {
implementation.push("testBottom");
implementation.push("testMiddle");
implementation.push("testTop");
Assertions.assertEquals("testMiddle", implementation.peek(1));
}
}
@Test
public void testStackSetItemPeekTop() {
for (StackSet<String> implementation : getImplementations()) {
implementation.push("testBottom");
implementation.push("testMiddle");
implementation.push("testTop");
Assertions.assertEquals("testTop", implementation.peek(0));
}
}
@Test
public void testStackSetItemPeekTopSingle() {
for (StackSet<String> implementation : getImplementations()) {
implementation.push("testTop");
Assertions.assertEquals("testTop", implementation.peek(0));
}
}
@Test
public void testStackSetEmptyIsEmpty() {
for (StackSet<String> implementation : getImplementations()) {
Assertions.assertTrue(implementation.isEmpty());
}
}
@Test
public void testStackSetFullIsEmpty() {
for (StackSet<String> implementation : getImplementations()) {
implementation.push("testTop");
Assertions.assertFalse(implementation.isEmpty());
}
}
@Test
public void testStackSetEmptyPeekTop() {
for (StackSet<String> implementation : getImplementations()) {
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> implementation.peek(0));
}
}
@Test
public void testStackSetPeekOverRange() {
for (StackSet<String> implementation : getImplementations()) {
implementation.push("testTop");
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> implementation.peek(10));
}
}
@Test
public void testStackSetPeekUnderRange() {
for (StackSet<String> implementation : getImplementations()) {
implementation.push("testTop");
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> implementation.peek(-10));
}
}
@Test
public void testStackSetItemPop() {
for (StackSet<String> 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<String> 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<String> implementation : getImplementations()) {
Assertions.assertThrows(NoSuchElementException.class, implementation::pop);
}
}
private Set<StackSet<String>> getImplementations() {
return Set.of(new HashStackSet<>(),
new JavaStackSetWrapper<>(new LinkedHashSet<>()),
new FastUtilStackSetWrapper<>(new ObjectLinkedOpenHashSet<>())
);
}
}