Tag Archives: Boost

Find and replace in C++

The standard C++ string class has 39 member functions, but never the one you need. A find-and-replace function one of those that is missing. Fortunately it can easily be implemented in terms of the find() and replace() methods:

#include <string>
#include <iostream>
#include <climits>

std::string string_replace(const std::string& str, const std::string& match, 
        const std::string& replacement, unsigned int max_replacements = UINT_MAX)
{
    size_t pos = 0;
    std::string newstr = str;
    unsigned int replacements = 0;
    while ((pos = newstr.find(match, pos)) != std::string::npos
            && replacements < max_replacements)
    {
         newstr.replace(pos, match.length(), replacement);
         pos += replacement.length();
         replacements++;
    }
    return newstr;
}

The Boost library has replace_all(), which does the replacement in-place, modifying the original string.

The pystring library has replace(), which not surprisingly works the same way as the Python string.replace() method.

ELIZA in C++

Introduction

ELIZA was a chatterbot program developed by Joseph Weizenbaum at MIT Artificial Intelligence Laboratory in the 1960s. Although very simple in design, Weizenbaum was dismayed to find that people who talked to ELIZA often believed that the program actually understood them, and his secretary even asked to be left alone with ELIZA so that she could have a real conversation.

The original program was written in a variant of LISP, but was later translated to other languages, and became very popular with he rise of personal computers when it appeared in books of BASIC games, which was where I first saw it. This post contains an implementation of the program in C++.

How it works

ELIZA’s operation is based on pattern matching and the selection of ready-made responses. The program has a list of patterns to match with the user input, and for each pattern there are a number of response templates. Below are some examples:

    el.responds_to("Can you (.*)")
        .with("Don't you believe that I can %1%?")
        .with("You want me to be able to %1%?");

    el.responds_to("Can I ([^\\?]*)\\??")
        .with("Perhaps you don't want to %1%.")
        .with("Do you want to be able to %1%?")
        .with("If you could %1%, would you?");

    el.responds_to("You are (.*)")
        .with("Why do you think I am %1%?")
        .with("Does it please you to think that I'm %1%?")
        .with("Perhaps you would like be %1%.")
        .with("Do you sometimes wish you were %1%?");

    el.responds_to("I don'?t (.*)")
        .with("Don't you really %1%?")
        .with("Why don't you %1%?")
        .with("Do you wish to be able to %1%?");

As C++ isn’t very good with data literals I have used some proxy class magic to make a fluent syntax for defining the patterns and response templates.

The response templates contain placeholders for expansion (the %1% symbols) and these are filled in using the text captured by the pattern.

The whole algorithm for processing the input and generating the output requires just two small functions, which are shown below:

std::string eliza::translate(const std::string& input) const
{
    std::vector<std::string> words;
    boost::split(words, input, boost::is_any_of(" \t"));
    for (auto& word : words) {
        boost::algorithm::to_lower(word);
        for (auto& trans : translations_) {
            if (trans.first == word) {
                word = trans.second;
                break;
            }
        }
    }
    return boost::algorithm::join(words, " ");
}

std::string eliza::respond(const std::string& input) const
{
    std::string response;
    for (auto& ex : exchanges_) {
        boost::smatch m;
        if (boost::regex_search(input, m, ex.prompt_)) {
            response = ex.responses_[rand() % ex.responses_.size()];
            if (m.size() > 1 && response.find("%1%") != response.npos) {
                std::string translation = translate(std::string(m[1].first, m[1].second));
                boost::trim_right_if(translation, boost::is_punct());
                response = boost::str(boost::format(response) % translation);
            }
            break;
        }
    }
    return response;
}

The respond() function first goes through the list of patterns and finds the first one that matches. From that it randomly selects a response template. If the pattern captured some text, and the response has placeholders for replacement, these are filled in. Before using the captured text, it is passed to translate(), which converts pronouns, changing "I" to "you" "my" to "your" and so on. It is this translation, and the incorporation of captured phrases from the input in the response, that makes ELIZA surprisingly convincing.

There is a lot of scope for improving the program, not only by adding more patterns and responses, but also by making the parsing more sophisticated (for example collapsing similar inputs to a canonical form), adding a more capable, recursive templating system, and possibly connecting the program to external data sources.

The Code

This is the header file:

#ifndef ELIZA_HPP
#define ELIZA_HPP

#include <string>
#include <vector>
#include <boost/regex.hpp>

namespace MB
{

struct exchange
{
    boost::regex prompt_;
    std::vector<std::string> responses_;
    explicit exchange(const std::string& prompt)
      : prompt_(prompt, boost::regex::icase)
    {
    }
};

class exchange_builder;

class eliza
{
    std::string name_;
    std::vector<exchange> exchanges_;
    std::vector<std::pair<std::string, std::string> > translations_;
public:
    eliza(const std::string& name = "Eliza")
      : name_(name)
    {
        add_translations();
    }
    const std::string& name() const
    {
        return name_;
    }
    exchange_builder responds_to(const std::string& prompt);
    void add_exchange(const exchange& ex)
    {
        exchanges_.push_back(ex);
    }
    std::string respond(const std::string& input) const;
private:
    void add_translations();
    std::string translate(const std::string& input) const;
};

class exchange_builder
{
    friend eliza;
    eliza& eliza_;
    exchange exchange_;

    exchange_builder(eliza& el, const std::string& prompt)
      : eliza_(el), exchange_(prompt)
    {
    }

public:
    ~exchange_builder()
    {
        eliza_.add_exchange(exchange_);
    }

    exchange_builder& with(const std::string& response)
    {
        exchange_.responses_.push_back(response);
        return *this;
    }
};

}; // namespace MB

#endif // ELIZA_HPP

The implementation:

#include <cstdlib>

#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>

#include "eliza.hpp"

using namespace MB;

exchange_builder eliza::responds_to(const std::string& prompt)
{
    return exchange_builder(*this, prompt);
}

void eliza::add_translations()
{
    translations_.push_back(std::make_pair("am", "are"));
    translations_.push_back(std::make_pair("was", "were"));
    translations_.push_back(std::make_pair("i" , "you"));
    translations_.push_back(std::make_pair("i'd", "you would"));
    translations_.push_back(std::make_pair("you'd", "I would"));
    translations_.push_back(std::make_pair("you're", "I am"));
    translations_.push_back(std::make_pair("i've", "you have"));
    translations_.push_back(std::make_pair("i'll", "you will"));
    translations_.push_back(std::make_pair("i'm", "you are"));
    translations_.push_back(std::make_pair("my", "your"));
    translations_.push_back(std::make_pair("are", "am"));
    translations_.push_back(std::make_pair("you've", "I have"));
    translations_.push_back(std::make_pair("you'll", "I will"));
    translations_.push_back(std::make_pair("your", "my"));
    translations_.push_back(std::make_pair("yours", "mine"));
    translations_.push_back(std::make_pair("you", "me"));
    translations_.push_back(std::make_pair("me", "you"));
    translations_.push_back(std::make_pair("myself", "yourself"));
    translations_.push_back(std::make_pair("yourself", "myself"));

}

std::string eliza::translate(const std::string& input) const
{
    std::vector<std::string> words;
    boost::split(words, input, boost::is_any_of(" \t"));
    for (auto& word : words) {
        boost::algorithm::to_lower(word);
        for (auto& trans : translations_) {
            if (trans.first == word) {
                word = trans.second;
                break;
            }
        }
    }
    return boost::algorithm::join(words, " ");
}

std::string eliza::respond(const std::string& input) const
{
    std::string response;
    for (auto& ex : exchanges_) {
        boost::smatch m;
        if (boost::regex_search(input, m, ex.prompt_)) {
            response = ex.responses_[rand() % ex.responses_.size()];
            if (m.size() > 1 && response.find("%1%") != response.npos) {
                std::string translation = translate(std::string(m[1].first, m[1].second));
                boost::trim_right_if(translation, boost::is_punct());
                response = boost::str(boost::format(response) % translation);
            }
            break;
        }
    }
    return response;
}

An example program, which includes the initialisation of ELIZA with a default set of patterns and responses:

#include <iostream>

#include "eliza.hpp"

static void add_responses(MB::eliza& el);

int main()
{
    MB::eliza el;

    add_responses(el);

    std::cout << "Hello. I'm " << el.name() << ". How are you feeling today?\n";
    std::string input;
    while (std::getline(std::cin, input) && input != "quit") {
        std::cout << el.respond(input) << "\n";
    }
}

static void add_responses(MB::eliza& el)
{
    el.responds_to("Can you (.*)")
        .with("Don't you believe that I can %1%?")
        .with("You want me to be able to %1%?");

    el.responds_to("Can I ([^\\?]*)\\??")
        .with("Perhaps you don't want to %1%.")
        .with("Do you want to be able to %1%?")
        .with("If you could %1%, would you?");

    el.responds_to("You are (.*)")
        .with("Why do you think I am %1%?")
        .with("Does it please you to think that I'm %1%?")
        .with("Perhaps you would like be %1%.")
        .with("Do you sometimes wish you were %1%?");

    el.responds_to("I don'?t (.*)")
        .with("Don't you really %1%?")
        .with("Why don't you %1%?")
        .with("Do you wish to be able to %1%?");

    el.responds_to("I feel (.*)")
        .with("Does it trouble you to feel %1%?")
        .with("Do you often feel %1%?")
        .with("Do you enjoy feeling %?%")
        .with("When do you usually feel %1%?")
        .with("When you feel %1%, what do you do?");

    el.responds_to("Why don'?t you ([^\\?]*)\\??")
        .with("Do you really believe I don't %1%?")
        .with("Perhaps in good time I will %1%.")
        .with("Do you want me to %1%?");

    el.responds_to("Why can'?t I ([^\\?]*)\\??")
        .with("Do you think you should be able to %1%?")
        .with("Why can't you?");

    el.responds_to("Are you ([^\\?]*)\\??")
        .with("Why are you interested in whether I am %1%?")
        .with("Would you prefer it if I were not %1%?")
        .with("Perhaps in your fantasies I am %1%.");

    el.responds_to("I can'?t (.*)")
        .with("How do you know you can't %1%?")
        .with("Have you tried to %1%?")
        .with("Perhaps now you can %1%");

    el.responds_to("I am (.*)")
        .with("Did you come to me because you are %1%?")
        .with("How long have you been %1%?");

    el.responds_to("I'?m (.*)")
        .with("Do you believe it is normal to be %1%?")
        .with("Do you enjoy being %1%?");

    el.responds_to("You (.*)")
        .with("We were discussing you - not me.")
        .with("Oh, I %1%?");

    el.responds_to("I want (.*)")
        .with("What would it mean to you if you got %1%?")
        .with("Why do you want %1%?")
        .with("Suppose you soon got %1%?")
        .with("What if you never got %1%?")
        .with("I sometimes also want %1%");

    el.responds_to("What (.*)")
        .with("Why do you ask?")
        .with("Does that question interest you?")
        .with("What answer would please you the most?")
        .with("What do you think?")
        .with("Are such questions on your mind often?")
        .with("What is it that you really want to know?")
        .with("Have you asked anyone else?")
        .with("Have you asked such questions before?")
        .with("What else comes to mind when you ask that?");

    el.responds_to("Because")
        .with("Is that the real reason?")
        .with("Don't any other reasons come to mind?")
        .with("Does that reason explain anything else?")
        .with("What other reasons might there be?");

    el.responds_to("Sorry")
        .with("There are many times when no apology is needed.")
        .with("What feelings do you have when you apologize?");

    el.responds_to("Dream")
        .with("What does that dream suggest to you?")
        .with("Do you dream often?")
        .with("What persons appear in your dreams?")
        .with("Are you disturbed by your dreams?");

    el.responds_to("^Hello")
        .with("How do you do... Please state your problem.");

    el.responds_to("^Maybe")
        .with("You don't seem quite certain.")
        .with("Why the uncertain tone?")
        .with("Can't you be more positive?")
        .with("You aren't sure?")
        .with("Don't you know?");

    el.responds_to("^No[.!]?$")
        .with("Are you saying that just to be negative?")
        .with("You are being a bit negative.")
        .with("Why not?")
        .with("Are you sure?")
        .with("Why no?");

    el.responds_to("Your (.*)")
        .with("Why are you concerned about my %1%?")
        .with("What about your own %1%?");

    el.responds_to("Always")
        .with("Can you think of a specific example?")
        .with("When?")
        .with("Really, always?");

    el.responds_to("Think (.*)")
        .with("What are you thinking of?")
        .with("Do you really think so?")
        .with("But you are not sure %1%?")
        .with("Do you doubt %1%?");

    el.responds_to("Alike")
        .with("In what way?")
        .with("What resemblance do you see?")
        .with("What does the similarity suggest to you?")
        .with("what other connections do you see?")
        .with("Could there really be some connection?")
        .with("How?");

    el.responds_to("^Yes[.!]?$")
        .with("You seem quite positive.")
        .with("Are you sure?")
        .with("I see.")
        .with("I understand.");

    el.responds_to("Friend")
        .with("Why do you bring up the topic of friends?")
        .with("Do your friends worry you?")
        .with("Do your friends pick on you?")
        .with("Are you sure you have any friends?")
        .with("Do you impose on your friends?")
        .with("Perhaps your love for friends worries you.");

    el.responds_to("Computer")
        .with("Do computers worry you?")
        .with("Are you talking about me in particular?")
        .with("Are you frightened by machines?")
        .with("Why do you mention computers?")
        .with("What do you think machines have to do with your problem?")
        .with("Don't you think computers can help people?")
        .with("What is it about machines that worries you?");

    el.responds_to("(.*)")
        .with("Say, do you have any psychological problems?")
        .with("What does that suggest to you?")
        .with("I see.")
        .with("I'm not sure I understand you fully.")
        .with("Come come elucidate your thoughts.")
        .with("Can you elaborate on that?")
        .with("That is quite interesting.");
}

Finally, here is a makefile:

ALL := eliza
all : $(ALL)
eliza : eliza.cpp eliza.hpp main.cpp
$(CXX) $(CXXFLAGS) -g -std=c++11 eliza.cpp main.cpp -o eliza -lboost_regex
.PHONY : clean
clean :
$(RM) $(ALL)

	

Finding a substring in C++

Say we have the following string:

std::string str = "The quick brown fox jumps over the lazy dog";

And we want to find "fox"

Method 1: Use std::string::find

bool found = str.find("fox") != str.npos;

This returns an iterator pointing at the beginning of the substring if found, or std::string::npos otherwise.

Method 2: Use boost::contains

#include <boost/algorithm/string/predicate.hpp>
found = boost::contains(str, "fox");

This has the advantage of returning a boolean.

Reference: Function contains

Method 3: Use pystring::find

#include <pystring.h>
found = pystring::find(str, "fox") != -1;

Reference: imageworks/pystring

Looping over the keys and values in a map

Say we have this map:

#include <map>
#include <string>

typedef std::map<std::string, unsigned int> map_string_to_uint;
const size_t n = 12;

map_string_to_uint monthdays;
const char *months[] 
        = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
unsigned int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
for (int m = 0; m < n; ++m) {
    monthdays[months[m]] = days[m];
}

And we want to loop over the keys and values.

Method 1: Use an iterator

    for (map_string_to_uint::const_iterator it = monthdays.begin(); it != monthdays.end(); ++it) {
        std::cout << it->first << ": " << it->second << "\n";
    }

Method 2: Use a range-based for loop (C++11)

    for (const auto& pair : monthdays) {
        std::cout << pair.first << ": " << pair.second << "\n";
    }

Method 3: Use Boost.Foreach

#include <boost/foreach.hpp>

BOOST_FOREACH(const map_string_to_uint::value_type& pair, monthdays) {
    std::cout << pair.first << ": " << pair.second << "\n";
}

Reference: Boost.Foreach

How to create, launch, and join a thread in C++

Method 1: Boost.Thread

#include <iostream>
#include <string>
#include <boost/thread.hpp>

class Hello
{
public:
	void operator()(const std::string& msg) const
	{
		std::cout << msg << "\n";
	}
};

int main()
{
	boost::thread t = boost::thread(Hello(), "Hello");
	t.join();
}

Method 2: pthreads (POSIX)

#include <iostream>

#include <pthread.h>

void* hello(void *msg)
{
    std::cout << static_cast<const char*>(msg) << "\n";
    return NULL;
}

int main()
{
    pthread_t thread;
    pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    char msg[] = "Hello";
	int status = pthread_create(&thread, &attr, hello, msg);
	pthread_attr_destroy(&attr);
	if (status != 0) {
        std::cerr << "Failed to create thread\n";
        return 1;
	}
    status = pthread_join(thread, NULL);
    if (status != 0) {
        std::cerr << "Failed to join thread\n";
        return 1;
    }
}

Method 3: Windows threads

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <Windows.h>
#include <process.h>

#include <iostream>

void hello(void *msg)
{
	std::cout << static_cast<const char*>(msg) << "\n";
}

int main()
{
	char msg[] = "Hello";
	HANDLE thread = reinterpret_cast<HANDLE>(_beginthread(hello, 0, msg));
	if (thread == INVALID_HANDLE_VALUE) {
		std::cerr << "Failed to create thread\n";
		return 1;
	}
	WaitForSingleObject(thread, INFINITE);
}

Method 4: std::thread (C++11)

#include <string>
#include <iostream>
#include <thread>

void hello(const std::string& msg)
{
    std::cout << msg << "\n";
}

int main()
{
    std::thread t(hello, "Hello");
    t.join();
}

String formatting in C++

Method 1: Use vsnprintf() (C++11 and POSIX)

#include <iostream>
#include <vector>
#include <string>
#include <cstdarg>
#include <cstring>

std::string format(const std::string& format, ...)
{
    va_list args;
    va_start (args, format);
    size_t len = std::vsnprintf(NULL, 0, format.c_str(), args);
    va_end (args);
    std::vector<char> vec(len + 1);
    va_start (args, format);
    std::vsnprintf(&vec[0], len + 1, format.c_str(), args);
    va_end (args);
    return &vec[0];
}

int main()
{
    char str[] = "%s => %d";
    std::cout << string_format(str, "apples", 7) << "\n";
}

Method 2: Use Boost.Format

#include <iostream>
#include <boost/format.hpp>

int main()
{
    char str[] = "%s => %d";
    std::cout <<  boost::str(boost::format(str) % "apples" % 7) << "\n";
}

Reference: Boost Format library

Method 3: Use folly::format()

#include <iostream>
#include <folly/Format.h>

int main()
{
    char str[] = "{} => {}";
    std::cout <<  folly::format(str, "apples", 7) << "\n";
}

Reference: folly/Format.h

Dynamically allocated 2-d array in C++

Method 1: Use an array of arrays

#include <iostream>

int main()
{
    const size_t rows = 10, columns = 10;
    unsigned int r, c;

    /* Initialize */
    int** array1 = new int*[rows];
    for (r = 0; r < rows; ++r) {
        array1[r] = new int[columns];
    }

    /* Write */
    for (r = 0; r < rows; ++r) {
        for (c = 0; c < columns; ++c) {
            array1[r] = r * c;
        }
    }

    /* Read */
    std::cout << "7 times 8 equals " << array1[7][8] << "\n";

    /* Delete */
    for (r = 0; r < rows; ++r) {
        delete[] array1[r];
    }
    delete[] array1;
}

Method 2: Use a single array

#include <iostream>

int main()
{
    const size_t rows = 10, columns = 10;
    unsigned int r, c;

    /* Initialize */
    int* array2 = new int[rows * columns];

    /* Write */
    for (r = 0; r < rows; ++r) {
        for (c = 0; c < columns; ++c) {
            array2[r * columns + c] = r * c;
        }
    }

    /* Read */
    std::cout << "7 times 8 equals " << array2[7 * columns + 8] << "\n";

    /* Delete */
    delete[] array2;
}

Note that the formula for accessing a cell is row * columns + row.

Method 3: Use a vector of vectors

#include <iostream>
#include <vector>

int main()
{
    const size_t rows = 10, columns = 10;
    unsigned int r, c;

    /* Initialize */
    std::vector<std::vector<int> > array3(rows,
            std::vector<int>(columns));

    /* Write */
    for (r = 0; r < rows; ++r) {
        for (c = 0; c < columns; ++c) {
            array3[r] = r * c;
        }
    }

    /* Read */
    std::cout << "7 times 8 equals " << array3[7][8] << "\n";
}

Notice the use of the vector constructor that makes rows copies of a row vector.

Method 4: Use Boost.MultiArray

#include <iostream>
#include <boost/multi_array.hpp>

int main()
{
    const size_t rows = 10, columns = 10;

    /* Initialize */
    boost::multi_array<int, 2> array4(boost::extents[rows][columns]);
    boost::multi_array<int, 2>::index r, c;

    /* Write */
    for (r = 0; r < rows; ++r) {
        for (c = 0; c < columns; ++c) {
            array4[r] = r * c;
        }
    }

    /* Read */
    std::cout << "7 times 8 equals " << array4[7][8] << "\n";
}

Reference:The Boost Multidimensional Array Library

Populate a map with literal values

Method 1: Use parallel arrays

#include <map>
#include <string>

int main()
{
    typedef std::map<std::string, unsigned int> map_string_to_uint;
    const size_t n = 12;

    map_string_to_uint monthdays1;
    const char *months1[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
        "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
    unsigned int days1[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    for (int m = 0; m < n; ++m) {
        monthdays1[months1[m]] = days1[m];
    }
}

Method 2: Use an array of structs

#include <map>
#include <string>

int main()
{
    typedef std::map<std::string, unsigned int> map_string_to_uint;
    const size_t n = 12;

    map_string_to_uint monthdays2;
    struct Monthdays {
        const char *month;
        unsigned int days;
    };
    Monthdays months2[] =  {{"JAN", 31}, {"FEB", 28}, {"MAR", 31},
        {"APR", 30}, {"MAY", 31}, {"JUN", 30}, {"JUL", 31}, {"AUG", 31}, {"SEP", 30},
        {"OCT", 31}, {"NOV", 30}, {"DEC", 31}};
    for (int m = 0; m < n; ++m) {
        monthdays2[months2[m].month] = months2[m].days;
    }
}

Method 3: Use an array of std::pairs and the range constructor

#include <map>
#include <string>

int main()
{
    typedef std::map<std::string, unsigned int> map_string_to_uint;
    const size_t n = 12;

    std::pair<std::string, unsigned int> months3[] = {
        std::make_pair("JAN", 31), std::make_pair("FEB", 28), std::make_pair("MAR", 31),
        std::make_pair("APR", 30), std::make_pair("MAY", 31), std::make_pair("JUN", 30),
        std::make_pair("JUL", 31), std::make_pair("AUG", 31), std::make_pair("SEP", 30), 
        std::make_pair("OCT", 31), std::make_pair("NOV", 30), std::make_pair("DEC", 31)};
    map_string_to_uint monthdays3(months3, months3 + n);
}

Method 4: Use Boost.Assign

#include <map>
#include <string>
#include <boost/assign.hpp>

int main()
{
    typedef std::map<std::string, unsigned int> map_string_to_uint;

    map_string_to_uint monthdays4 = boost::assign::map_list_of ("JAN", 31) ("FEB", 28) ("MAR", 31)
        ("APR", 30) ("MAY", 31) ("JUN", 30) ("JUL", 31) ("AUG", 31) ("SEP", 30) 
        ("OCT", 31) ("NOV", 30) ("DEC", 31);
}

Method 5: Use a C++11 initializer

#include <map>
#include <string>

int main()
{
    typedef std::map<std::string, unsigned int> map_string_to_uint;

    /* Method 5: Use a C++11 initializer */
    map_string_to_uint monthdays5 =  {{"JAN", 31}, {"FEB", 28}, {"MAR", 31},
        {"APR", 30}, {"MAY", 31}, {"JUN", 30}, {"JUL", 31}, {"AUG", 31}, {"SEP", 30},
        {"OCT", 31}, {"NOV", 30}, {"DEC", 31}};
}

Reverse an array without affecting special characters

I found this little programming challenge here. The task is to take a string and reverse only the alphabetic characters in it, leaving any other characters unaffected.
So, given the string:

a,b$c

The program should output:

c,b$a

I think that the best method is to walk a pair of pointers inwards from both ends of the string at the same time, examining the characters they point to. If the character being examined at either end is not a letter, keep going inwards. Once both characters are letters, swap them. Finish when the pointers meet in the middle.

Here is my implementation in C:

#include <stdio.h>
#include <string.h>
#include <ctype.h>

void swap(char *a, char *b)
{
    char temp = *a;
    *a = *b;
    *b = temp;
}

void reverse(char *str)
{
    char *p1 = str;
    char *p2 = str + strlen(str) - 1;
    while (p1 < p2) {
        while (!isalpha(*p1)) p1++;
        while (!isalpha(*p2)) p2--;
        if (p1 < p2) {
            swap(p1, p2);
            p1++;
            p2--;
        }
    }
}

int main(void)
{
    char str[6];
    strcpy(str, "a,b$c");
    reverse2(str);
    printf("%s\n", str);
    return 0;
}

While I was writing this it occurred to me that it would be nice if you could have a filtered view into the string that hid the special characters, and then you could just reverse what you can see. Then I remembered boost::filter_iterator, and found that it can do just that, so here’s my C++ version using filter_iterator from Boost:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <boost/iterator/filter_iterator.hpp>

struct is_alpha
{
    bool operator()(char c)
    {
        return std::isalpha(c);
    }
};

void reverse(char *str)
{
    const size_t len = std::strlen(str);
    typedef boost::filter_iterator<is_alpha, char*> FilterIter;
    is_alpha predicate;
    FilterIter first(predicate, str, str + len);
    FilterIter last(predicate, str + len, str + len);
    std::reverse(first, last);
}

int main()
{
    char str[6];
    strcpy(str, "a,b$c");
    reverse(str);
    std::cout << str << "\n";
}

Reference: Filter Iterator