mirror of
https://github.com/apache/commons-collections.git
synced 2025-02-22 18:28:46 +00:00
[COLLECTIONS-433] Improve performance of TreeList#addAll and TreeList(Collection). Thanks to Jeffrey Barnes for the patch.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1470159 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
c143260d76
commit
9bdfac0fad
5
pom.xml
5
pom.xml
@ -124,11 +124,14 @@
|
||||
<name>Federico Barbieri</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Arron Bates</name>
|
||||
<name>Jeffrey Barnes</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Nicola Ken Barozzi</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Arron Bates</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Sebastian Bazley</name>
|
||||
</contributor>
|
||||
|
@ -22,6 +22,10 @@
|
||||
<body>
|
||||
|
||||
<release version="4.0" date="TBA" description="Next release">
|
||||
<action issue="COLLECTIONS-433" dev="tn" type="fix" due-to="Jeffrey Barnes">
|
||||
Improve performance of "TreeList#addAll" and "TreeList(Collection)" by converting
|
||||
the input collection into an AVL tree and efficiently merge it into the existing tree.
|
||||
</action>
|
||||
<action issue="COLLECTIONS-296" dev="tn" type="add" due-to="Julius Davies">
|
||||
Added methods "CollectionUtils#merge(...)" to merge two sorted Collections
|
||||
into a sorted List using the standard O(n) merge algorithm.
|
||||
|
@ -23,6 +23,7 @@ import java.util.Iterator;
|
||||
import java.util.ListIterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.apache.commons.collections4.ArrayStack;
|
||||
import org.apache.commons.collections4.OrderedIterator;
|
||||
|
||||
/**
|
||||
@ -53,6 +54,7 @@ import org.apache.commons.collections4.OrderedIterator;
|
||||
* @since 3.1
|
||||
* @version $Id$
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // replace ArrayStack by ArrayDeque when moving to Java 1.6
|
||||
public class TreeList<E> extends AbstractList<E> {
|
||||
// add; toArray; iterator; insert; get; indexOf; remove
|
||||
// TreeList = 1260;7360;3080; 160; 170;3400; 170;
|
||||
@ -74,14 +76,17 @@ public class TreeList<E> extends AbstractList<E> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new empty list that copies the specified list.
|
||||
* Constructs a new empty list that copies the specified collection.
|
||||
*
|
||||
* @param coll the collection to copy
|
||||
* @throws NullPointerException if the collection is null
|
||||
*/
|
||||
public TreeList(final Collection<E> coll) {
|
||||
super();
|
||||
addAll(coll);
|
||||
if (!coll.isEmpty()) {
|
||||
root = new AVLNode<E>(coll);
|
||||
size = coll.size();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
@ -203,6 +208,29 @@ public class TreeList<E> extends AbstractList<E> {
|
||||
size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends all of the elements in the specified collection to the end of this list,
|
||||
* in the order that they are returned by the specified collection's Iterator.
|
||||
* <p>
|
||||
* This method runs in O(n + log m) time, where m is
|
||||
* the size of this list and n is the size of {@code c}.
|
||||
*
|
||||
* @param c the collection to be added to this list
|
||||
* @return {@code true} if this list changed as a result of the call
|
||||
* @throws NullPointerException {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean addAll(final Collection<? extends E> c) {
|
||||
if (c.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
modCount += c.size();
|
||||
final AVLNode<E> cTree = new AVLNode<E>(c);
|
||||
root = root == null ? cTree : root.addAll(cTree, size);
|
||||
size += c.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the element at the specified index.
|
||||
*
|
||||
@ -294,7 +322,7 @@ public class TreeList<E> extends AbstractList<E> {
|
||||
* Constructs a new node with a relative position.
|
||||
*
|
||||
* @param relativePosition the relative position of the node
|
||||
* @param obj the value for the ndoe
|
||||
* @param obj the value for the node
|
||||
* @param rightFollower the node with the value following this one
|
||||
* @param leftFollower the node with the value leading this one
|
||||
*/
|
||||
@ -308,6 +336,58 @@ public class TreeList<E> extends AbstractList<E> {
|
||||
left = leftFollower;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new AVL tree from a collection.
|
||||
* <p>
|
||||
* The collection must be nonempty.
|
||||
*
|
||||
* @param coll a nonempty collection
|
||||
*/
|
||||
private AVLNode(final Collection<? extends E> coll) {
|
||||
this(coll.iterator(), 0, coll.size() - 1, 0, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new AVL tree from a collection.
|
||||
* <p>
|
||||
* This is a recursive helper for {@link #AVLNode(Collection)}. A call
|
||||
* to this method will construct the subtree for elements {@code start}
|
||||
* through {@code end} of the collection, assuming the iterator
|
||||
* {@code e} already points at element {@code start}.
|
||||
*
|
||||
* @param iterator an iterator over the collection, which should already point
|
||||
* to the element at index {@code start} within the collection
|
||||
* @param start the index of the first element in the collection that
|
||||
* should be in this subtree
|
||||
* @param end the index of the last element in the collection that
|
||||
* should be in this subtree
|
||||
* @param absolutePositionOfParent absolute position of this node's
|
||||
* parent, or 0 if this node is the root
|
||||
* @param prev the {@code AVLNode} corresponding to element (start - 1)
|
||||
* of the collection, or null if start is 0
|
||||
* @param next the {@code AVLNode} corresponding to element (end + 1)
|
||||
* of the collection, or null if end is the last element of the collection
|
||||
*/
|
||||
private AVLNode(final Iterator<? extends E> iterator, final int start, final int end,
|
||||
final int absolutePositionOfParent, final AVLNode<E> prev, final AVLNode<E> next) {
|
||||
final int mid = start + (end - start) / 2;
|
||||
if (start < mid) {
|
||||
left = new AVLNode<E>(iterator, start, mid - 1, mid, prev, this);
|
||||
} else {
|
||||
leftIsPrevious = true;
|
||||
left = prev;
|
||||
}
|
||||
value = iterator.next();
|
||||
relativePosition = mid - absolutePositionOfParent;
|
||||
if (mid < end) {
|
||||
right = new AVLNode<E>(iterator, mid + 1, end, mid, this, next);
|
||||
} else {
|
||||
rightIsNext = true;
|
||||
right = next;
|
||||
}
|
||||
recalcHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value.
|
||||
*
|
||||
@ -716,6 +796,119 @@ public class TreeList<E> extends AbstractList<E> {
|
||||
recalcHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the elements of another tree list to this tree list by efficiently
|
||||
* merging the two AVL trees. This operation is destructive to both trees and
|
||||
* runs in O(log(m + n)) time.
|
||||
*
|
||||
* @param otherTree
|
||||
* the root of the AVL tree to merge with this one
|
||||
* @param currentSize
|
||||
* the number of elements in this AVL tree
|
||||
* @return the root of the new, merged AVL tree
|
||||
*/
|
||||
private AVLNode<E> addAll(AVLNode<E> otherTree, final int currentSize) {
|
||||
final AVLNode<E> maxNode = max();
|
||||
final AVLNode<E> otherTreeMin = otherTree.min();
|
||||
|
||||
// We need to efficiently merge the two AVL trees while keeping them
|
||||
// balanced (or nearly balanced). To do this, we take the shorter
|
||||
// tree and combine it with a similar-height subtree of the taller
|
||||
// tree. There are two symmetric cases:
|
||||
// * this tree is taller, or
|
||||
// * otherTree is taller.
|
||||
if (otherTree.height > height) {
|
||||
// CASE 1: The other tree is taller than this one. We will thus
|
||||
// merge this tree into otherTree.
|
||||
|
||||
// STEP 1: Remove the maximum element from this tree.
|
||||
final AVLNode<E> leftSubTree = removeMax();
|
||||
|
||||
// STEP 2: Navigate left from the root of otherTree until we
|
||||
// find a subtree, s, that is no taller than me. (While we are
|
||||
// navigating left, we store the nodes we encounter in a stack
|
||||
// so that we can re-balance them in step 4.)
|
||||
final ArrayStack<AVLNode<E>> sAncestors = new ArrayStack<AVLNode<E>>();
|
||||
AVLNode<E> s = otherTree;
|
||||
int sAbsolutePosition = s.relativePosition + currentSize;
|
||||
int sParentAbsolutePosition = 0;
|
||||
while (s != null && s.height > getHeight(leftSubTree)) {
|
||||
sParentAbsolutePosition = sAbsolutePosition;
|
||||
sAncestors.push(s);
|
||||
s = s.left;
|
||||
if (s != null) {
|
||||
sAbsolutePosition += s.relativePosition;
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 3: Replace s with a newly constructed subtree whose root
|
||||
// is maxNode, whose left subtree is leftSubTree, and whose right
|
||||
// subtree is s.
|
||||
maxNode.setLeft(leftSubTree, null);
|
||||
maxNode.setRight(s, otherTreeMin);
|
||||
if (leftSubTree != null) {
|
||||
leftSubTree.max().setRight(null, maxNode);
|
||||
leftSubTree.relativePosition -= currentSize - 1;
|
||||
}
|
||||
if (s != null) {
|
||||
s.min().setLeft(null, maxNode);
|
||||
s.relativePosition = sAbsolutePosition - currentSize + 1;
|
||||
}
|
||||
maxNode.relativePosition = currentSize - 1 - sParentAbsolutePosition;
|
||||
otherTree.relativePosition += currentSize;
|
||||
|
||||
// STEP 4: Re-balance the tree and recalculate the heights of s's ancestors.
|
||||
s = maxNode;
|
||||
while (!sAncestors.isEmpty()) {
|
||||
final AVLNode<E> sAncestor = sAncestors.pop();
|
||||
sAncestor.setLeft(s, null);
|
||||
s = sAncestor.balance();
|
||||
}
|
||||
return s;
|
||||
} else {
|
||||
// CASE 2: This tree is taller. This is symmetric to case 1.
|
||||
// We merge otherTree into this tree by finding a subtree s of this
|
||||
// tree that is of similar height to otherTree and replacing it
|
||||
// with a new subtree whose root is otherTreeMin and whose
|
||||
// children are otherTree and s.
|
||||
|
||||
otherTree = otherTree.removeMin();
|
||||
|
||||
final ArrayStack<AVLNode<E>> sAncestors = new ArrayStack<AVLNode<E>>();
|
||||
AVLNode<E> s = this;
|
||||
int sAbsolutePosition = s.relativePosition;
|
||||
int sParentAbsolutePosition = 0;
|
||||
while (s != null && s.height > getHeight(otherTree)) {
|
||||
sParentAbsolutePosition = sAbsolutePosition;
|
||||
sAncestors.push(s);
|
||||
s = s.right;
|
||||
if (s != null) {
|
||||
sAbsolutePosition += s.relativePosition;
|
||||
}
|
||||
}
|
||||
|
||||
otherTreeMin.setRight(otherTree, null);
|
||||
otherTreeMin.setLeft(s, maxNode);
|
||||
if (otherTree != null) {
|
||||
otherTree.min().setLeft(null, otherTreeMin);
|
||||
otherTree.relativePosition++;
|
||||
}
|
||||
if (s != null) {
|
||||
s.max().setRight(null, otherTreeMin);
|
||||
s.relativePosition = sAbsolutePosition - currentSize;
|
||||
}
|
||||
otherTreeMin.relativePosition = currentSize - sParentAbsolutePosition;
|
||||
|
||||
s = otherTreeMin;
|
||||
while (!sAncestors.isEmpty()) {
|
||||
final AVLNode<E> sAncestor = sAncestors.pop();
|
||||
sAncestor.setRight(s, null);
|
||||
s = sAncestor.balance();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// private void checkFaedelung() {
|
||||
// AVLNode maxNode = left.max();
|
||||
// if (!maxNode.rightIsFaedelung || maxNode.right != this) {
|
||||
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
package org.apache.commons.collections4.list;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
@ -265,6 +266,66 @@ public class TreeListTest<E> extends AbstractListTest<E> {
|
||||
// previous() after remove() should move to
|
||||
// the element before the one just removed
|
||||
assertEquals("A", li.previous());
|
||||
}
|
||||
}
|
||||
|
||||
public void testIterationOrder() {
|
||||
// COLLECTIONS-433:
|
||||
// ensure that the iteration order of elements is correct
|
||||
// when initializing the TreeList with another collection
|
||||
|
||||
for (int size = 1; size < 1000; size++) {
|
||||
List<Integer> other = new ArrayList<Integer>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
other.add(i);
|
||||
}
|
||||
TreeList<Integer> l = new TreeList<Integer>(other);
|
||||
ListIterator<Integer> it = l.listIterator();
|
||||
int i = 0;
|
||||
while (it.hasNext()) {
|
||||
Integer val = it.next();
|
||||
assertEquals(i++, val.intValue());
|
||||
}
|
||||
|
||||
while (it.hasPrevious()) {
|
||||
Integer val = it.previous();
|
||||
assertEquals(--i, val.intValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testIterationOrderAfterAddAll() {
|
||||
// COLLECTIONS-433:
|
||||
// ensure that the iteration order of elements is correct
|
||||
// when calling addAll on the TreeList
|
||||
|
||||
// to simulate different cases in addAll, do different runs where
|
||||
// the number of elements already in the list and being added by addAll differ
|
||||
|
||||
int size = 1000;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
List<Integer> other = new ArrayList<Integer>(size);
|
||||
for (int j = i; j < size; j++) {
|
||||
other.add(j);
|
||||
}
|
||||
TreeList<Integer> l = new TreeList<Integer>();
|
||||
for (int j = 0; j < i; j++) {
|
||||
l.add(j);
|
||||
}
|
||||
|
||||
l.addAll(other);
|
||||
|
||||
ListIterator<Integer> it = l.listIterator();
|
||||
int cnt = 0;
|
||||
while (it.hasNext()) {
|
||||
Integer val = it.next();
|
||||
assertEquals(cnt++, val.intValue());
|
||||
}
|
||||
|
||||
while (it.hasPrevious()) {
|
||||
Integer val = it.previous();
|
||||
assertEquals(--cnt, val.intValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user