IndexedArray.h#
Implementation of a special kind of map that stores the data in a linear, contiguous store (std::vector interface), but additionally contains an Index to them (std::map interface). Supports custom association of names with values, and retrieval of indices by name or values by index.
Detailed Description#
PowerVR by Imagination, Developer Technology Team.
Copyright (c) Imagination Technologies Limited.
Includes#
algorithm
list
map
vector
Included By#
Namespaces#
Classes#
Source Code#
#pragma once
#include <vector>
#include <map>
#include <list>
#include <algorithm>
namespace pvr {
template<typename ValueType_, typename IndexType_ = std::string>
class IndexedArray
{
private:
struct DictionaryEntry
{
ValueType_ value;
IndexType_ key;
DictionaryEntry() {}
DictionaryEntry(const IndexType_& key, const ValueType_& value) : key(key), value(value) {}
};
struct StorageItem_ : DictionaryEntry
{
bool isUnused;
StorageItem_() : isUnused(false) {}
StorageItem_(const IndexType_& key, const ValueType_& value) : DictionaryEntry(key, value), isUnused(false) {}
};
typedef std::vector<StorageItem_> vectortype_;
typedef typename std::map<IndexType_, size_t> maptype_;
typedef std::list<size_t> deleteditemlisttype_;
vectortype_ mystorage;
maptype_ myindex;
deleteditemlisttype_ myDeletedItems;
public:
class iterator
{
friend class IndexedArray<ValueType_, IndexType_>;
class const_iterator;
StorageItem_* start;
size_t current;
size_t size; // required for out-of-bounds checks when skipping empty...
iterator(StorageItem_* start, size_t size, size_t current = 0) : start(start), current(current), size(size) {}
public:
iterator(const const_iterator& rhs) : start(rhs.start), size(rhs.size), current(rhs.current) {}
DictionaryEntry& operator*() { return *(start + current); }
DictionaryEntry* operator->() { return (start + current); }
size_t getItemIndex() { return current; }
iterator& operator++()
{
while ((start + (++current))->isUnused && current != size) {} // skip empty values
return *this;
}
iterator operator++(int)
{
iterator ret = *this;
while ((start + (++current))->isUnused && current != size) {} // skip empty values
++(*this);
return ret;
}
iterator& operator--()
{
while ((start + (--current))->isUnused && current != static_cast<size_t>(-1)) {} // CAREFUL! this is a size_t, which means it WILL eventually overflow.
return *this;
}
iterator operator--(int)
{
iterator ret = *this;
--(*this);
return ret;
}
bool operator!=(const iterator& rhs) { return this->current != rhs.current; }
bool operator==(const iterator& rhs) { return !((*this) != rhs); }
};
class const_iterator
{
friend class IndexedArray<ValueType_, IndexType_>;
const StorageItem_* start;
size_t current;
size_t size; // required for out-of-bounds checks when skipping empty...
const_iterator(const StorageItem_* start, size_t size, size_t current = 0) : start(start), size(size), current(current) {}
public:
const DictionaryEntry& operator*() { return *(start + current); }
const DictionaryEntry* operator->() { return (start + current); }
size_t getItemIndex() { return current; }
const_iterator& operator++()
{
while (++current < size && (start + current)->isUnused) {} // skip empty values
return *this;
}
const_iterator operator++(int)
{
const_iterator ret = *this;
++(*this);
return ret;
}
const_iterator& operator--()
{
while (--current != static_cast<size_t>(-1) && (start + current)->isUnused) {} // CAREFUL! this is a size_t, which means it WILL eventually overflow.
return *this;
}
const_iterator operator--(int)
{
iterator ret = *this;
--(*this);
return ret;
}
bool operator!=(const const_iterator& rhs) { return this->current != rhs.current; }
bool operator==(const const_iterator& rhs) { return !((*this) != rhs); }
};
typedef typename maptype_::iterator index_iterator;
typedef typename maptype_::const_iterator const_index_iterator;
iterator begin()
{
if (!mystorage.empty())
{
iterator ret(&mystorage.front(), mystorage.size());
while ((ret.start + ret.current)->isUnused && (ret.current != ret.size)) { ++ret.current; } // skip empty values
return ret;
}
else
{
return iterator(NULL, 0);
}
}
const_iterator begin() const
{
if (!mystorage.empty())
{
const_iterator ret(&mystorage.front(), mystorage.size());
while ((ret.start + ret.current)->isUnused && ret.current != ret.size) { ++ret.current; } // skip empty values
return ret;
}
else
{
return const_iterator(NULL, 0);
}
}
typename maptype_::iterator indexed_find(const IndexType_& key) { return myindex.find(key); }
typename maptype_::const_iterator indexed_find(const IndexType_& key) const { return myindex.find(key); }
typename maptype_::iterator indexed_begin() { return myindex.begin(); }
typename maptype_::const_iterator indexed_begin() const { return myindex.begin(); }
typename maptype_::iterator indexed_end() { return myindex.end(); }
typename maptype_::const_iterator indexed_end() const { return myindex.end(); }
iterator end() { return mystorage.empty() ? iterator(NULL, 0) : iterator(&mystorage.front(), mystorage.size(), mystorage.size()); }
const_iterator end() const { return mystorage.empty() ? const_iterator(NULL, 0) : const_iterator(&mystorage.front(), mystorage.size(), mystorage.size()); }
void insertAt(size_t where, const IndexType_& key, const ValueType_& val)
{
if (insert(key, val) != where) { relocate(key, where); }
}
size_t insert(const IndexType_& key, const ValueType_& val)
{
std::pair<typename maptype_::iterator, bool> found = myindex.insert(std::make_pair(key, 0));
if (!found.second) // Element already existed!
{ mystorage[found.first->second].value = val; } else
{
found.first->second = insertinvector(key, val);
}
return found.first->second;
}
size_t getIndex(const IndexType_& key) const
{
typename maptype_::const_iterator found = myindex.find(key);
if (found != myindex.end()) // Element already existed!
{ return found->second; } return static_cast<size_t>(-1);
}
void erase(const IndexType_& key)
{
typename maptype_::iterator where = myindex.find(key);
if (where != myindex.end())
{
removefromvector(where->second);
myindex.erase(where);
// SPECIAL CASE: If no more items are left, there is absolutely no point in NOT compacting, as no iterators or indices exist to be invalidated, so we can clean up
// even though "deferred" was asked. Additionally, this is essentially free, except maybe for the list...
if (myindex.empty())
{
mystorage.clear();
myDeletedItems.clear();
}
}
}
ValueType_& operator[](size_t idx) { return mystorage[idx].value; }
const ValueType_& operator[](size_t idx) const { return mystorage[idx].value; }
ValueType_& operator[](const IndexType_& key) { return mystorage[myindex.find(key)->second].value; }
const ValueType_& operator[](const IndexType_& key) const { return mystorage[myindex.find(key)->second].value; }
void compact()
{
// We can do that because the last remove() tears down all datastructures used.
if (!myindex.size()) { return; }
// First, make sure there is something to compact...
deleteditemlisttype_::iterator unused_spot = myDeletedItems.begin();
while (myDeletedItems.size() && unused_spot != myDeletedItems.end())
{
// Last item in the storage vector.
size_t last = mystorage.size();
// 1) Trim the end of the vector...
while (last-- && mystorage[last].isUnused)
{
mystorage.pop_back();
// Rinse, repeat. If size is zero, there is nothing to do.
}
// Either the storage is empty
if (mystorage.empty())
{
myDeletedItems.clear();
// the rest should already be cleared...
// the loop will exit naturally
}
else // Or we can have find its last item!
{
// 2)Trim any items from the end of the unused spots list that may have been trimmed off by the vector...
while (unused_spot != myDeletedItems.end() && *unused_spot >= last) { myDeletedItems.erase(unused_spot++); }
// Any spots left?
if (unused_spot != myDeletedItems.end())
{
// Do the actual data movement. After all we've been through, we know that
// i. The last item of the vector is a valid item (guaranteed by 1)
// ii. The unused spot is not out of bounds of the vector
// iii.
// Copy by hand(as we have not defined a move assignment operator for compatibility reasons.
// Also, since the std::string does not throw, this makes easier to reason about exceptions.
mystorage[*unused_spot].value = std::move(mystorage[last].value);
mystorage[*unused_spot].key = std::move(mystorage[last].key);
mystorage[*unused_spot].isUnused = false;
myindex[mystorage[*unused_spot].key] = *unused_spot;
mystorage.pop_back();
myDeletedItems.erase(unused_spot++);
} // else : No action needed - unused spots has been trimmed off completely, so no movement is possible, or necessary...
}
}
}
void clear()
{
myindex.clear();
mystorage.clear();
myDeletedItems.clear();
}
size_t size() const { return myindex.size(); }
size_t sizeWithDeleted() const { return mystorage.size(); }
size_t capacity() const { return mystorage.size(); }
size_t deletedItemsCount() const { return myDeletedItems.size(); }
bool relocate(const IndexType_& key, size_t index)
{
typename maptype_::iterator found = myindex.find(key);
if (found == myindex.end()) { return false; }
size_t old_index = myindex[key];
if (index == old_index) { return true; } // No-op
if (index + 1 > mystorage.size()) // Storage not big enough.
{
// Grow, and mark unused all required items. Need to add all spots (But the last) to unusedspots.
size_t oldsize = mystorage.size();
mystorage.resize(index + 1);
for (size_t i = oldsize; i < index; ++i)
{
myDeletedItems.push_front(i);
mystorage[i].isUnused = 1;
}
mystorage.back() = mystorage[old_index];
removefromvector(old_index);
}
else if (mystorage[index].isUnused) // Lucky! Storage is big enough, and the item is not used!
{
deleteditemlisttype_::iterator place = std::find(myDeletedItems.begin(), myDeletedItems.end(), index);
assert(place != myDeletedItems.end()); // Shouldn't happen! Ever!
myDeletedItems.erase(place);
mystorage[index] = mystorage[old_index];
removefromvector(old_index);
}
else // Whoops! Space is already occupied. Swap with the old item!
{
myindex[mystorage[index].key] = old_index;
std::swap(mystorage[index], mystorage[old_index]);
}
myindex[key] = index;
return true;
}
private:
size_t insertinvector(const IndexType_& key, const ValueType_& val)
{
size_t retval;
if (myDeletedItems.empty())
{
retval = mystorage.size();
mystorage.emplace_back(StorageItem_());
mystorage.back().isUnused = false;
mystorage.back().key = key;
mystorage.back().value = val;
}
else
{
retval = myDeletedItems.back();
myDeletedItems.pop_back();
mystorage[retval].value = val;
mystorage[retval].key = key;
mystorage[retval].isUnused = false;
}
return retval;
}
void removefromvector(size_t index)
{
if (index == (mystorage.size() - 1))
{
// Removing the last item from the vector -- just pop it.
mystorage.pop_back();
}
else
{
// NOT the last item, so we just destruct it (to free any potential expensive resources),
// and then default-construct it (to have the spot destructible). We keep the reference to
// the key as we will need it.
myDeletedItems.push_front(index);
mystorage[index].isUnused = true;
mystorage[index].value.ValueType_::~ValueType_();
new (&mystorage[index].value) ValueType_;
}
}
};
} // namespace pvr