I have already implemented a templated Linked List which uses unique pointers internally. First I create a ListNode struct that holds this pointer to the next node and the data. Finally, it also has a handy function to turn it to string (for printing later on):
template < typename T>
struct ListNode;
template<typename T >
using ListNodePtr = std::unique_ptr<ListNode<T>>;
template < typename T>
struct ListNode
{
ListNode(T data) : data(data) {}
T data;
ListNodePtr<T> next = nullptr;
std::string str()
{
std::string output;
ListNode<T>* curr = this;
while (curr)
{
output += std::to_string(curr->data) + " ";
curr = curr->next.get();
}
return output;
}
};
The linked list itself stores a smart pointer to a node, which contains all other nodes. Several methods with basic list features have been implemented like inserting an element, deleting at position n, or reversing the list.
template <typename T>
class LinkedList
{
public:
LinkedList() = delete;
LinkedList(const std::vector<T>& data)
{
createListFromVector(data);
}
void createListFromVector(const std::vector<T>& data)
{
if (data.empty())
{
return;
}
ListNodePtr<T> head = nullptr;
ListNode<T>* curr = nullptr;
for (const auto& item : data)
{
if (!head)
{
head = std::make_unique<ListNode<T>>(item);
curr = head.get();
continue;
}
curr->next = std::make_unique<ListNode<T>>(item);
curr = curr->next.get();
}
m_list = std::move(head);
};
// insert node at head
void insertNode(T data)
{
ListNodePtr<T> newNode = std::make_unique<ListNode<T>>(data);
newNode->next = std::move(m_list);
m_list = std::move(newNode);
}
// delete the nth node
void deleteNode(int pos)
{
if (pos < 1) { return; }
ListNode<T>* curr = m_list.get();
while ((pos-1) && curr)
{
curr = curr->next.get();
--pos;
}
if (curr)
{
ListNodePtr<T> next = std::move(curr->next->next);
curr->next = std::move(next);
}
}
// reverse the list iteratively
void reverse()
{
ListNodePtr<T> prev = nullptr;
ListNodePtr<T> curr = std::move(m_list);
ListNodePtr<T> next = nullptr;
while (curr)
{
next = std::move(curr->next);
curr->next = std::move(prev);
prev = std::move(curr);
curr = std::move(next);
}
m_list = std::move(prev);
}
void print() { std::cout << str() << std::endl; };
std::string str() { return getHead()->str(); }
private:
ListNode<T>* getHead() const { return m_list.get(); };
std::unique_ptr<ListNode<T>> m_list = nullptr;
};
As you can see, I implemented some basic functionality in this way, however, this seems way too convoluted, when compared to how I would do this in Python, for example. Things would be so much simpler without being restricted to a unique_ptr.
For example, suppose I want to make a method: toOddThenEven which rearranges the elements in the list - first all odd positions, then all even positions: 1, 7, 2, 14, 5, 32 becomes 1, 2, 5, 7, 14, 32. The first index is considered to be 1 and therefore, odd.
Normally one can accomplish this easily by making some copies of the head of the odds, the head of the evens, and then two more pointers for moving forward through the list. At the end, you can have the odds list: 1->2->5 and the evens list: 7->14->32, which can be merged together by setting the next pointer of the last odd index element to the head of the evens, and returning head of the odds.
Example in Python:
def toOddThenEven(node):
if not node:
return None
headO = node
headE = headO.next
curr = headO
nxt = headE
while(nxt and nxt.next):
# skip adjancent element for both curr and nxt
curr.next = curr.next.next
nxt.next = nxt.next.next
# move forward
nxt = curr.next.next
curr = curr.next
# concatenate odds list and evens list
curr.next = headE
return headO
Now, there is probably a better way to implement this, but, nevertheless, you probably need to make some temporary copies as I've done above, in order to make it work without exploding your brain.
Therefore, a C++ solution, would be to use shared_ptrs instead of unique_ptrs. HOWEVER, in this case, we would use 2 times the memory, for each pointer, thus making our LinkedList data structures NOT OPTIMAL - when compared to a simple implementation of just using raw pointers with new/delete and RAII, such that the user's of our data structures will not be concerned with managing it's memory directly.
So, in case of designing such a data structure - especially when time is short (such as during an interview setting, or demonstration purposes to other developers) it seems like raw pointers are the better choice in this case. Either that or shared_ptr if you don't care about the extra performance hit. Any thoughts on this would be appreciated. Thanks.