Add CircularDeque

This commit is contained in:
dota17 2019-12-06 16:17:27 +08:00
parent d682042aaa
commit c9500bb6bd
3 changed files with 418 additions and 21 deletions

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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";
}
}