mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-24 19:02:53 +00:00
911 lines
43 KiB
C
911 lines
43 KiB
C
![]() |
/* 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(¢ralFreeItem) {
|
||
|
}
|
||
|
~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(¢ralFreeItem), 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(¢ralFreeItem), 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
|