Add CircularDeque
This commit is contained in:
parent
d682042aaa
commit
c9500bb6bd
|
@ -23,8 +23,16 @@ import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.AbstractCollection;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Iterator;
|
||||||
/**
|
/**
|
||||||
* CircularDeque is a deque with a fixed size that replaces its oldest
|
* CircularDeque is a deque with a fixed size that replaces its oldest
|
||||||
* element if full.
|
* element if full.
|
||||||
|
@ -99,7 +107,13 @@ public class CircularDeque<E> extends AbstractCollection<E>
|
||||||
* @throws NullPointerException if the collection is null
|
* @throws NullPointerException if the collection is null
|
||||||
*/
|
*/
|
||||||
public CircularDeque(final Collection<? extends E> coll) {
|
public CircularDeque(final Collection<? extends E> coll) {
|
||||||
this(coll.size());
|
if (coll == null) {
|
||||||
|
throw new NullPointerException("Collection must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
elements = (E[]) new Object[coll.size()];
|
||||||
|
maxElements = elements.length;
|
||||||
|
|
||||||
addAll(coll);
|
addAll(coll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +181,7 @@ public class CircularDeque<E> extends AbstractCollection<E>
|
||||||
* @param index the index to increment
|
* @param index the index to increment
|
||||||
* @return the updated index
|
* @return the updated index
|
||||||
*/
|
*/
|
||||||
private int indexIncrease(int index) {
|
private int increaseIndex(int index) {
|
||||||
index++;
|
index++;
|
||||||
if (index >= maxElements) {
|
if (index >= maxElements) {
|
||||||
index = 0;
|
index = 0;
|
||||||
|
@ -336,58 +350,138 @@ public class CircularDeque<E> extends AbstractCollection<E>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeFirstOccurrence(final Object o) {
|
public boolean removeFirstOccurrence(final Object o) {
|
||||||
int mask = elements.length - 1;
|
|
||||||
int i = head;
|
int i = head;
|
||||||
Object x;
|
while (true) {
|
||||||
while ((x = elements[i]) != null) {
|
if (Objects.equals(o,elements[i])) {
|
||||||
if (Objects.equals(o,x)) {
|
|
||||||
delete(i);
|
delete(i);
|
||||||
ArrayDeque;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
i = (i + 1) & mask;
|
if (i == decreaseIndex(tail)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i = increaseIndex(i);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeLastOccurrence(final Object o) {
|
public boolean removeLastOccurrence(final Object o) {
|
||||||
return false;
|
int i = decreaseIndex(tail);
|
||||||
|
while (true) {
|
||||||
|
if (Objects.equals(o,elements[i])) {
|
||||||
|
delete(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (i == head) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i = decreaseIndex(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(final E e) {
|
||||||
|
if (isAtFullCapacity()) {
|
||||||
|
remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
elements[tail++] = e;
|
||||||
|
|
||||||
|
if (tail >= maxElements) {
|
||||||
|
tail = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tail == head) {
|
||||||
|
full = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean offer(final E e) {
|
public boolean offer(final E e) {
|
||||||
return false;
|
return offerLast(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public E remove() {
|
public E remove() {
|
||||||
return null;
|
return removeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public E poll() {
|
public E poll() {
|
||||||
return null;
|
return pollFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public E element() {
|
public E element() {
|
||||||
return null;
|
return getFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public E peek() {
|
public E peek() {
|
||||||
return null;
|
return peekFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void push(final E e) {
|
public void push(final E e) {
|
||||||
|
addFirst(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public E pop() {
|
public E pop() {
|
||||||
return null;
|
return removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the element at the specified position in the elements array,
|
||||||
|
* adjusting head and tail as necessary. This can result in motion of
|
||||||
|
* elements backwards or forwards in the array.
|
||||||
|
*
|
||||||
|
* <p>This method is called delete rather than remove to emphasize
|
||||||
|
* that its semantics differ from those of {@link List#remove(int)}.
|
||||||
|
*
|
||||||
|
* @return true if elements moved backwards
|
||||||
|
*/
|
||||||
|
private boolean delete(final int i) {
|
||||||
|
if (i == head) {
|
||||||
|
elements[i] = null;
|
||||||
|
head = increaseIndex(head);
|
||||||
|
full = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == decreaseIndex(tail)) {
|
||||||
|
elements[i] = null;
|
||||||
|
tail = decreaseIndex(tail);
|
||||||
|
full = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int front = i - head;
|
||||||
|
final int back = tail - i - 1;
|
||||||
|
|
||||||
|
if (head < tail) {
|
||||||
|
if (front < back) {
|
||||||
|
System.arraycopy(elements, head, elements, head + 1, front);
|
||||||
|
head++;
|
||||||
|
} else {
|
||||||
|
System.arraycopy(elements, i + 1, elements, i, back);
|
||||||
|
tail--;
|
||||||
|
}
|
||||||
|
} else if (head >tail) {
|
||||||
|
if (i > head) {
|
||||||
|
System.arraycopy(elements, head, elements, head + 1, front);
|
||||||
|
head++;
|
||||||
|
} else if (i < tail) {
|
||||||
|
System.arraycopy(elements, i + 1, elements, i, back);
|
||||||
|
tail--;
|
||||||
|
} else {
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
full = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -425,16 +519,115 @@ public class CircularDeque<E> extends AbstractCollection<E>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int maxSize() {
|
public int maxSize() {
|
||||||
return 0;
|
return maxElements;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<E> iterator() {
|
public Iterator<E> iterator() {
|
||||||
return null;
|
return new Iterator<E>() {
|
||||||
|
|
||||||
|
private int index = head;
|
||||||
|
private int lastReturnedIndex = -1;
|
||||||
|
private boolean isFirst = full;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return isFirst || index != tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public E next() {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
isFirst = false;
|
||||||
|
lastReturnedIndex = index;
|
||||||
|
index = increaseIndex(index);
|
||||||
|
return elements[lastReturnedIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
if (lastReturnedIndex == -1) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// First element can be removed quickly
|
||||||
|
if (lastReturnedIndex == head) {
|
||||||
|
CircularDeque.this.remove();
|
||||||
|
lastReturnedIndex = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = lastReturnedIndex + 1;
|
||||||
|
if (head < lastReturnedIndex && pos < tail) {
|
||||||
|
// shift in one part
|
||||||
|
System.arraycopy(elements, pos, elements, lastReturnedIndex, tail - pos);
|
||||||
|
} else {
|
||||||
|
// Other elements require us to shift the subsequent elements
|
||||||
|
while (pos != tail) {
|
||||||
|
if (pos >= maxElements) {
|
||||||
|
elements[pos - 1] = elements[0];
|
||||||
|
pos = 0;
|
||||||
|
} else {
|
||||||
|
elements[decreaseIndex(pos)] = elements[pos];
|
||||||
|
pos = increaseIndex(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastReturnedIndex = -1;
|
||||||
|
tail = decreaseIndex(tail);
|
||||||
|
elements[tail] = null;
|
||||||
|
full = false;
|
||||||
|
index = decreaseIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<E> descendingIterator() {
|
public Iterator<E> descendingIterator() {
|
||||||
return null;
|
return new DescendingIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DescendingIterator implements Iterator<E> {
|
||||||
|
/*
|
||||||
|
* This class is nearly a mirror-image of DeqIterator, using
|
||||||
|
* tail instead of head for initial cursor, and head instead of
|
||||||
|
* tail for fence.
|
||||||
|
*/
|
||||||
|
private int cursor = tail;
|
||||||
|
private int fence = head;
|
||||||
|
private int lastRet = -1;
|
||||||
|
private boolean isFull = full;
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return isFull || cursor != fence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E next() {
|
||||||
|
if (!hasNext())
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
cursor = decreaseIndex(cursor);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
E result = (E) elements[cursor];
|
||||||
|
if (head != fence )
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
lastRet = cursor;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
if (lastRet < 0)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
if (!delete(lastRet)) {
|
||||||
|
cursor = increaseIndex(cursor);
|
||||||
|
fence = head;
|
||||||
|
}
|
||||||
|
lastRet = -1;
|
||||||
|
isFull = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* This package contains implementations for the {@link java.util.Deque Deque} interface.
|
||||||
|
* <p>
|
||||||
|
* The following implementations are provided in the package:
|
||||||
|
* <ul>
|
||||||
|
* <li>CircularDeque - implements a Deque with a fixed size that discards oldest when full
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* The following decorators are provided in the package:
|
||||||
|
* <ul>
|
||||||
|
* <li>Predicated - ensures that only elements that are valid according to a predicate can be added
|
||||||
|
* <li>Transformed - transforms elements added to the queue
|
||||||
|
* <li>Blocking - ensures the collection is thread-safe and can be operated concurrently
|
||||||
|
* <li>Synchronized - ensures the collection is thread-safe
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.apache.commons.collections4.deque;
|
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.commons.collections4.deque;
|
||||||
|
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension of {@link AbstractDequeTest} for exercising the
|
||||||
|
* {@link CircularDeque} implementation.
|
||||||
|
*
|
||||||
|
* @since 4.5
|
||||||
|
*/
|
||||||
|
public class CircularDequeTest<E> extends AbstractDequeTest<E> {
|
||||||
|
/**
|
||||||
|
* JUnit constructor.
|
||||||
|
*
|
||||||
|
* @param testName the test class name
|
||||||
|
*/
|
||||||
|
public CircularDequeTest(String testName) {
|
||||||
|
super(testName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an empty CircularDeque that won't overflow.
|
||||||
|
*
|
||||||
|
* @return an empty CircularDeque
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Deque<E> makeObject() {
|
||||||
|
return new CircularDeque<>(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the removal operation actually removes the first element.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testCircularDequeCircular() {
|
||||||
|
final List<E> list = new LinkedList<>();
|
||||||
|
list.add((E) "A");
|
||||||
|
list.add((E) "B");
|
||||||
|
list.add((E) "C");
|
||||||
|
final Deque<E> deque = new CircularDeque<>(list);
|
||||||
|
|
||||||
|
assertEquals(true, deque.contains("A"));
|
||||||
|
assertEquals(true, deque.contains("B"));
|
||||||
|
assertEquals(true, deque.contains("C"));
|
||||||
|
|
||||||
|
deque.add((E) "D");
|
||||||
|
|
||||||
|
assertEquals(false, deque.contains("A"));
|
||||||
|
assertEquals(true, deque.contains("B"));
|
||||||
|
assertEquals(true, deque.contains("C"));
|
||||||
|
assertEquals(true, deque.contains("D"));
|
||||||
|
|
||||||
|
assertEquals("B", deque.peek());
|
||||||
|
assertEquals("B", deque.remove());
|
||||||
|
assertEquals("C", deque.remove());
|
||||||
|
assertEquals("D", deque.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the constructor correctly throws an exception.
|
||||||
|
*/
|
||||||
|
public void testConstructorException1() {
|
||||||
|
try {
|
||||||
|
new CircularDeque<E>(0);
|
||||||
|
} catch (final IllegalArgumentException ex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the constructor correctly throws an exception.
|
||||||
|
*/
|
||||||
|
public void testConstructorException2() {
|
||||||
|
try {
|
||||||
|
new CircularDeque<E>(-20);
|
||||||
|
} catch (final IllegalArgumentException ex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the constructor correctly throws an exception.
|
||||||
|
*/
|
||||||
|
public void testConstructorException3() {
|
||||||
|
try {
|
||||||
|
new CircularDeque<E>(null);
|
||||||
|
} catch (final NullPointerException ex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the removal operation using method removeFirstOccurrence() and removeFirstOccurrence().
|
||||||
|
*/
|
||||||
|
public void testRemoveElement() {
|
||||||
|
final List<E> list = new LinkedList<>();
|
||||||
|
list.add((E) "A");
|
||||||
|
list.add((E) "B");
|
||||||
|
list.add((E) "C");
|
||||||
|
list.add((E) "D");
|
||||||
|
list.add((E) "E");
|
||||||
|
final Deque<E> deque = new CircularDeque<>(list);
|
||||||
|
assertEquals(5, ((CircularDeque<E>) deque).maxSize());
|
||||||
|
|
||||||
|
deque.addLast((E) "F");
|
||||||
|
deque.addLast((E) "G");
|
||||||
|
deque.addLast((E) "H");
|
||||||
|
|
||||||
|
assertEquals(false, deque.removeFirstOccurrence("A"));
|
||||||
|
assertEquals(false, deque.removeFirstOccurrence("B"));
|
||||||
|
assertEquals(false, deque.removeLastOccurrence("C"));
|
||||||
|
assertEquals("[D, E, F, G, H]", deque.toString());
|
||||||
|
|
||||||
|
assertEquals(true, deque.removeLastOccurrence("H"));
|
||||||
|
assertEquals("[D, E, F, G]", deque.toString());
|
||||||
|
|
||||||
|
assertEquals(true, deque.removeLastOccurrence("E"));
|
||||||
|
assertEquals("[D, F, G]", deque.toString());
|
||||||
|
|
||||||
|
assertEquals(true, deque.removeLastOccurrence("F"));
|
||||||
|
assertEquals("[D, G]", deque.toString());
|
||||||
|
|
||||||
|
assertEquals("D", deque.removeFirst());
|
||||||
|
assertEquals("G", deque.removeLast());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDescendingIterator() {
|
||||||
|
final List<E> list = new LinkedList<>();
|
||||||
|
list.add((E) "A");
|
||||||
|
list.add((E) "B");
|
||||||
|
list.add((E) "C");
|
||||||
|
list.add((E) "D");
|
||||||
|
list.add((E) "E");
|
||||||
|
final Deque<E> deque = new CircularDeque<>(list);
|
||||||
|
|
||||||
|
final Iterator<E> iterator = deque.descendingIterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
iterator.next();
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
assertTrue(deque.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCompatibilityVersion() {
|
||||||
|
return "4.5";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue