In a binary search tree, the end() member function shall simply return iterator(nullptr), right? But the node nullptr contains no information about its left, right, and parent data. So how do we decrement an iterator from end()? The following is what I have so far (defined in the iterator<T> class). My iterator<T> class has node<T>* ptr as its only data member, and my node<T> class has members T value; node *left, *right, *parent;.
iterator& operator--() {
// if (!ptr) what to do???
if (ptr->left)
ptr = node<T>::max_node(ptr->left);
else {
node<T>* previous = ptr;
ptr = ptr->parent;
while (ptr && ptr->left == previous) {
previous = ptr;
ptr = ptr->parent;
}
}
return *this;
}
If you want your iterator to be a bidirectional iterator you’ll need to provide the necessary information to find the last node of the tree in the iterator returned by end(). To avoid dealing with a special case just to decrement the end() iterator you could use a “secret” node which uses its left pointer to point at the root of the tree. This way the end() iterator would simply be a pointer to this secret node: with this approach there is no need for any special treatment in increment, decrement, or comparison.
Since it is generally undesirable to store a node value in the root the usual implementation splits the nodes into a NodeBase holding only the pointer needed for navigation and a NodeValue deriving from the NodeBase and adding the node’s value. The root uses a NodeBase member and the iterator type a NodeBase*: for the navigation only the pointers are needed. Since only valid iterators are allowed to be dereferenced a static_cast<NodeValue*>(n) can be used to obtain the NodeValue when accessing the node’s value.
Instead of end() being iterator(nullptr) make it iterator(&anchor), where anchor is the member of the containing tree that is used to hold the root beginning and final node pointers. And then to decrement you simply step back to the preceding node (the opposite action to increment).
It does require somewhat more work than simply holding a root node pointer but it also allows begin() and end() to be O(1) operations.
Related
I'm trying to implement about list and iterator of list like C++ STL.
The node in list is defined like this:
struct Node{
Node *prev,*next;
value_type data;
};
And I want to overloading operator > and < :
bool list_iterator::operator>(const iterator_impl_base &rhs) const
bool list_iterator::operator<(const iterator_impl_base &rhs) const
which means if I need to call next to reach rhs.node , it will return 0 in > and return 1 in <.
if I need to call prev to reach rhs.node , it return 1 in > and return 0 in <.
And I implement list using circular list. Below is one part of list class :
class List : public ordered_container {
protected:
Node* begin_;
Node* end_;
size_type size_;
public:
List::List() : end_(new Node){
begin_ = end_->prev = end_->next = end_;
size_=0;
}
}
So , I don't know how to distinguish whether I just pass the begin_ of list. Can someone help me about that? Thankyou.
Firstly, using operator< to mean "precedes" is weird. I know it's an exercise, I just don't want you thinking this is normal.
Now, your circular list stores the begin and end in the top-level container object, so there's no way for a node itself to tell whether it's the head, or the trail, or whether a reversal wraps around. Only the container can tell this.
The usual solution for circular lists is to have a sentinel node between head and tail. Then sentinel.next is the head, sentinel.prev is the tail, and you need some way to mark the sentinel itself (either a magic value of data or an extra flag). This sentinel node can then replace the two pointers in your container object (so you're not wasting any space).
When you traverse the list, it's still circular, but you can tell if you pass the sentinel that you've wrapped around.
The sentinel has the additional benefit that you never have to worry about nullptrs in an empty list.
Incidentally, I find it very strange that instructors keep using doubly-linked lists and don't show the sentinel arrangement. It's described within a couple of pages in a copy of Elson's Data Structures book I've inherited, and that was published in 1975. What's the point of deliberately teaching a bad doubly-linked list?
If they wanted you to figure this out for yourself, they ought to have you working through the fundamental list operations rather than these odd precedes/succeeds operators, as the basic operations can actually work without a sentinel but are visibly improved with its addition.
An element a is less than another element b iff you can reach b before end by following next.
An element a is greater than another element b iff b is less than a.
Is there a possiblity to write a template for List (like in STL) that will be made of double linked list using nodes connected to themself and to provide ability to use iterators like begin or end?
If I had nested class:
class Node{
T data;
Node* previous, next;
Node(T &data, Node* next);
};
And my list would have begin() function:
template<class T>
class List {
Node *data; //first element
...
public:
T* begin() { return data->data; }; //return content of the first element
...
I assume that if I would like to use that list with for example std::copy function like
copy(l.begin(), l.end(), out);
then copy function iterates through the list using "begin++" then it would like to increment a pointer that points to the "data" object inside of node. Then it would not take a data from next node.
So is it possible to make that kind of a list?
First of all, there's std::list - which is probably what you want.
Secondly, your implementation of begin() does not fit the expectation for what that function returns for containers. You'll want to return something that at the very least models ForwardIterator (and since it's doubly-linked, BidirectionalIterator). Basically, this needs to work:
List<int> my_list = ...;
auto it = my_list.begin();
int& x = *it; // first value in the list
++it; // next element in the list
int& y = *it; // next value in the list
Right now, begin() yields a List<int>::Node*. That dereferences to a List<int>::Node, but it should dereference to an int. Wrong type and leaking the abstraction. Incrementing the pointer compiles, but it will point to some arbitrary spot in memory rather than the next node. There is no guarantee after all that the next node will be adjacent in memory (almost certainly it isn't!)
So you need to write your own iterator type which wraps your Node class, which will have to do those operations correctly based on the iterator concepts. Basically you're just mapping the iterator concept operations to what those look like for Node. To get you started as an example:
Node* underlying;
iterator& operator++() {
underlying = underlying->next;
return *this;
}
T& operator*() {
return underlying->data;
}
Also, check out the Boost's Iterator Facade library, which is helpful for writing iterators correctly.
If I understand right, range-based for-loops check the end condition by calling iter != c.end() on your iterator (iter) and collection (c). But suppose I have a collection where generating the iterator to c.end() is not particularly easy or efficient?
Can range-based for-loops recognize another method (something like bool c.ended(iter) const) which will take iter as an argument and check if it has reached the end? Is there some trick with templates to get the same effect?
EXAMPLE:
Imagine we have a forward_list that has a header/end node to mark the end, but it doesn't hold a pointer to that node. Instead, it has some method by which it recognizes the node when it's reached. Then, we can't easily create an iterator which points to that node, but if an iterator already does point to that node, we can tell.
EXAMPLE 2:
Ok, clearly example 1 was bad. I'll get closer to what I'm actually doing (although there's a little more). forward_list can be frustrating because it has no erase() method. There's only an erase_after(). So, to rectify this I want to have an alternate forward_list where the iterator internally contains a pointer to the node prior to the one it claims to point to. Now I can have an erase() method which behaves properly (at the expense of having to worry about whether something else might erase the node I'm internally sitting on). Now generating an iterator to end() involves figuring out what's the very last element in the list. I could maintain a pointer, but that's really just wasted wasted memory because checking if the iterator==end() should be easy. I can just check if p_my_node -> next == NULL.
The implementation of the range-based for loop must only call the end() function on the container once, so if that is your concern, it has already been addressed in the standard.
If you have anything other than c.end() that can be used as a check for end-of-range, and is cheaper to calculate than c.end(), why is c.end() not just that?
It may be hard to get the idea through just in English in the comments, but this sketch of an implementation should be able to give you an idea:
class Iterator {
node *ptr; // nullptr == end() by convention
public:
Iterator() : ptr() {}
Iterator(node *ptr) : ptr(ptr) {}
bool isEnd() const {
return !ptr || ptr->isEnd();
}
friend bool operator==(Iterator const& lhs, Iterator const& rhs) {
return (lhs.isEnd() && rhs.isEnd())
|| (lhs.ptr == rhs.ptr);
}
};
Now the implementation of end() is a cheap implementation:
Iterator Container::end() {
return Iterator();
}
Possible solution could be to create a special kind of iterator for c.end() and then make your_iterator::operator==() call c.ended(iter) and return that value when it sees end iterator as argument. I think you can create special overloaded operator== for different iterator type (end iterator), but if that a good solution difficult to say in advance. But you can have a special flag or special value in the end iterator and have that behavior in runtime in operator==().
In the past I've implemented a linked list using nodes.
I am looking at some of the properties of the standard library list and it has iterators and the appropriate member functions.
What are the iterators in a list exactly? Are they the node pointers?
For a vector, basically you have pointers to the element type and the data structures is built on an underlying dynamic array of that given type.
For the lists, it seems it is just a sequence of nodes, array of nodes. So are the iterators the node pointers rather than a pointer to the node data type?
Basically what I am asking is if for a vector I had this iterator:
tyepdef T* iterator;
Would the iterator for the list be
typedef node* iterator;
where node is something like:
template <class T> struct node {
node() { next = 0; }
node(T i, node* n = 0) : data(i), next(n) {}
node* next;
T data;
}
If this is the case, it seems that operations like dereferencing will have to be overloaded.
The std::list<T>::iterator objects internally point to a node but have operators which appropriately follow the pointers to next or previoys node. That is, they are not pointers as incrementing a pointer just adds one rather than follow a link. You can inagine a list iterator looks somewhat like this:
template <typename T>
class list_iterator {
friend list<T>
Node* node;
list_iterator(T* node):node(node) {}
list_iterator& operator++() {
node = node->next;
return *this;
}
T& operator*() { return *node; }
// ...
};
The iterator on lists should behave similar to the iterators on other sequence containers like vector. I.e. it should behave like a pointer to the list::value_type as if it were in an array or similar (with ++ and -- doing the expected operation going to next and previous). The internals of the holding structure aren't really exposed through the iterator. The iterator abstraction generally frees the programmer from thinking about how the data is stored. In the future, you could theoretically swap your std::list for a std::vector without changing your code, so long as you're only using operations available to both.
I'm implementing a vector type. I'm not troubled by the algorithms or the data structure at all but I am unsure about a remove method. for instance:
bool Remove(Node* node)
{
/* rearrange all the links and extract the node */
delete node;
}
where node is a pointer to the current node that we are at. But if I delete node then how do I prevent this from happening:
Node* currentNode = MoveToRandNode();
Remove(currentNode);
cout << currentNode->value;
If currentNode were a pointer to a pointer it would be easier but...it's not.
You could add another level of abstraction to your iterator (which now is a raw pointer)
If you do not handle raw pointers, but create some sort of iterator class instead of a pointer, it is possible to invalidate the iterator, and thus failing controlled if anyone tries to access the iterator after it has been removed.
class Iterator {
Node operator*() {
if (node) return *node;
else throw Something();}
private:
Node* node;
}
Of course this wrapping of a pointer will come at a cost of some overhead (checking the pointer on each deref). So you will have to decide how safe you want to play. Either document as suggested by others or wrap for safety.
Step back first. You need to define who "owns" the memory pointed to by the vector. Is it the vector itself, or the code that uses the vector? Once you define this, the answer will be easy - either Remove() method should always delete it or never.
Note that you've just scratched the surface of the possible bugs and you answer to "who owns it" will help with other possible issues like:
If you copy a vector, do you need to copy the items within it, or just the pointers (e.g. do a shallow or deep copy
When you destroy a vector, should you destroy the items within it?
When you insert an item, should you make a copy of the item, or does the vector take ownership of it?
well, you cannot do that, but some modifications to your code can improve safety.
Add ref
bool Remove(Node*& node)
{
/* rearrange all the links and extract the node */
delete node;
node = nullptr;
}
check for nullptr
if(currentNode)
cout << currentNode->value;
probably you need to try std::shared_ptr
This is similar to "iterator invalidation". E.g., if you have a std::list l and a std::list::iterator it pointing into that list, and you call l.erase(it), then the iterator it is invalidated -- i.e., if you use it in any way then you get undefined behavior.
So following that example, you should include in your documentation of the Remove method something along the lines: "the pointer node is invalidated, and may not be used or dereferenced after this method returns."
(Of course, you could also just use std::list, and not bother to re-invent the wheel.)
For more info on iterator invalidation, see: http://www.angelikalanger.com/Conferences/Slides/CppInvalidIterators-DevConnections-2002.pdf
In addition what innochenti wrote.
I think you have to decide what is expected/desired behavior of cout << currentNode->value;:
Error - (as innochenti wrote node = nullptr)
Default Value - create node devault_value (which has some default value for its value), and after delete node; do node=default_value