I have a dummy question. I always read that C++ std::list container has constant time for inserting elements at the beginning, at the end and in the middle:
Which is the correct way to insert an element directly in the middle of a std::list?
Is maybe this one?
std::list<int> l;
l.push_back(10);
l.push_back(20);
l.push_back(30);
l.push_back(40);
l.push_back(50);
l.push_back(60);
l.insert( l.end()- l.begin() /2 ); //? is this
// inserting directly in the middle?
When we say 'inserting in the middle' do we really mean that we save linear time to go from the beginning of the list to the desired point ( traversing one by one through all linked elements in between)?
You can do the iterator maths generically like this:
std::list<int>::iterator it = l.begin();
std::advance(it, std::distance(l.begin(), l.end())/2);
l.insert(it, value);
This will work for any iterator type (except OutputIterator or InputIterator)
Of course it is way more efficient to say
std::advance(it, l.size()/2);
l.insert(it, value);
Unfortunately, l.insert(l.begin + (l.size()/2), value) won't work because list iterators aren't random access, therefore don't have operator+ defined (to prevent performance surprises!). Keep in mind that std::advance() might be a costly operation depending on iterator type (it will be slow for reverse iterators implemented on a forward-only container, e.g.).
Here, "the middle" means an arbitrary point in the list, as long as you already have an iterator referring to that point. Given that, insertion is just a matter of modifying a few pointers around the insertion point.
If you don't already have an iterator for the insertion point, and it isn't somewhere simple like the beginning or the end, then it will take linear time to walk through the list to find it.
When we say 'inserting in the middle' do we really mean that we save linear time to go from the beginning of the list to the desired point ( traversing one by one through all linked elements in between)?
Yes.
Basically, It means the list needs to just change pointers to insert a new element and not navigate the entire list or copy the contents etc.Hence the insertion is constant time, because there is no need of traversing the list or copying the containers etc.
Inserting in the "middle" of a list means inserting somewhere other than the beginning or end. But doing an insertion requires an iterator to the insertion point. Once you have such an iterator, the insertion time is constant, but getting such an iterator in the first place is a separate issue.
No, when we say "inserting in the middle" we do not mean "finding the insertion point according to whatever criteria that takes traversing of the whole or indefinite part of the list".
Related
I have written a circular buffer of size N. I've also written a custom iterator.
I'm using them for logic like this:
auto iter = circular_buffer.begin();
while(iter != circular_buffer.end())
{
++iter;
}
What is the implementation for end()? Usually it should point to the last element + 1, but if the buffer contains N items, last element + 1 will be the first element and the above code won't even loop once. I cannot do:
do
{
++iter;
} while(iter != circular_buffer.end());
because if the buffer is empty, it will execute once.
Is there a way to solve this?
Note: the following assumes that the circular nature of the buffer is a property that is being exposed to the user for their use, rather than being an implementation detail of the system (as is the case for std::list implementations).
Giving a circle a proper "range" is something of a problem since... it's a circle. It conceptually has neither a beginning nor an end.
One way to do this is to make the "circle" into a helix. Every iterator stores both whatever normal information it needs as well as a cycle count. When an iterator is incremented to whatever the arbitrary "beginning" of the list is, the cycle count is incremented. If the iterator is decremented to before the beginning of the list, the cycle count is decremented. Two iterators are only equal if their position and cycle counts are equal.
Most iterators created by the circular list should have a cycle count of zero on construction. The end iterator should have a cycle count of 1 while having the same position in the circle as the begin iterator.
Of course, there are some implementation problems. Doing this requires that every iterator's increment/decrement operations to know where the "beginning" of the circle is. So they all need a pointer to the first element or something. But if they have that, then these iterators all become invalid if you modify the list, since that can change what the "first element" is. Now, invalidating iterators on container modification is something that is true of many container iterator types, so it's not too big of a problem. But it is something you should be aware of.
That's kind of an unavoidable downside of trying to use ranges with a concept that inherently has neither a beginning nor an end.
I want to replace a range of elements in a vector by a single other element (built from that range, but it doesn't matter). I'd like to do so without erasing first and inserting then, for all subsequent elements would me moved twice.
I figured we could generalize that with the replacement of a range by another range, whatever their sizes. Replace an empty range with something and you get insert. Replace something with an empty range and you get erase.
Anyways replace seems not to do that. It replaces all or some elements in a rage with copies of the given element. No moving around.
swap_ranges swaps elements one by one, no erase or insert either.
Have I missed an algorithm for that? Maybe a member function?
Maybe I should use list then?
I want to replace a range of elements in a vector by a single other element
As far as I know, there is no algorithm for this in standard library. But it can be fairly simply be built on top of the existing ones: Remove all except first of the range. Assign the element that wasn't removed. In the special case of empty range, insert instead of assigning. Return iterator to the beginning of the removed elements (iterator to end in case nothing was removed) so that the caller can erase them.
I figured we could generalize that with the replacement of a range by another range
This works pretty much the same as the single insert variation, but is more complex. Replace assignment with std::copy, and take into consideration that there is also a case where you both assign (copy) and insert a subrange depending on lengths of the input ranges.
The order assignment, remove, insert does not matter.
This is an algorithm that can't be done with "just iterators".
To change the size of the underlying sequence, you need to be able to call insert or erase on the underlying container.
So even though you might want to write something like:
template <typename Iter1, typename Iter2>
void replaceRange (Iter1 s_first, Iter1 s_last, Iter2 d_first, Iter2 d_last);
it won't ever work (except for the cases where the sizes are the same)
How can I define operator< for bidirectional iterator? ( list::iterator )
(I would like to use list and not vector.)
You can't do it directly, but you can compute std::distance(x.begin(), it1) and std::distance(x.begin(), it2) and compare those. Given that lists don't have random access, you expect to have to pay the price for such a query by having to traverse the entire list.
Edit: This will perform poorly if both iterators are near the end of the list. If you want to get more fancy, you could write some exploring algorithm that moves outwards from both iterators:
[ .... <-- it1 --> .... <-- it2 --> .... ]
You would basically keep two copies for each, fwd1/rev1 and fwd2/rev2, and you decrement the rev* iterators until you hit x.begin() and advance the fwd* iterators until you hit x.end(). If your iterator pairs are uniformly distributed, this probably has better expected runtime.
Impossible. You might need to walk until end, and for that you need to know the list of origin, which is not encoded in a list::iterator.
(You can make a function object for this purpose, though, which takes the list or origin as a constructor argument. Mind you, finding out whether one iterator is less-than another would take O(n) time.)
You can't do this because you'd have to know the start and/or end of the list in order to make such a comparison. Only random access iterators define operator<.
Supposing ++(list.end()) is not undefined behaviour and equals list.end(), there is a way. But i am not sure of this hypothesis.
If it is valid, you can define a simple algorithm to obtain the result you want.
Is there a reason that the STL does not provide functions to return an iterator into a container via an index?
For example, let's say I wanted to insert an element into a std::list but at the nth position. It appears that I have to retrieve an iterator via something like begin() and add n to that iterator. I'm thinking it would be easier if I could just get an iterator at the nth position with something like, std::list::get_nth_iterator(n).
I suspect I have misunderstood the principles of the STL. Can anyone help explain?
Thanks
BeeBand
You can use advance() from the <iterator> header:
list<foo>::iterator iter = advance(someFooList.begin(), n);
list<foo>::iterator iter = someFooList.begin();
std::advance( iter, n);
If the iterator supports random access (like vector) it'll work quite efficiently, if it only supports increasing (or decreasing) the iterator, like list, it'll work but only as well as can be.
std::list is a linked list. So it does not support random access. To get to the nth position in the list, you have to start at the beginning and move through all the nodes until you arrive at n. This is pretty expensive (O(n)), and thus it's bad to have a method that does not suggest this expense. get_nth_iterator(n) implies getting the iterator that points to the nth node is cheap.
std::vector of course supports this directly with the [] operator, because the datastructure supports random access and so this is very inexpensive for it.
std::list isn't random-access container, so there is no reason for accessing n-th element. if you need this, consider using std::vector instead..
Generally, anything that might be costly is made a bit clumsy, so you won't do it by accident. Using your example, with a container that provides random access iterators it would be simply container.begin()+n, but for an std::list (which provides a forward iterator) you'd need to use list.begin() followed by advance().
If you want to get the Nth iterator, chances are that you just shouldn't be using std::list in the first place. Then again, if you started that sentence at "chances", it would remain nearly as true...
I need a container (not necessarily a STL container) which let me do the following easily:
Insertion and removal of elements at any position
Accessing elements by their index
Iterate over the elements in any order
I used std::list, but it won't let me insert at any position (it does, but for that I'll have to iterate over all elements and then insert at the position I want, which is slow, as the list may be huge). So can you recommend any efficient solution?
It's not completely clear to me what you mean by "Iterate over the elements in any order" - does this mean you don't care about the order, as long as you can iterate, or that you want to be able to iterate using arbitrarily defined criteria? These are very different conditions!
Assuming you meant iteration order doesn't matter, several possible containers come to mind:
std::map [a red-black tree, typically]
Insertion, removal, and access are O(log(n))
Iteration is ordered by index
hash_map or std::tr1::unordered_map [a hash table]
Insertion, removal, and access are all (approx) O(1)
Iteration is 'random'
This diagram will help you a lot, I think so.
Either a vector or a deque will suit. vector will provide faster accesses, but deque will provide faster instertions and removals.
Well, you can't have all of those in constant time, unfortunately. Decide if you are going to do more insertions or reads, and base your decision on that.
For example, a vector will let you access any element by index in constant time, iterate over the elements in linear time (all containers should allow this), but insertion and removal takes linear time (slower than a list).
You can try std::deque, but it will not provide the constant time removal of elements in middle but it supports
random access to elements
constant time insertion and removal
of elements at the end of the
sequence
linear time insertion and removal of
elements in the middle.
A vector. When you erase any item, copy the last item over one to be erased (or swap them, whichever is faster) and pop_back. To insert at a position (but why should you, if the order doesn't matter!?), push_back the item at that position and overwrite (or swap) with item to be inserted.
By "iterating over the elements in any order", do you mean you need support for both forward and backwards by index, or do you mean order doesn't matter?
You want a special tree called a unsorted counted tree. This allows O(log(n)) indexed insertion, O(log(n)) indexed removal, and O(log(n)) indexed lookup. It also allows O(n) iteration in either the forward or reverse direction. One example where these are used is text editors, where each line of text in the editor is a node.
Here are some references:
Counted B-Trees
Rope (computer science)
An order statistic tree might be useful here. It's basically just a normal tree, except that every node in the tree includes a count of the nodes in its left sub-tree. This supports all the basic operations with no worse than logarithmic complexity. During insertion, anytime you insert an item in a left sub-tree, you increment the node's count. During deletion, anytime you delete from the left sub-tree, you decrement the node's count. To index to node N, you start from the root. The root has a count of nodes in its left sub-tree, so you check whether N is less than, equal to, or greater than the count for the root. If it's less, you search in the left subtree in the same way. If it's greater, you descend the right sub-tree, add the root's count to that node's count, and compare that to N. Continue until A) you've found the correct node, or B) you've determined that there are fewer than N items in the tree.
(source: adrinael.net)
But it sounds like you're looking for a single container with the following properties:
All the best benefits of various containers
None of their ensuing downsides
And that's impossible. One benefit causes a detriment. Choosing a container is about compromise.
std::vector
[padding for "15 chars" here]