Editing a C String In-Place

by Paul Eissen

21 Feb 2020

My least favorite part of a technical interview is the whiteboard exercise, in which yours truly has to stand up and try to come up with a solution to a non-trivial coding challenge while the interviewer(s) stare at my back and marvel at my illegible scribbles and frantic hand-waving.

I've never been able to solve any of these problems in the time allotted, but I'm told the interviewers just want to know how I would reason my way to a solution. I wonder, though, what they're really thinking when I can't even get past main() {}.

Recently, I was asked to write a function to edit a C string in-place with the goal of removing one or more occurrences of a specified character. So if the original string is "google" and 'g' is to be removed, the result will be "oole". I never reached the code-writing stage, but at least I was able to generate a function signature and babble about shifting characters to the left (which turned out to be what the interviewers were looking for, so I wasn't a complete noob):

void
stredit_in_place(char* s, char rmch) {
    // Huh?
}

When I got home, I decided I was going to fill in that function body even if it killed me. [1]

Left Shift

After many hours investigating methods for shifting chars to be kept on top of chars that need to be removed, I stumbled upon the following:

left shift drawing

That left shift operation looked familiar, but I couldn't remember the name of the relevant libc function until I poked around the man pages and came across memmove(). I knew this function needed to be enclosed in some sort of loop, and eventually I came up with the following (minus error checking and casts involving ptrdiff_t):

void
stredit_in_place_ftw(char* s, char rmch)
{
    char* starts = s;
    char* ends = s + strlen(s);

    while (1) {
        if (!(starts = strchr(starts, rmch))) {
            break;
        }
        memmove(starts, (starts + 1), (ends - starts));
    }
}

The loop body looks for the next matching rmch char, and then does the left shift, which we can get away with because memmove() knows how to safely copy overlapping memory segments. My algorithm replaces every rmch char with a left-shifted NUL char so that the result will always be a legal C string.

If there are no rmch matches, then strchr() will visit every char in the original string exactly once until it reaches the trailing NUL char. If all the chars are equal to rmch, then strchr() will always match the first char in the string until all are replaced with NUL chars. In fact, each of the chars in the original string will be visited exactly once by strchr() no matter what.

Not bad for a noob.

Notes

  1. ^ After I wrote my code, I was curious to see if the problem exists online. The closest match I could find is Remove Spaces, but I can't see the solution because I don't have an account on that site.

Creative Commons Attribution-ShareAlike