kde-extraapps/kdevplatform/util/embeddedfreetree.h

911 lines
43 KiB
C
Raw Normal View History

/* This file is part of KDevelop
Copyright 2008 David Nolden <david.nolden.kdevelop@art-master.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef EMBEDDED_FREE_TREE
#define EMBEDDED_FREE_TREE
#include <sys/types.h>
#include <limits>
#include <stdlib.h>
#include <QtCore/QPair>
#include "kdevvarlengtharray.h"
#include <iostream>
//Uncomment this to search for tree-inconsistencies, however it's very expensive
// #define DEBUG_FREEITEM_COUNT debugFreeItemCount(); verifyTreeConsistent(*m_centralFreeItem, 0, m_itemCount);
#define DEBUG_FREEITEM_COUNT
/**
* This file implements algorithms that allow managing a sorted list of items, and managing "free" items
* for reuse efficiently in that list. Among those free items a tree is built, and they are traversed
* on insertion/removal to manage free items in the tree.
*
* There is specific needs on the embedded items:
* - They must be markable "invalid", so after they are deleted they can stay in their place as invalid items.
* - While they are invalid, they still must be able to hold 2 integers, needed for managing the tree of free items.
* - One integer is needed for each list to hold a pointer to the central free item.
*
* Only these functions must be used to manipulate the lists, from the beginning up. First create an empty list
* and initialize centralFreeItem with -1, and then you start adding items.
*
* Since the list is sorted, and each item can be contained only once, these lists actually represent a set.
*
* EmbeddedTreeAlgorithms implements an efficient "contains" function that uses binary search within the list.
*/
namespace KDevelop {
///Responsible for handling the items in the list
///This is an example. ItemHandler::rightChild(..) and ItemHandler::leftChild(..) must be values that must be able to hold the count of positive
///values that will be the maximum size of the list, and additionally -1.
// template<class Data>
// class ExampleItemHandler {
// public:
// ExampleItemHandler(const Data& data) : m_data(data) {
// }
// int ItemHandler::rightChild() const {
// Q_ASSERT(0);
// }
// int ItemHandler::leftChild() const {
// Q_ASSERT(0);
// }
// void ItemHandler::setLeftChild(int child) {
// Q_ASSERT(0);
// }
// void setRightChild(int child) {
// Q_ASSERT(0);
// }
// bool operator<(const StandardItemHandler& rhs) const {
// Q_ASSERT(0);
// }
// //Copies this item into the given one
// void copyTo(Data& data) const {
// data = m_data;
// }
//
// static void createFreeItem(Data& data) {
// data = Data();
// }
//
// bool isFree() const {
// Q_ASSERT(0);
// }
//
// const Data& data() {
// }
//
// private:
// const Data& m_data;
// };
/**
* Use this for several constant algorithms on sorted lists with free-trees
* */
template<class Data, class ItemHandler>
class EmbeddedTreeAlgorithms {
public:
EmbeddedTreeAlgorithms(const Data* items, uint itemCount, const int& centralFreeItem) : m_items(items), m_itemCount(itemCount), m_centralFreeItem(&centralFreeItem) {
}
~EmbeddedTreeAlgorithms() {
}
///Efficiently checks whether the item is contained in the set.
///If it is contained, returns the index. Else, returns -1.
int indexOf(const Data& data) {
return indexOf(data, 0, m_itemCount);
}
///Searches the given item within the specified bounds.
int indexOf(const Data& data, uint start, uint end) {
while(1) {
if(start >= end)
return -1;
int center = (start + end)/2;
//Skip free items, since they cannot be used for ordering
for(; center < (int)end; ) {
if(!ItemHandler::isFree(m_items[center]))
break;
++center;
}
if(center == (int)end) {
end = (start + end)/2; //No non-free items found in second half, so continue search in the other
}else{
if(ItemHandler::equals(data, m_items[center])) {
return center;
}else if(data < m_items[center]) {
end = (start + end)/2;
}else{
//Continue search in second half
start = center+1;
}
}
}
}
///Returns the first valid index that has a data-value larger or equal to @param data.
///Returns -1 if nothing is found.
int lowerBound(const Data& data, int start, int end) {
int currentBound = -1;
while(1) {
if(start >= end)
return currentBound;
int center = (start + end)/2;
//Skip free items, since they cannot be used for ordering
for(; center < end; ) {
if(!ItemHandler::isFree(m_items[center]))
break;
++center;
}
if(center == end) {
end = (start + end)/2; //No non-free items found in second half, so continue search in the other
}else{
if(ItemHandler::equals(data, m_items[center])) {
return center;
}else if(data < m_items[center]) {
currentBound = center;
end = (start + end)/2;
}else{
//Continue search in second half
start = center+1;
}
}
}
}
uint countFreeItems() const {
return countFreeItemsInternal(*m_centralFreeItem);
}
uint countFreeItemsNaive() const {
uint ret = 0;
for(uint a = 0; a < m_itemCount; ++a) {
if(ItemHandler::isFree(m_items[a]))
++ret;
}
return ret;
}
void verifyOrder() {
Data last;
for(uint a = 0; a < m_itemCount; ++a) {
if(!ItemHandler::isFree(m_items[a])) {
if(!ItemHandler::isFree(last))
Q_ASSERT(last < m_items[a]);
last = m_items[a];
}
}
}
void verifyTreeConsistent() {
verifyTreeConsistentInternal(*m_centralFreeItem, 0, m_itemCount);
Q_ASSERT(countFreeItems() == countFreeItemsNaive());
}
private:
void verifyTreeConsistentInternal(int position, int lowerBound, int upperBound) {
if(position == -1)
return;
Q_ASSERT(lowerBound <= position && position < upperBound);
verifyTreeConsistentInternal(ItemHandler::leftChild(m_items[position]), lowerBound, position);
verifyTreeConsistentInternal(ItemHandler::rightChild(m_items[position]), position+1, upperBound);
}
uint countFreeItemsInternal(int item) const {
if(item == -1)
return 0;
return 1 + countFreeItemsInternal(ItemHandler::leftChild(m_items[item])) + countFreeItemsInternal(ItemHandler::rightChild(m_items[item]));
}
const Data* m_items;
uint m_itemCount;
const int* m_centralFreeItem;
};
/**Use this to add items.
* The added item must not be in the set yet!
* General usage:
* - Construct the object
* - Check if newItemCount() equals the previous item-count. If not, construct
* a new list as large as newItemCount, and call object.transferData to transfer the data
* into the new list. The new size must match the returned newItemCount.
* - Either call object.apply(), or let it be called automatically by the destructor.
* @param increaseFraction By what fraction the list is increased when it needs to. For example the size will be increased by 1/5 if it's 5.
* @param rebuildIfInsertionMoreExpensive The structure is rebuilt completely when an insertion needs a moving around of more than rebuildIfInsertionMoreExpensive times
the count of items needed to be moved in worst case in a fresh tree.
* After rebuilding the tree, the free space is evenly distributed, and thus insertions require much less moving.
* */
template<class Data, class ItemHandler, int increaseFraction = 5, int rebuildIfInsertionMoreExpensive = 20>
class EmbeddedTreeAddItem {
public:
EmbeddedTreeAddItem(Data* items, uint itemCount, int& centralFreeItem, const Data& add) : m_add(add), m_items(items), m_itemCount(itemCount), m_centralFreeItem(&centralFreeItem), m_applied(false), m_needResize(false) {
m_needResize = !apply();
}
~EmbeddedTreeAddItem() {
if(!m_applied)
apply(true);
}
///Check this to see whether a new item-count is needed. If this does not equal the given itemCount, then
///the data needs to be transferred into a new list using transferData
uint newItemCount() const {
if(!m_applied) {
if(*m_centralFreeItem == -1) {
uint realItemCount = m_itemCount - countFreeItems(*m_centralFreeItem);
uint newItemCount = realItemCount + (realItemCount/increaseFraction);
if(newItemCount <= m_itemCount)
newItemCount = m_itemCount+1;
return newItemCount;
}else if(m_needResize) {
uint realItemCount = m_itemCount - countFreeItems(*m_centralFreeItem);
uint newItemCount = realItemCount + (realItemCount/increaseFraction);
return newItemCount;
}
}
return m_itemCount;
}
///Transfers the data into a new item-list. The size of the new item-list must equal newItemCount()
void transferData(Data* newItems, uint newCount, int* newCentralFree = 0) {
DEBUG_FREEITEM_COUNT
uint currentRealCount = m_itemCount - countFreeItems(*m_centralFreeItem);
// Q_ASSERT(currentRealCount + (currentRealCount/increaseFraction) == newCount);
//Create a new list where the items from m_items are put into newItems, with the free items evenly
//distributed, and a clean balanced free-tree.
uint newFreeCount = newCount - currentRealCount;
volatile uint freeItemRaster;
if(newFreeCount)
freeItemRaster = newCount / newFreeCount;
else {
freeItemRaster = newCount+1; //No free items
}
///@todo Do not iterate through all items, instead use the free-tree and memcpy for the ranges between free items.
///Ideally, even the free-tree would be built on-the-fly.
Q_ASSERT(freeItemRaster);
uint offset = 0;
uint insertedValidCount = 0;
for(uint a = 0; a < newCount; ++a) {
//Create new free items at the end of their raster range
if(a % freeItemRaster == (freeItemRaster-1)) {
//We need to insert a free item
ItemHandler::createFreeItem(newItems[a]);
++offset;
}else{
++insertedValidCount;
while(ItemHandler::isFree(m_items[a-offset]) && a-offset < m_itemCount)
--offset;
Q_ASSERT(a-offset < m_itemCount);
newItems[a] = m_items[a-offset];
// Q_ASSERT(!ItemHandler::isFree(newItems[a]));
}
}
Q_ASSERT(insertedValidCount == m_itemCount - countFreeItems(*m_centralFreeItem));
// kDebug() << m_itemCount << newCount << offset;
// Q_ASSERT(m_itemCount == newCount-offset);
m_items = newItems;
m_itemCount = newCount;
if(newCentralFree)
m_centralFreeItem = newCentralFree;
*m_centralFreeItem = buildFreeTree(newFreeCount, freeItemRaster, freeItemRaster-1);
// kDebug() << "count of new free items:" << newFreeCount;
// Q_ASSERT(countFreeItems( *m_centralFreeItem ) == newFreeCount);
DEBUG_FREEITEM_COUNT
}
///Tries to put the item into the list. If the insertion would be too inefficient or is not possible, returns false, unless @param force is true
bool apply(bool force = false) {
if(m_applied)
return true;
if(*m_centralFreeItem == -1) {
Q_ASSERT(!force);
return false;
}
//Find the free item that is nearest to the target position in the item order
int previousItem = -1;
int currentItem = *m_centralFreeItem;
int replaceCurrentWith = -1;
//In currentLowerBound and currentUpperBound, we count the smallest contiguous range between free
//items that the added items needs to be sorted into. If the range is empty, the item can just be inserted.
int currentLowerBound = 0;
int currentUpperBound = m_itemCount;
//Now go down the chain, always into the items direction
while(1) {
QPair<int, int> freeBounds = leftAndRightRealItems(currentItem);
const Data& current(m_items[currentItem]);
if(freeBounds.first != -1 && m_add < m_items[freeBounds.first]) {
//Follow left child
currentUpperBound = freeBounds.first+1;
if(ItemHandler::leftChild(current) != -1) {
//Continue traversing
previousItem = currentItem;
currentItem = ItemHandler::leftChild(current);
}else{
replaceCurrentWith = ItemHandler::rightChild(current);
break;
}
}else if(freeBounds.second != -1 && m_items[freeBounds.second] < m_add) {
//Follow right child
currentLowerBound = freeBounds.second;
if(ItemHandler::rightChild(current) != -1) {
//Continue traversing
previousItem = currentItem;
currentItem = ItemHandler::rightChild(current);
}else{
replaceCurrentWith = ItemHandler::leftChild(current);
break;
}
}else{
//We will use this item! So find a replacement for it in the tree, and update the structure
force = true;
currentLowerBound = currentUpperBound = currentItem;
int leftReplaceCandidate = -1, rightReplaceCandidate = -1;
if(ItemHandler::leftChild(current) != -1)
leftReplaceCandidate = rightMostChild(ItemHandler::leftChild(current));
if(ItemHandler::rightChild(current) != -1)
rightReplaceCandidate = leftMostChild(ItemHandler::rightChild(current));
///@todo it's probably better using lowerBound and upperBound like in the "remove" version
//Left and right bounds of all children of current
int leftChildBound = leftMostChild(currentItem), rightChildBound = rightMostChild(currentItem);
Q_ASSERT(leftChildBound != -1 && rightChildBound != -1);
int childCenter = (leftChildBound + rightChildBound)/2;
if(leftReplaceCandidate == -1 && rightReplaceCandidate == -1) {
//We don't have a replace candidate, since there is no children
Q_ASSERT(ItemHandler::leftChild(current) == -1);
Q_ASSERT(ItemHandler::rightChild(current) == -1);
}else if(rightReplaceCandidate == -1 || abs(leftReplaceCandidate - childCenter) < abs(rightReplaceCandidate - childCenter)) {
//pick the left replacement, since it's more central
Q_ASSERT(leftReplaceCandidate != -1);
replaceCurrentWith = leftReplaceCandidate;
Data& replaceWith(m_items[replaceCurrentWith]);
if(replaceCurrentWith == ItemHandler::leftChild(current)) {
//The left child of replaceWith can just stay as it is, and we just need to add the right child
Q_ASSERT(ItemHandler::rightChild(replaceWith) == -1);
}else{
takeRightMostChild(ItemHandler::leftChild(current));
//Since we'll be clearing the item, we have to put this childsomewhere else.
// Either make it our new "left" child, or make it the new left children "rightmost" child.
int addRightMostLeftChild = ItemHandler::leftChild(replaceWith);
ItemHandler::setLeftChild(replaceWith, -1);
Q_ASSERT(ItemHandler::leftChild(replaceWith) == -1);
Q_ASSERT(ItemHandler::rightChild(replaceWith) == -1);
if(ItemHandler::leftChild(current) != -1)
{
Q_ASSERT(rightMostChild(ItemHandler::leftChild(current)) != replaceCurrentWith);
Q_ASSERT(ItemHandler::leftChild(current) == -1 || ItemHandler::leftChild(current) < replaceCurrentWith);
ItemHandler::setLeftChild(replaceWith, ItemHandler::leftChild(current));
if(addRightMostLeftChild != -1) {
int rightMostLeft = rightMostChild(ItemHandler::leftChild(replaceWith));
Q_ASSERT(rightMostLeft != -1);
// Q_ASSERT(item(rightMostLeft).ItemHandler::rightChild() == -1);
Q_ASSERT(rightMostLeft < addRightMostLeftChild);
ItemHandler::setRightChild(m_items[rightMostLeft], addRightMostLeftChild);
}
}else{
Q_ASSERT(addRightMostLeftChild == -1 || addRightMostLeftChild < replaceCurrentWith);
ItemHandler::setLeftChild(replaceWith, addRightMostLeftChild);
}
}
Q_ASSERT(ItemHandler::rightChild(current) == -1 || replaceCurrentWith < ItemHandler::rightChild(current));
ItemHandler::setRightChild(replaceWith, ItemHandler::rightChild(current));
}else{
//pick the right replacement, since it's more central
Q_ASSERT(rightReplaceCandidate != -1);
replaceCurrentWith = rightReplaceCandidate;
Data& replaceWith(m_items[replaceCurrentWith]);
if(replaceCurrentWith == ItemHandler::rightChild(current)) {
//The right child of replaceWith can just stay as it is, and we just need to add the left child
Q_ASSERT(ItemHandler::leftChild(replaceWith) == -1);
}else{
takeLeftMostChild(ItemHandler::rightChild(current));
//Since we'll be clearing the item, we have to put this childsomewhere else.
// Either make it our new "right" child, or make it the new right children "leftmost" child.
int addLeftMostRightChild = ItemHandler::rightChild(replaceWith);
ItemHandler::setRightChild(replaceWith, -1);
Q_ASSERT(ItemHandler::rightChild(replaceWith) == -1);
Q_ASSERT(ItemHandler::leftChild(replaceWith) == -1);
if(ItemHandler::rightChild(current) != -1)
{
Q_ASSERT(leftMostChild(ItemHandler::rightChild(current)) != replaceCurrentWith);
Q_ASSERT(ItemHandler::rightChild(current) == -1 || replaceCurrentWith < ItemHandler::rightChild(current));
ItemHandler::setRightChild(replaceWith, ItemHandler::rightChild(current));
if(addLeftMostRightChild != -1) {
int leftMostRight = leftMostChild(ItemHandler::rightChild(replaceWith));
Q_ASSERT(leftMostRight != -1);
Q_ASSERT(ItemHandler::leftChild(m_items[leftMostRight]) == -1);
Q_ASSERT(addLeftMostRightChild < leftMostRight);
ItemHandler::setLeftChild(m_items[leftMostRight], addLeftMostRightChild);
}
}else{
Q_ASSERT(addLeftMostRightChild == -1 || replaceCurrentWith < addLeftMostRightChild);
ItemHandler::setRightChild(replaceWith, addLeftMostRightChild);
}
}
Q_ASSERT(ItemHandler::leftChild(current) == -1 || ItemHandler::leftChild(current) < replaceCurrentWith);
ItemHandler::setLeftChild(replaceWith, ItemHandler::leftChild(current));
}
break;
}
}
//We can insert now
//currentItem and previousItem are the two items that best enclose the target item
// for(int a = currentLowerBound; a < currentUpperBound; ++a) {
// Q_ASSERT(!ItemHandler::isFree(m_items[a]));
// }
Q_ASSERT(currentItem < currentLowerBound || currentItem >= currentUpperBound);
//If the current item is on a border of the bounds, it needs to be inserted in the right position.
//Else, the current position already is right, and we only need to copy it in.
if(currentLowerBound < currentUpperBound && (currentItem == currentLowerBound-1 || currentItem == currentUpperBound)) {
if(!insertSorted(m_add, currentItem, currentLowerBound, currentUpperBound, force)) {
return false;
}
}else{
ItemHandler::copyTo(m_add, m_items[currentItem]);
}
m_applied = true;
//First, take currentItem out of the chain, by replacing it with current.rightChild in the parent
if(previousItem != -1) {
Data& previous(m_items[previousItem]);
if(ItemHandler::leftChild(previous) == currentItem) {
Q_ASSERT(replaceCurrentWith == -1 || replaceCurrentWith < previousItem);
ItemHandler::setLeftChild(previous, replaceCurrentWith);
} else if(ItemHandler::rightChild(previous) == currentItem) {
Q_ASSERT(replaceCurrentWith == -1 || previousItem < replaceCurrentWith);
ItemHandler::setRightChild(previous, replaceCurrentWith);
} else {
Q_ASSERT(0);
}
} else {
*m_centralFreeItem = replaceCurrentWith;
}
return true;
DEBUG_FREEITEM_COUNT
}
private:
void verifyTreeConsistent(int position, int lowerBound, int upperBound) {
if(position == -1)
return;
Q_ASSERT(lowerBound <= position && position < upperBound);
verifyTreeConsistent(ItemHandler::leftChild(m_items[position]), lowerBound, position);
verifyTreeConsistent(ItemHandler::rightChild(m_items[position]), position+1, upperBound);
}
void debugFreeItemCount() {
uint count = 0;
for(uint a = 0; a < m_itemCount; ++a) {
if(isFree(m_items[a]))
++count;
}
uint counted = countFreeItems(*m_centralFreeItem);
Q_ASSERT(count == counted);
Q_UNUSED(counted);
}
QPair<int, int> leftAndRightRealItems(int pos) {
Q_ASSERT(ItemHandler::isFree(m_items[pos]));
int left = -1, right = -1;
for(int a = pos-1; a >= 0; --a) {
if(!ItemHandler::isFree(m_items[a])) {
left = a;
break;
}
}
for(uint a = pos+1; a < m_itemCount; ++a) {
if(!ItemHandler::isFree(m_items[a])) {
right = a;
break;
}
}
return qMakePair(left, right);
}
int buildFreeTree(int count, uint raster, int start) {
Q_ASSERT((start % raster) == (raster-1));
Q_ASSERT(count != 0);
Q_ASSERT(count <= (int)m_itemCount);
if(count == 1) {
ItemHandler::createFreeItem(m_items[start]);
return start;
}else{
int central = start + (count / 2) * raster;
int leftCount = count / 2;
int midCount = 1;
int rightCount = count - leftCount - midCount;
Q_ASSERT(leftCount + midCount <= count);
ItemHandler::createFreeItem(m_items[central]);
Q_ASSERT(ItemHandler::isFree(m_items[central]));
int leftFreeTree = buildFreeTree(leftCount, raster, start);
Q_ASSERT(leftFreeTree == -1 || leftFreeTree < central);
ItemHandler::setLeftChild(m_items[central], leftFreeTree );
if(rightCount > 0) {
int rightFreeTree = buildFreeTree(rightCount, raster, central+raster);
Q_ASSERT(rightFreeTree == -1 || central < rightFreeTree);
ItemHandler::setRightChild(m_items[central], rightFreeTree );
}
return central;
}
}
uint countFreeItems(int item) const {
if(item == -1)
return 0;
const Data& current(m_items[item]);
return 1 + countFreeItems(ItemHandler::leftChild(current)) + countFreeItems(ItemHandler::rightChild(current));
}
int leftMostChild(int pos) const {
while(1) {
if(ItemHandler::leftChild(m_items[pos]) != -1)
pos = ItemHandler::leftChild(m_items[pos]);
else
return pos;
}
}
int takeLeftMostChild(int pos) const {
int parent = -1;
while(1) {
if(ItemHandler::leftChild(m_items[pos]) != -1) {
parent = pos;
pos = ItemHandler::leftChild(m_items[pos]);
} else {
ItemHandler::setLeftChild(m_items[parent], -1);
return pos;
}
}
}
int rightMostChild(int pos) const {
while(1) {
if(ItemHandler::rightChild(m_items[pos]) != -1)
pos = ItemHandler::rightChild(m_items[pos]);
else
return pos;
}
}
int takeRightMostChild(int pos) const {
int parent = -1;
while(1) {
if(ItemHandler::rightChild(m_items[pos]) != -1) {
parent = pos;
pos = ItemHandler::rightChild(m_items[pos]);
} else {
ItemHandler::setRightChild(m_items[parent], -1);
return pos;
}
}
}
void insertBubbleSorted(const Data& data, int pos) {
//Since we don't know how the target is enclosed, just do naive bubble sort
ItemHandler::copyTo(data, m_items[pos]);
while(1) {
int prev = pos-1;
int next = pos+1;
if(prev >= 0 && !ItemHandler::isFree(m_items[prev]) && m_items[pos] < m_items[prev]) {
Data backup(m_items[prev]);
m_items[prev] = m_items[pos];
m_items[pos] = backup;
pos = prev;
}else if(next < m_itemCount && !ItemHandler::isFree(m_items[next]) && m_items[next] < m_items[pos]) {
Data backup(m_items[next]);
m_items[next] = m_items[pos];
m_items[pos] = backup;
pos = next;
}else{
break;
}
}
}
///Maximum "moving" out of the way of items without forcing a complete rebuild of the list
inline int maxMoveAround() const {
return increaseFraction * rebuildIfInsertionMoreExpensive;
}
///Inserts the given data item into position pos, and updates the sorting
///@param otherBound can be another empty item, that together with @param pos represents the closest enclosure of the target position
///@return Whether the item could be inserted. It is not inserted if
bool insertSorted(const Data& data, int pos, int start, int end, bool force) {
if(pos < start)
start = pos;
if(pos >= end)
end = pos+1;
/* for(int a = start; a < end; ++a) {
if(a != pos) {
Q_ASSERT(!ItemHandler::isFree(m_items[a]));
}
}*/
EmbeddedTreeAlgorithms<Data, ItemHandler> alg(m_items, m_itemCount, *m_centralFreeItem);
int bound = alg.lowerBound(data, start, end);
//Now find the position that should be taken
if(bound == -1)
bound = end;
//Now our item should end up right before bound
int target;
//bound cannot be pos, because pos is invalid
Q_ASSERT(bound != pos);
//Shuffle around the item at the free pos, so reference counting in constructors/destructors is not screwed up
char backup[sizeof(Data)];
memcpy(backup, m_items+pos, sizeof(Data));
if(bound < pos) {
if(!force && pos-bound > maxMoveAround()) {
// kDebug() << "increasing because" << pos-bound << ">" << maxMoveAround() << "left free items:" << countFreeItems(*m_centralFreeItem) << "target free items:" << (m_itemCount-countFreeItems(*m_centralFreeItem))/increaseFraction;
return false;
}
//Move [bound, pos) one to right, and insert at bound
memmove(m_items+bound+1, m_items+bound, sizeof(Data)*(pos-bound));
target = bound;
}else {
Q_ASSERT(bound > pos);
if(!force && bound-pos-1 > maxMoveAround()) {
// kDebug() << "increasing because" << bound-pos-1 << ">" << maxMoveAround() << "left free items:" << countFreeItems(*m_centralFreeItem)<< "target free items:" << (m_itemCount-countFreeItems(*m_centralFreeItem))/increaseFraction;
return false;
}
//Move (pos, bound) one to left, and insert at bound-1
memmove(m_items+pos, m_items+pos+1, sizeof(Data)*(bound-pos-1));
target = bound-1;
}
memcpy(m_items+target, backup, sizeof(Data));
ItemHandler::copyTo(data, m_items[target]);
return true;
}
const Data& m_add;
Data* m_items;
uint m_itemCount;
int* m_centralFreeItem;
bool m_applied, m_needResize;
};
/**Use this to add items.
* The removed item must be in the set!
* General usage:
* - Construct the object
* - Check if newItemCount() equals the previous item-count. If not, construct
* a new list as large as newItemCount, and call object.transferData to transfer the data
* into the new list. The new size must match the returned newItemCount.
* However this may also be ignored if the memory-saving is not wanted in that moment.
* */
template<class Data, class ItemHandler, int increaseFraction = 5 >
class EmbeddedTreeRemoveItem {
public:
EmbeddedTreeRemoveItem(Data* items, uint itemCount, int& centralFreeItem, const Data& remove) : m_remove(remove), m_items(items), m_itemCount(itemCount), m_centralFreeItem(&centralFreeItem), m_insertedAtDepth(0) {
apply();
}
~EmbeddedTreeRemoveItem() {
}
///Check this to see whether a new item-count is needed. If this does not equal the given itemCount, then
///the data needs to be transferred into a new list using transferData
uint newItemCount() const {
uint maxFreeItems = ((m_itemCount / increaseFraction)*3)/2 + 1;
//First we approximate the count of free items using the insertion depth
if((1u << m_insertedAtDepth) >= maxFreeItems) {
uint freeCount = countFreeItems(*m_centralFreeItem);
if(freeCount > maxFreeItems || freeCount == m_itemCount) {
return m_itemCount - freeCount;
}
}
return m_itemCount;
}
///Transfers the data into a new item-list. The size of the new item-list must equal newItemCount()
void transferData(Data* newItems, uint newCount, int* newCentralFree = 0) {
DEBUG_FREEITEM_COUNT
//We only transfer into a new list when all the free items are used up
//Create a list where only the non-free items exist
uint offset = 0;
for(uint a = 0; a < m_itemCount; ++a) {
if(!ItemHandler::isFree(m_items[a])) {
newItems[offset] = m_items[a];
++offset;
}
}
Q_ASSERT(offset == newCount);
if(newCentralFree)
m_centralFreeItem = newCentralFree;
*m_centralFreeItem = -1;
m_items = newItems;
m_itemCount = newCount;
DEBUG_FREEITEM_COUNT
}
private:
void verifyTreeConsistent(int position, int lowerBound, int upperBound) {
if(position == -1)
return;
Q_ASSERT(lowerBound <= position && position < upperBound);
verifyTreeConsistent(ItemHandler::leftChild(m_items[position]), lowerBound, position);
verifyTreeConsistent(ItemHandler::rightChild(m_items[position]), position+1, upperBound);
}
uint countFreeItems(int item) const {
if(item == -1)
return 0;
const Data& current(m_items[item]);
return 1 + countFreeItems(ItemHandler::leftChild(current)) + countFreeItems(ItemHandler::rightChild(current));
}
int findItem(const Data& data, uint start, uint end) {
EmbeddedTreeAlgorithms<Data, ItemHandler> alg(m_items, m_itemCount, *m_centralFreeItem);
return alg.indexOf(data, start, end);
}
void apply() {
DEBUG_FREEITEM_COUNT
int removeIndex = findItem(m_remove, 0, m_itemCount);
Q_ASSERT(removeIndex != -1);
Q_ASSERT(!ItemHandler::isFree(m_items[removeIndex]));
//Find the free item that is nearest to the target position in the item order
int currentItem = *m_centralFreeItem;
int lowerBound = 0; //The minimum position the searched item can have
int upperBound = m_itemCount; //The lowest known position the searched item can _not_ have
if(*m_centralFreeItem == -1) {
*m_centralFreeItem = removeIndex;
Q_ASSERT(*m_centralFreeItem != -1);
ItemHandler::createFreeItem(m_items[*m_centralFreeItem]);
return;
}
//Now go down the chain, always into the items direction
///@todo make the structure better: Don't just put left/right child, but also swap when neede
/// to balance the tree
while(1) {
Q_ASSERT(removeIndex != currentItem);
Data& current(m_items[currentItem]);
++m_insertedAtDepth;
if(removeIndex < currentItem) {
upperBound = currentItem;
//Follow left child
if(ItemHandler::leftChild(current) != -1) {
//Continue traversing
currentItem = ItemHandler::leftChild(current);
Q_ASSERT(currentItem >= lowerBound && currentItem < upperBound);
}else{
//The to-be deleted item is before current, and can be added as left child to current
int item = findItem(m_remove, lowerBound, upperBound);
Q_ASSERT(item == removeIndex);
ItemHandler::createFreeItem(m_items[item]);
Q_ASSERT(item == -1 || item < currentItem);
ItemHandler::setLeftChild(current, item);
Q_ASSERT(item >= lowerBound && item < upperBound);
break;
}
}else{
lowerBound = currentItem+1;
//Follow right child
if(ItemHandler::rightChild(current) != -1) {
//Continue traversing
currentItem = ItemHandler::rightChild(current);
Q_ASSERT(currentItem >= lowerBound && currentItem < upperBound);
}else{
//The to-be deleted item is behind current, and can be added as right child to current
int item = findItem(m_remove, lowerBound, upperBound);
Q_ASSERT(item == removeIndex);
ItemHandler::createFreeItem(m_items[item]);
Q_ASSERT(item == -1 || currentItem < item);
ItemHandler::setRightChild(current, item);
Q_ASSERT(item >= lowerBound && item < upperBound);
break;
}
}
}
DEBUG_FREEITEM_COUNT
}
void debugFreeItemCount() {
uint count = 0;
for(uint a = 0; a < m_itemCount; ++a) {
if(ItemHandler::isFree(m_items[a]))
++count;
}
uint counted = countFreeItems(*m_centralFreeItem);
Q_ASSERT(count == counted);
Q_UNUSED(counted);
}
const Data& m_remove;
Data* m_items;
uint m_itemCount;
int* m_centralFreeItem;
int m_insertedAtDepth;
};
}
#endif