The problem here is that you have natural iterators whose elements can be more than one character, but you want to do an operation over just the sequence of characters. So I would define another iterator type that behaves the way we need, using boost::iterator_facade
. (Often for this sort of thing boost::iterator_adaptor
is even more convenient, but in this case it wouldn't help.)
The diagram suggests a raw C-like struct and pointer setup, so I'll assume the custom doubly linked list is defined like this:
struct list_node {
list_node* prev;
list_node* next;
const char* data;
};
class LinkedList {
public:
list_node* head() const;
list_node* tail() const;
// ...
};
The custom iterator class needs to contain a list_node*
pointer and a pointer to an element of the char
array.
#include <boost/iterator_facade.hpp>
class ListCharIter :
public boost::iterator_facade<
ListCharIter, // Derived class for CRTP
const char, // Element type
std::bidirectional_iterator_tag > // Iterator category
{
public:
ListCharIter() : m_node(nullptr), m_ch(nullptr) {}
// "Named constructors":
static ListCharIter begin(const LinkedList& listobj);
static ListCharIter end(const LinkedList& listobj);
private:
list_node* m_node;
const char* m_ch;
ListCharIter(list_node* node, const char* where)
: m_node(node), m_ch(where) {}
// Methods iterator_facade will use:
char dereference() const;
bool equal(const ListCharIter& other) const;
void increment();
void decrement();
// And allow boost to use them:
friend class boost::iterator_core_access;
};
For a past-the-end iterator only, we'll allow m_ch
to point at the last node's terminating '\0'
. In the special case of a list with no elements, we'll set both members null for the single iterator which is both beginning and end and cannot be dereferenced.
inline ListCharIter ListCharIter::begin(const LinkedList& listobj)
{
list_node* node = listobj.head();
const char* str = nullptr;
if (node) {
str = node->data;
}
return ListCharIter(node, str);
}
inline ListCharIter ListCharIter::end(const LinkedList& listobj)
{
list_node* node = listobj.tail();
const char* nul = nullptr;
if (node) {
nul = node->data;
while (*nul != '\0') ++nul; // Find the '\0'.
}
return ListCharIter(node, nul);
}
dereference()
and equal()
are trivial:
inline char ListCharIter::dereference() const
{ return *m_ch; }
inline bool ListCharIter::equal(const ListCharIter& other) const
{ return this->m_node == other.m_node && this->m_ch == other.m_ch; }
And finally, to step forward or backward, the basic idea is to change only m_ch
if that makes sense, or change m_node
otherwise.
inline void ListCharIter::increment()
{
++m_ch;
// If m_node->next is null, we're advancing
// past the end of the entire list.
while (*m_ch == '\0' && m_node->next) {
m_node = m_node->next;
m_ch = m_node->data; // Start of new node.
// while loop repeats if m_node contains "".
}
}
inline void ListCharIter::decrement()
{
if (m_ch == m_node->data) {
// Already at the start of this node.
do {
m_node = m_node->prev;
m_ch = m_node->data; // Start of new node.
// while loop repeats if m_node contains "".
} while (*m_ch == '\0');
// Find the char before the terminating nul.
while (m_ch[1] != '\0') ++m_ch;
} else {
--m_ch;
}
}
Now you can use that custom iterator in an ordinary palindrome algorithm (and many other algorithms).
template<typename BidirIter>
bool is_palindrome(BidirIter start, BidirIter stop)
{
for (;;) {
if (start == stop) return true;
if (*start != *stop) return false;
++start;
if (start == stop) return true;
--stop;
}
}
bool is_palindrome(const LinkedList& the_list)
{
return is_palindrome(ListCharIter::begin(the_list),
ListCharIter::end(the_list));
}