External Doubly Linked-list Iterator c++ - c++

I made a doubly linked list. In my main, I need to use an external List Iterator that has a constructor that takes a list. This is what I have so far:
struct ListIterator : List {
Node* cur = head;
ListIterator(List* list) {
this -> list = list;
}
bool hasNext() {
return (cur -> next) != nullptr;
}
int next() {
return list.at(cur -> next);
}
};
This is my main:
List list1;
list1.append('I');
list1.append('D');
list1.append('F');
list1.append('G');
ListIterator it(list1);
while (it.hasNext()) {
cout << it.next();
}
As you can see, I'm trying to use hasNext() and next() to display all the nodes in a list. I'm confused on how to create a constructor that takes a list and use the function in ListIterator struct. Any tips and hints would be very much appreciated.

You seem to be heavily influenced by Java (or perhaps even C#). It's not a bad thing, you just need to learn the ins and outs of C++.
For your first problem: in C++, there is a clear distinction between values, references and pointers. When you declare an instance of your List class as follows:
List list1;
that is actually a value. The instance exists on the stack and will be properly disposed off (the destructor will be called) once the scope is ended.
If you now were to initialize a second list as follows:
List list2 = list1;
that is also a value, and it will copy the entire list (the copy constructor or assignment operator will be called). Now list1 and list2 are two distinct list instances and modifying one will not affect the other.
Anyhow, to get a pointer to a list, you need the following syntax:
List *listPtr1 = &list1;
As it is a pointer, an indirection, copying it will not copy the underlying structure:
List *listPtr2 = listPtr1; // Will also point to list1
While using the proper syntax will solve your immediate problem, it doesn't properly address the odd iterator implementation.
In C++, iterators either point to a single element in a container or they point past-the-end. An iterator typically does not know if the end of the container is reached by itself; instead, it needs to be compared to this past-the-end iterator.
Containers typically define a begin method that returns an iterator to the first element, and an end method that returns an iterator to past-the-end.
Iterating over a container typically happens using the following prototype:
for (auto it = container.begin(); it != container.end(); ++it)
{
// To access the element, you need to dereference the iterator:
std::cout << "The current value is :" << *it << std::endl;
}
In this regard, iterators behave much like pointers to the elements. There's no need for the clunky next and hasNext methods. You simply get an iterator to the start of your container, make sure to stop iterating once it points to past-the-end, and increment it when you want to go to the next element.
A container that properly defines the begin and end methods can also be used in a range-based for loop:
for (auto &element : container)
{
std::cout << "The current value is :" << element << std::endl;
}
I understand it is fun and very educational to implement your own containers. I reinvented my fair share of wheels just to understand how things work exactly, so nothing wrong with that. But I would advice you to perhaps play around with standard C++ containers first, get the hang of the major differences between C++ and languages like Java and C# and then have another go at the exercise.

Related

Declaring a std::list with an array index C++

I was following a hash table implementation online (https://www.youtube.com/watch?v=2_3fR-k-LzI) when I observed the video author initialize a std::list with an array index. This was very confusing to me as I was always under the impression that std::list was always meant to operate like a linked list and was not capable of supporting random indexing. However, I thought it was maybe a weird way to declare the size of a list and ignored it and moved on. Specifically, he did the following:
static const int hashGroups = 10;
std::list<std::pair<int, std::string>> table[hashGroups];
Upon trying to implement a function to search to see if a key resided in the hash table, I realized that I could not access the std::list objects as I would expect to be able to. In HashTable.cpp (which includes the header file that defines the two variables above) I was only able to access the table member variable's elements as a pointer with -> instead of with . as I would expect to be able to. It looks like what is directly causing this is using the array index in the list definition. This seems to change the type of the table variable from a std::list to a pointer to a std::list. I do not understand why this is the case. This also appears to break my current implementation of attempting to iterate through the table variable because when I declare an iterator to iterate through table's elements, I am able to see that the table has the correct data in the VS debugger but the iterator seems to have completely invalid data and does not iterate through the loop even once despite seeing table correctly have 10 elements. My attempt at the search function is pasted below:
std::string HashTable::searchTable(int key) {
for (std::list<std::pair<int, std::string>>::const_iterator it = table->begin(); it != table->end(); it++)
{
if (key == it->first) {
return it->second;
}
std::cout << "One iteration performed." << std::endl;
}
return "No value found for that key.";
}
With all of this being said, I have several burning questions:
Why are we even able to declare a list with brackets when a std::list does not support random access?
Why does declaring a list like this change the type of the list from std::list to a pointer?
What would be the correct way to iterate through table in its current implementation with an iterator?
Thank you for any help or insight provided!
After reading the responses from #IgorTandetnik I realized that I was thinking about the list incorrectly. What I didn't fully understand was that we were declaring an array of lists and not attempting to initialize a list like an array. Once I realized this, I was able to access the elements correctly since I was not trying to iterate through an array with an iterator for a list. My revised searchTable function which to my knowledge now works correctly looks like this:
std::string HashTable::searchTable(int key) {
int hashedKey = hashFunction(key);
if (table[hashedKey].size() > 0)
{
for (std::list<std::pair<int, std::string>>::const_iterator it = table[hashedKey].begin(); it != table[hashedKey].end(); it++)
{
if (key == it->first) {
return it->second;
}
}
}
return "No value found for that key.";
}
And to answer my three previous questions...
1. Why are we even able to declare a list with brackets when a std::list does not support random access?
Response: We are declaring an array of std::list that contains a std::pair of int and std::string, not a list with the array index operator.
2. Why does declaring a list like this change the type of the list from std::list to a pointer?
Response: Because we are declaring table to be an array (which is equivalent to a const pointer to the first element) which contains instances of std::list. So we are never "changing" the type of the list variable.
3. What would be the correct way to iterate through table in its current implementation with an iterator?
Response: The current implementation only attempts to iterate over the first element of table. Create an iterator which uses the hashed key value as the array index of table and then tries to iterate through the std::list that holds instances of std::pair at that index.

Is there any way to swap nodes in std::list?

I'm implementing LRUCache, where in unordered_map I store an iterator to list. When I move the most "fresh" element to the head, I need to iterator not changed.
I need to swap exactly nodes, not values in nodes. I'm finding the way to do it.
I tried to do it with std::iter_swap, but it's just implemented as std::swap(*it_first, *it_second)
std::list<std::string> list;
list.emplace_back("first");
list.emplace_back("second");
auto it_first = list.begin();
auto it_second = ++list.begin();
std::iter_swap(it_first, it_second);
assert(list.begin() == it_second);
I need to swap two nodes to passed assert.
splice looks like it can do this with something like:
list.splice(it_first, list, it_second);
That says "Splice in it_second from myself (list, the second argument), before the first node in myself". The method guarantees that "the iterators to moved elements remain valid, but now refer into *this, not into other.", which implies the raw nodes themselves are moved.

C++: cannot assign index to iterator

Okay, a small problem with, hopefully, a quick, simple solution.
In my school textbook, in a chapter about the STL, it gives a simple sample program to input for using lists and for using an iterator with a list, like so:
#include <list>
#include <iostream>
#include <string>
using namespace std;
int main()
{
list<int> myIntList;
// Insert to the front of the list.
myIntList.push_front(4);
myIntList.push_front(3);
myIntList.push_front(2);
myIntList.push_front(1);
// Insert to the back of the list.
myIntList.push_back(5);
myIntList.push_back(7);
myIntList.push_back(8);
myIntList.push_back(9);
// Forgot to add 6 to the list, insert before 7. But first
// we must get an iterator that refers to the position
// we want to insert 6 at. So do a quick linear search
// of the list to find that position.
list<int>::iterator i = 0;
for( i = myIntList.begin(); i != myIntList.end(); ++i )
if( *i == 7 ) break;
// Insert 6 were 7 is (the iterator I refers to the position
// that 7 is located. This does not overwrite 7; rather it
// inserts 6 between 5 and 7.
myIntList.insert(i, 6);
// Print the list to the console window.
for( i = myIntList.begin(); i != myIntList.end(); ++i )
cout << *i << " "; cout << endl;
}
Now, at the line that says
list<int>::iterator i = 0;
I get an error in VS 2015 that says:
no suitable constructor exists to convert from"int" to "std::_List_iterator<std::_List_val<std::_List simple_types<int>>>"
What is the problem with the code presented, what is the solution, and why is this a problem to begin with? <-(I'll even settle with a simple grammatical error).
The value 0 may not be a valid value for an iterator. Try either removing the assignment or assigning the iterator to myIntList.begin().
The iterator may not be able to be treated as an index, like with a vector or array. Usually linked lists are not accessed by index; you have to traverse from the beginning.
What is the problem with the code presented
A simple typo in the example.
what is the solution
Replace this line:
list<int>::iterator i = 0;
With this instead:
list<int>::iterator i;
why is this a problem to begin with?
You cannot initialize an iterator with an integer value. Only the container knows what its iterators refer to, so only the container can initialize them. All you can do is request an iterator from a container, assign an iterator to another iterator, and increment/decrement/dereference an iterator. That is all.
From http://www.cplusplus.com/reference/iterator/:
An iterator is any object that, pointing to some element in a range of elements (such as an array or a container), has the ability to iterate through the elements of that range using a set of operators (with at least the increment (++) and dereference (*) operators).
This means that an iterator should be able to do the following:
Return the object it's currently "pointing" to (using the * operator)
Change itself to "point" to the next object in its list (using the ++ operator)
A reason for the existence of the iterator as a data type is to create a general way of interacting with different kinds of lists. However, this means that different lists will implement their iterators differently.
In many circumstances, initializing an iterator to a number doesn't make sense because of the implementation under the hood. As a result, we don't define an assignment operator with our iterator type std::vector<int>::iterator on the left and an int on the right. So when you try to assign your iterator to an integral value, list<int>::iterator i = 0; your compiler throws an error.
Let's look at an example where assigning an iterator to 0 doesn't make sense. You could implement an iterator for std::vector<int> as a pointer to an element in your vector. In this case:
* dereferences the pointer stored in vector<int>::iterator and returns its value.
++ modifies the pointer stored in vector<int>::iterator to point at the next element in the list.
However, assigning this pointer to 0 would be the same as assigning it to NULL, and dereferencing it no longer returns a valid element in your vector. (In fact, dereferencing NULL will cause an error!)
To avoid this error, simply make sure that you always assign your iterator to a value of the same type. In the STL, this is usually accomplished by using .begin() to return an iterator that points to the first element in your list.

How do I write end() for a custom linked list container?

I am writing a linked list for my homework assignment and I need to implement begin() and end() for my program's requirement.
Begin() is fairly easy
Node* List::begin(){return head;}
How should I implement end()?
You can return a NULL pointer, or if you have a custom tail node, return that. If it's a circular doubly-linked list, you can return the sentinel node.
Keep in mind that the proper way to check for the end of the list is to call List::end(), so if you've properly setup your List class, then it doesn't exactly matter what you return, as long as
Node* node = mylist.begin();
while (node != mylist.end()) { /*... loop */ }
works and you exit the while-loop when you reach the end of the list. So that main thing is that List::end() needs to return something unique that you will never encounter if you're traversing the middle of the list.

Iterating over a map

In this question I'm not asking how to do it but HOW IS IT DONE.
I'm trying (as an excersise) implement simple map and although I do not have problems with implementing links and they behavior (how to find next place to insert new link etc.) I'm stuck with the problem how to implement iterating over a map. When you think about it and look at std::map this map is able to return begin and end iterator. How? Especially end?
If map is a tree how can you say which branch of this map is an end? I just do not understand it. An how to iterate over a map? Starting from the top of the tree and then what? Go and list everything on the left? But those nodes on the left have also links to the right. I really don't know. I will be really glad if someone could explain it to me or give me a link so I could read about it.
A map is implemented using a binary search tree. To meet the complexity requirements it has to be a self-balancing tree, so a red-black tree is usually used, but that doesn't affect how you iterate over the tree.
To read the elements out of a binary search tree in order from least to greatest, you need to perform an in-order traversal of the tree. The recursive implementation is quite simple but isn't really practical for use in an iterator (the iterator would have to maintain a stack internally, which would make it relatively expensive to copy).
You can implement an iterative in-order traversal. This is an implementation taken from a library of tree containers I wrote a while ago. NodePointerT is a pointer to a node, where the node has left_, right_, and parent_ pointers of type NodePointerT.
// Gets the next node in an in-order traversal of the tree; returns null
// when the in-order traversal has ended
template <typename NodePointerT>
NodePointerT next_inorder_node(NodePointerT n)
{
if (!n) { return n; }
// If the node has a right child, we traverse the link to that child
// then traverse as far to the left as we can:
if (n->right_)
{
n = n->right_;
while (n->left_) { n = n->left_; }
}
// If the node is the left node of its parent, the next node is its
// parent node:
else if (n->parent_ && n == n->parent_->left_)
{
n = n->parent_;
}
// Otherwise, this node is the furthest right in its subtree; we
// traverse up through its parents until we find a parent that was a
// left child of a node. The next node is that node's parent. If
// we have reached the end, this will set node to null:
else
{
while (n->parent_ && n == n->parent_->right_) { n = n->parent_; }
n = n->parent_;
}
return n;
}
To find the first node for the begin iterator, you need to find the leftmost node in the tree. Starting at the root node, follow the left child pointer until you encounter a node that has no left child: this is the first node.
For an end iterator, you can set the node pointer to point to the root node or to the last node in the tree and then keep a flag in the iterator indicating that it is an end iterator (is_end_ or something like that).
The representation of your map's iterator is totally up to you. I think it should suffice to use a single wrapped pointer to a node. E.g.:
template <typename T>
struct mymapiterator
{
typename mymap<T>::node * n;
};
Or something similar. Now, mymap::begin() could return such instance of the iterator that n would point to the leftmost node. mymap::end() could return instance with n pointing to root probably or some other special node from which it is still possible to get back to rightmost node so that it could satisfy bidirectional iteration from end iterator.
The operation of moving between the nodes (operators++() and operator--(), etc.) are about traversing the tree from smaller to bigger values or vice versa. Operation that you probably have already implemented during insertion operation implementation.
For sorting purposes, a map behaves like a sorted key/value container (a.k.a. a dictionary); you can think of it as a sorted collection of key/value pairs, and this is exactly what you get when you query for an iterator. Observe:
map<string, int> my_map;
my_map["Hello"] = 1;
my_map["world"] = 2;
for (map<string, int>::const_iterator i = my_map.begin(); i != my_map.end(); ++i)
cout << i->first << ": " << i->second << endl;
Just like any other iterator type, the map iterator behaves like a pointer to a collection element, and for map, this is a std::pair, where first maps to the key and second maps to the value.
std::map uses a binary search internally when you call its find() method or use operator[], but you shouldn't ever need to access the tree representation directly.
One big trick you may be missing is that the end() iterator does not need to point to anything. It can be NULL or any other special value.
The ++ operator sets an iterator to the same special value when it goes past the end of the map. Then everything works.
To implement ++ you might need to keep next/prev pointers in each node, or you could walk back up the tree to find the next node by comparing the node you just left to the parent's right-most node to see if you need to walk to that parent's node, etc.
Don't forget that the iterators to a map should stay valid during insert/erase operations (as long as you didn't erase the iterator's current node).