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.ObjectOutputStream;
|
||||
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
|
||||
* element if full.
|
||||
|
@ -99,7 +107,13 @@ public class CircularDeque<E> extends AbstractCollection<E>
|
|||
* @throws NullPointerException if the collection is null
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -167,7 +181,7 @@ public class CircularDeque<E> extends AbstractCollection<E>
|
|||
* @param index the index to increment
|
||||
* @return the updated index
|
||||
*/
|
||||
private int indexIncrease(int index) {
|
||||
private int increaseIndex(int index) {
|
||||
index++;
|
||||
if (index >= maxElements) {
|
||||
index = 0;
|
||||
|
@ -336,58 +350,138 @@ public class CircularDeque<E> extends AbstractCollection<E>
|
|||
|
||||
@Override
|
||||
public boolean removeFirstOccurrence(final Object o) {
|
||||
int mask = elements.length - 1;
|
||||
int i = head;
|
||||
Object x;
|
||||
while ((x = elements[i]) != null) {
|
||||
if (Objects.equals(o,x)) {
|
||||
while (true) {
|
||||
if (Objects.equals(o,elements[i])) {
|
||||
delete(i);
|
||||
ArrayDeque;
|
||||
return true;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
if (i == decreaseIndex(tail)) {
|
||||
return false;
|
||||
}
|
||||
i = increaseIndex(i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
public boolean offer(final E e) {
|
||||
return false;
|
||||
return offerLast(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E remove() {
|
||||
return null;
|
||||
return removeFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E poll() {
|
||||
return null;
|
||||
return pollFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E element() {
|
||||
return null;
|
||||
return getFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E peek() {
|
||||
return null;
|
||||
return peekFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(final E e) {
|
||||
|
||||
addFirst(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
public int maxSize() {
|
||||
return 0;
|
||||
return maxElements;
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
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