Skip to main content
deleted 18 characters in body
Source Link
Mark H
  • 2.3k
  • 13
  • 19

When you write a container, there are two concepts to consider: the container and what goes in the container. It can be much simpler to separate these concepts in your code--for example, into separate classes. I find it easier to reason about a container as a separate entity rather than holding on to one item in the container and manipulating the rest of the collection through that one item. It's the difference between trying to carry a chain by holding on to one link versus winding the whole chain around a spool and carrying the spool. Sure, the spool adds weight, but the savings in effort make it worth it.

To see how to make a spool, let's separate your class into two classes LinkedList and LinkedListNode.

struct LinkedListNode
{
    int value;
    LinkedListNode* next;
    LinkedListNode(int new_value, LinkListNode* new_next) : value(new_value), next(new_next) {}
}

class LinkedList
{
    private:
        LinkedListNode* head;
        LinkedListNode* tail;


    public:
        LinkedList() : head(nullptr), tail(nullptr), current(nullptr) {}
        void add(int value);
        LinkedListNode* find(int value);
}

At a cost of a single extra pointer, we can now have an add method that does not need to search for the end of the list.

void LinkedList::add(int value)
{
    if(head)
    {
        tail->next = new LinkedList(value, nullptr);
        tail = tail->next;
    }
    else
    {
        head = new LinkedList(value, nullptr);
        tail = head;
    }
}

This also allows for other methods like find() to be written as loops instead of recursion. If a LinkedList has 1,000,000 elements and the compiler doesn't happen to optimize the recursive calls into loops, your program will crash with a stack overflow error. One way to do this would be

LinkedListNode* LinkedList::find(int value)
{
    for(LinkedListNode* node = head; node; node = node->next)
    {
        if(node->value == value)
        {
            return node;
        }
    }
    return nullptr;
}

I'm not saying recursion is bad. Sometimes a recursive algorithm is easier to reason about; sometimes a looping algorithm is easier. These separated classes give you the option to implement either as needed.

When you write a container, there are two concepts to consider: the container and what goes in the container. It can be much simpler to separate these concepts in your code--for example, into separate classes. I find it easier to reason about a container as a separate entity rather than holding on to one item in the container and manipulating the rest of the collection through that one item. It's the difference between trying to carry a chain by holding on to one link versus winding the whole chain around a spool and carrying the spool. Sure, the spool adds weight, but the savings in effort make it worth it.

To see how to make a spool, let's separate your class into two classes LinkedList and LinkedListNode.

struct LinkedListNode
{
    int value;
    LinkedListNode* next;
    LinkedListNode(int new_value, LinkListNode* new_next) : value(new_value), next(new_next) {}
}

class LinkedList
{
    private:
        LinkedListNode* head;
        LinkedListNode* tail;


    public:
        LinkedList() : head(nullptr), tail(nullptr), current(nullptr) {}
        void add(int value);
        LinkedListNode* find(int value);
}

At a cost of a single extra pointer, we can now have an add method that does not need to search for the end of the list.

void LinkedList::add(int value)
{
    if(head)
    {
        tail->next = new LinkedList(value, nullptr);
        tail = tail->next;
    }
    else
    {
        head = new LinkedList(value, nullptr);
        tail = head;
    }
}

This also allows for other methods like find() to be written as loops instead of recursion. If a LinkedList has 1,000,000 elements and the compiler doesn't happen to optimize the recursive calls into loops, your program will crash with a stack overflow error. One way to do this would be

LinkedListNode* LinkedList::find(int value)
{
    for(LinkedListNode* node = head; node; node = node->next)
    {
        if(node->value == value)
        {
            return node;
        }
    }
    return nullptr;
}

I'm not saying recursion is bad. Sometimes a recursive algorithm is easier to reason about; sometimes a looping algorithm is easier. These separated classes give you the option to implement either as needed.

When you write a container, there are two concepts to consider: the container and what goes in the container. It can be much simpler to separate these concepts in your code--for example, into separate classes. I find it easier to reason about a container as a separate entity rather than holding on to one item in the container and manipulating the rest of the collection through that one item. It's the difference between trying to carry a chain by holding on to one link versus winding the whole chain around a spool and carrying the spool. Sure, the spool adds weight, but the savings in effort make it worth it.

To see how to make a spool, let's separate your class into two classes LinkedList and LinkedListNode.

struct LinkedListNode
{
    int value;
    LinkedListNode* next;
    LinkedListNode(int new_value, LinkListNode* new_next) : value(new_value), next(new_next) {}
}

class LinkedList
{
    private:
        LinkedListNode* head;
        LinkedListNode* tail;


    public:
        LinkedList() : head(nullptr), tail(nullptr) {}
        void add(int value);
        LinkedListNode* find(int value);
}

At a cost of a single extra pointer, we can now have an add method that does not need to search for the end of the list.

void LinkedList::add(int value)
{
    if(head)
    {
        tail->next = new LinkedList(value, nullptr);
        tail = tail->next;
    }
    else
    {
        head = new LinkedList(value, nullptr);
        tail = head;
    }
}

This also allows for other methods like find() to be written as loops instead of recursion. If a LinkedList has 1,000,000 elements and the compiler doesn't happen to optimize the recursive calls into loops, your program will crash with a stack overflow error. One way to do this would be

LinkedListNode* LinkedList::find(int value)
{
    for(LinkedListNode* node = head; node; node = node->next)
    {
        if(node->value == value)
        {
            return node;
        }
    }
    return nullptr;
}

I'm not saying recursion is bad. Sometimes a recursive algorithm is easier to reason about; sometimes a looping algorithm is easier. These separated classes give you the option to implement either as needed.

Source Link
Mark H
  • 2.3k
  • 13
  • 19

When you write a container, there are two concepts to consider: the container and what goes in the container. It can be much simpler to separate these concepts in your code--for example, into separate classes. I find it easier to reason about a container as a separate entity rather than holding on to one item in the container and manipulating the rest of the collection through that one item. It's the difference between trying to carry a chain by holding on to one link versus winding the whole chain around a spool and carrying the spool. Sure, the spool adds weight, but the savings in effort make it worth it.

To see how to make a spool, let's separate your class into two classes LinkedList and LinkedListNode.

struct LinkedListNode
{
    int value;
    LinkedListNode* next;
    LinkedListNode(int new_value, LinkListNode* new_next) : value(new_value), next(new_next) {}
}

class LinkedList
{
    private:
        LinkedListNode* head;
        LinkedListNode* tail;


    public:
        LinkedList() : head(nullptr), tail(nullptr), current(nullptr) {}
        void add(int value);
        LinkedListNode* find(int value);
}

At a cost of a single extra pointer, we can now have an add method that does not need to search for the end of the list.

void LinkedList::add(int value)
{
    if(head)
    {
        tail->next = new LinkedList(value, nullptr);
        tail = tail->next;
    }
    else
    {
        head = new LinkedList(value, nullptr);
        tail = head;
    }
}

This also allows for other methods like find() to be written as loops instead of recursion. If a LinkedList has 1,000,000 elements and the compiler doesn't happen to optimize the recursive calls into loops, your program will crash with a stack overflow error. One way to do this would be

LinkedListNode* LinkedList::find(int value)
{
    for(LinkedListNode* node = head; node; node = node->next)
    {
        if(node->value == value)
        {
            return node;
        }
    }
    return nullptr;
}

I'm not saying recursion is bad. Sometimes a recursive algorithm is easier to reason about; sometimes a looping algorithm is easier. These separated classes give you the option to implement either as needed.