Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hybrid vector/list container?

Tags:

c++

stl

I'm in need of a container that has the properties of both a vector and a list. I need fast random access to elements within the container, but I also need to be able to remove elements in the middle of the container without moving the other elements. I also need to be able to iterate over all elements in the container, and see at a glance (without iteration) how many elements are in the container.

After some thought, I've figured out how I could create such a container, using a vector as the base container, and wrapping the actual stored data within a struct that also contained fields to record whether the element was valid, and pointers to the next/previous valid element in the vector. Combined with some overloading and such, it sounds like it should be fairly transparent and fulfill my requirements.

But before I actually work on creating yet another container, I'm curious if anyone knows of an existing library that implements this very thing? I'd rather use something that works than spend time debugging a custom implementation. I've looked through the Boost library (which I'm already using), but haven't found this in there.

like image 792
Nairou Avatar asked Jun 14 '11 03:06

Nairou


2 Answers

If the order does not matter, I would just use a hash table mapping integers to pointers. std::tr1::unordered_map<int, T *> (or std::unordered_map<int, unique_ptr<T>> if C++0x is OK).

The hash table's elements can move around which is why you need to use a pointer, but it will support very fast insertion / lookup / deletion. Iteration is fast too, but the elements will come out in an indeterminate order.

Alternatively, I think you can implement your own idea as a very simple combination of a std::vector and a std::list. Just maintain both a list<T> my_list and a vector<list<T>::iterator> my_vector. To add an object, push it onto the back of my_list and then push its iterator onto my_vector. (Set an iterator to my_list.end() and decrement it to get the iterator for the last element.) To lookup, look up in the vector and just dereference the iterator. To delete, remove from the list (which you can do by iterator) and set the location in the vector to my_list.end().

std::list guarantees the elements within will not move when you delete them.

[update]

I am feeling motivated. First pass at an implementation:

#include <vector>
#include <list>

template <typename T>
class NairouList {
public:
  typedef std::list<T> list_t;
  typedef typename list_t::iterator iterator;
  typedef std::vector<iterator> vector_t;

  NairouList() : my_size(0)
  { }

  void push_back(const T &elt) {
      my_list.push_back(elt);
      iterator i = my_list.end();
      --i;
      my_vector.push_back(i);
      ++my_size;
  }

  T &operator[](typename vector_t::size_type n) {
      if (my_vector[n] == my_list.end())
          throw "Dave's not here, man";
      return *(my_vector[n]);
  }

  void remove(typename vector_t::size_type n) {
      my_list.erase(my_vector[n]);
      my_vector[n] = my_list.end();
      --my_size;
  }

  size_t size() const {
      return my_size;
  }

  iterator begin() {
      return my_list.begin();
  }

  iterator end() {
      return my_list.end();
  }

private:
  list_t my_list;
  vector_t my_vector;
  size_t my_size;
};

It is missing some Quality of Implementation touches... Like, you probably want more error checking (what if I delete the same element twice?) and maybe some const versions of operator[], begin(), end(). But it's a start.

That said, for "a few thousand" elements a map will likely serve at least as well. A good rule of thumb is "Never optimize anything until your profiler tells you to".

like image 156
Nemo Avatar answered Sep 28 '22 08:09

Nemo


Looks like you might be wanting a std::deque. Removing an element is not as efficient as a std::list, but because deque's are typically created by using non-contiguous memory "blocks" that are managed via an additional pointer array/vector internal to the container (each "block" would be an array of N elements), removal of an element inside of a deque does not cause the same re-shuffling operation that you would see with a vector.

Edit: On second though, and after reviewing some of the comments, while I think a std::deque could work, I think a std::map or std::unordered_map will actually be better for you since it will allow the array-syntax indexing you want, yet give you fast removal of elements as well.

like image 38
Jason Avatar answered Sep 28 '22 08:09

Jason