177 comments

[ 4.8 ms ] story [ 224 ms ] thread
So when do we get to read part 3? I want to see what "creative" solutions people came up with.
Currently working on that. It's safe to say that your two solutions were the most creative. In my graph of solution proximity, they are both a long way away from all the other solutions.
Awww. I was hoping that with an audience of hackers and such a trivial problem I wouldn't be alone in my creativity. :-/
If you want 'creative' solutions to problems you might want to check out google code jam archives. During the qualifications this year someone wrote a solution in lolcode. Was quite interesting.
Interesting, when I solved the problem originally on my own, I got to the exact same solution just with different variable names and braces. I suspect that this is probably the most common way to solve it.
Yeah, I had almost exactly the same code. I'm sure most people did too. The only other "obvious" algorithm is the n^2 one.
I use do...while instead of the for loop, making the usual nasty C-programmer use of ++ in the right spots, but other than that, my solution is the same.

Edit: Correction... I also copy the '\0' character before exiting instead of making that a separate step. It has to do with the order of things. Here is the code:

   char* read_ptr = s;
   do {
      if (*read_ptr != c) {
         *s++ = *read_ptr;
      }
   } while (*read_ptr++ != '\0');
You can use an inner loop to skip over removable chars in the source string but I didn't think that was n^2. (Code below for reference.) Am I wrong that my solution is n, or is there another obvious n^2 algorithm?

  void condense_by_removing( char *z_terminated , char char_to_remove)
  {
    char * sp;
    char * dp;
    sp = dp = z_terminated;
    while (*sp != '\0') {
      while (*sp == char_to_remove){
        sp++;
      }
      *dp = *sp;
      dp++;
      sp++;
    } 
    *dp = *sp;
  }
1) In TFA, s/cersion/version/, but that's an amusing typo. :-)

2) Initializing inside the for loop requires C99; is that to be considered idiomatic? I didn't think so. I'll be curious how many submissions were either identical to your suggested idiomatic solution or its K&R equiv:

https://gist.github.com/3f4daf24595788258a01

:-)

  1) In TFA, s/cersion/version/,
     but that's an amusing typo.
     :-)
Bother - now fixed. Thanks.
Although I'm not sure "its K&R equivalent" is correct (that's C89 and has "modern" function implementations), C99 is indeed not that common. If only because many compilers don't support it (including Microsoft's).
Bah, I learned my C from K&R 2nd ed. I guess that's not K&R C, is it? :-)
It's... complicated. K&R 2nd ed. (which is an excellent book!) is based on a draft of ANSI C; pre-ANSI C was not standardized but rather based on an informal reading of K&R 1st ed., hence "K&R syntax" and the like.

At least, if I recall correctly.

Am I OCD when I cringe when I see 'p_rite' where he (I think) means 'p_write'? That alone would knock a few points off a candidate doing this test when he's interviewing with me. 'rite' can mean 'write' or 'right' or even 'allright' - why the unnecessary confusion?
You're not alone in feeling the OCD twinge, but are almost certainly alone in being willing to demerit a candidate writing that.
Actually, he's not alone in demeriting =)
Why would he be alone? Why is spelling less important in code than in normal writing? I would argue it is considerably more important.
If you truly think the market for C developers is so favorable to employers that someone who can write the in-place-whitespace-removal function off the top of their heads is easy to find, then by all means, ding candidates for their spelling during interviews. Thanks for making it that much easier for the rest of us to hire.

In the industry I work in --- computer software --- it is ludicrously hard to find people competant in C programming, and I'm inclined to ignore idiosyncratic spelling in source code.

This is not a simple misspelling though. If someone would write 'int countar = 0;' because he doesn't speak English well or whatever, that's fine. But spelling 'write' as 'rite' is indicative of a certain mindset - of favoring a certain quirky sense of beauty or style over clarity. And I didn't say it'd be autoding, just that it lowers my opinion of the author a bit. If the rest was OK that would be noise in between all the other criteria.
Some would argue that if it lowers your opinion of the author it is an "autoding" already (whether you're willing to admit it or not).

Take this scenario: You have a single position you are interviewing people for. You've got two candidates with entirely even qualifications. The only perceivable difference between them is that one of them writes interview code with variable names spelled like in the original post. The other guy spells things the way you would expect. I'm willing to bet you'd end up hiring the second guy. Sounds like an "autoding" to me. Or would you argue that you would not do that?

I don't understand your logic. Two people who are in every respect exactly the same, but one can't spell or at least has weird, non-standard spelling habits. The rational choice here is the person who spells correctly.

My point was, spelling in variable names is only a very minor factor when deciding on hiring or not hiring a candidate. If his reasoning and experience and work ethic etc. are all ok, spelling wouldn't make those factors irrelevant.

When a factor is automatically excluding, that means that no matter how good in other respects the candidate is, I still wouldn't hire him (for example someone with a swastika tattooed on his face applying for a customer-facing job). I never suggested any such a thing anywhere (and I don't think you're claiming I am). So I don't see how you're coming to the conclusion that I think that making spelling mistakes automatically excludes a candidate.

His logic is that you are in fact "dinging" the candidate. He used an artificial scenario to illustrate that.

In reality, you never have two candidates who are absolutely equivalent except for spelling ability. The interview process is extremely high-error, brutally timeboxed, and infamously ineffective. The most important capability you need to assess is "ability to code". You know a priori you're going to do a crappy job assessing that capability.

Whether or not he thinks you should be wasting time on trivialities like variable name spelling (I myself would dodge this problem by using names like "x" and "cp" for utterly trivial, well-known example problems like this), I don't know. My bias is pretty clear.

Let's clarify terminology first - I shouldn't have used the word "autoding" because it's slang and its use in another message board that I visit may not be universally accepted. I meant "an automatic disqualifier" - a condition that would completely negate any positive aspects.

Under that definition, I don't see any way to construe my earlier remarks as meaning that I would such a high importance on spelling of variable names.

I think you exaggerate. If you actually look at advertisements for c/c++ developers, they tend to contain words to the effect, "Must have 4-5 years experience in high speed algorithmic trading", etc. But if you know any good-paying jobs that only require the skill level you suggest, point me in that direction. (I mean that--I can get away from my current huge and horrible codebase. Speaking of which, I remember being appalled by the consistently awful misspelling of virtually everything until someone pointed out that its DOS origins required 8 character limits on filenames. I suspect such charming beasts as atoi and strlen had similar constraints, if only for purposes of saving RAM on PDP 11s.)
In the original C implementation function names could be as long as you wanted, but the compiler only looked at the first six chars.
It's worse than that, actually: the C89/C90 standard allows this behaviour. Yes, many programs would break if compiled with such a theoretically conforming compiler.
I thought it was the linker that truncated. So functions called fredBlogs() and fredBlugs() would be fine as long as they were in the same compilation unit, and not called from any other compilation unit.
I personally would count that against any candidate during an interview. I wouldn't necessarily reject them, but I would definitely count that against them. I work in Toronto, Ontario. English is not my native language, but I always do my best to ensure that my code or documentation is as close to crystal clear as possible. This starts with using correct spelling. Code is written first for humans to read, and to me misspelling in code is no different than poor variable naming. It is the kind of broken window that, when left unchecked or dismissed as "not important", instills a sense of negligence that over time can turn a code base from "decent" to "poor old crappy code".
But if you're talking about a whiteboard coding problem, people are going to come in with different perceptions of what you're trying to test, and for the sake of brevity they'll do things like use shorter names or global variables. I feel like I'd cringe at seeing "rite," but not as much as when somebody thinks it looks impressive to start by showing you the testing code they would write.
The counterpoint to this is that if you are they type of boss who would nit pick a candidate over something like this, he probably wouldn't want to work for you anyway. Especially when you take a quirky/cute spelling and try to expand it into inferring all sorts of other traits.

It reminds me of people who say they would never hire a developer who doesn't write comments. Really? Writing comments is a precious skill that takes years to develop? You just tell someone, if you work here, you need to write comments. Problem solved in one sentence. Same with variable names. Its a standards issue, nothing more, nothing less.

Do you really think that ordering someone to write comments and use good variable names is sufficient to get good results?

The relevant chapters in Code Complete may help you understand how much more complicated the subject is than that. (It may also fix your apparent belief that comments are necessarily a good thing.)

If you want to narrow your shop down to the programmers who can quote chapter and verse from _Code Complete_ and who are inclined to care a lot about how people choose to abbreviate or mangle their variable names, that's totally fine. The rest of us can chop up the pool of developers who actually get stuff done. This isn't just snark; the Venn diagram we're dancing around here favors businesses that hire in my (implied) style.
I wasn't making a recommendation about who to hire. I was telling someone where they had room to learn more about an important topic.

In any case I don't care whether people can quote from Code Complete. What I care about is that they have good enough instincts that they can wind up writing reasonable code that I'd like to maintain. If you don't understand why people would want function names that describe what a function actually does, I don't want to maintain your code. Nor, in the long run, are you likely to be productive.

But it is a bar to meet, not a standard to judge by. Once you've met that bar, I don't care about the difference between OK, and best in the world. I'm on to caring about other things. Like productivity. Code quality and productivity isn't either/or. It is an and. (I've had to clean up enough after the super-productive producer of crap, and don't want to do it again.)

Except this is not a standards issue. It is a problem that can be solved in one sentence only if the programmer groks code construction to begin with. A lot of otherwise smart programmers - some of them very experienced - do not understand this stuff and do not develop good programming habits of the Code Complete kind. I probably exaggerated a little in my previous comment. Still, I always test for code construction "instincts" when I am interviewing a candidate. I may give the candidate a take home assignment - nothing difficult, as I really only want to see what the code looks like. Or I may ask the candidate to bring a sample of great code, and ask him why he thinks it is great. But I will always test for good habits, as they are too often taken for granted.
I haven't been in the position of hiring programmers, but I've mentored a few FreeBSD developers, and I would not allow one of my mentees to commit code like that.
I believe the intent was not to misspell (because clearly given one side of the coin being 'read' the word 'rite' is clearly meant to be its counterpart) but was to make for neat alignment of the variable declarations. char w, r might have been easier and less twinge inducing, but I think it important to correct the misgiving that the person writing 'rite' was not misspelling but making a deliberate choice to align their variables. His own form of OCD, a trait common in good programmers.
I think he used p_rite so that the variable names length is identical to p_read, which can make the code look more pretty.
I like having the variables the same length from an aesthetic point, but my brain also flags 'rite' as being spelled wrong and that detracts (albeit slightly) from reading and understanding the code.
Yes that's what I figured, but still that's only in one place (in the declaration).
In that case, I'd suggest `p_rd` (well, IMO, just `rd` would suffice) and `p_wr`. Then they're both fairly unambiguous abbreviations.
I think p_wt is better than p_wr. Honestly though, aligning the length of variable names is, in my humble opinion, retarded - he should have went for p_read and p_write.
"aligning the length of variable names is, in my humble opinion, retarded"

I feel the same way.

(comment deleted)
I'd probably go with just 'r' and 'w', or 'src' and 'dst'
Yes, but less readable. This is a trivial, sure, but I really dislike seeing misspelled variables. My favorite, of course, is $cue, which usually (but not always) means that the variable in question is related to a queue.
and more ambiguous if attempting to read quickly. "p_r" is the same on both, and they're the same length, it'd be easy to confuse one for the other.
As long as it's not a blatant misspelling, I think shorthand is fine.
hmm, is it normal to assume all strings in C are \0 terminated? What are the memory usage implications for that?

I won't pretend to know C but suppose you have a string that is 'ab{100}\0' and you wanted to remove all of the bs, you'd end up with 'a\0b{99}\0' in memory correct?

C works at a very low level. You have lumps of memory, and you can put stuff in it. By convention a "string" is actually a lump of memory with chars in it, and the end is indicated by a '\0'. The lump stays the same size and doesn't need tidying.

This is the source of the notorious "fgets" bug/hack/exploit.

This is not the time or place for a tutorial on C strings and memory management, but suffice to say that some people find it horrible, some people find it easy and obvious. It stems from C being a sort of macro-assembler with a simplistic type system glued on top of it.

ADDED IN EDIT: I don't know why you got down-voted - it's a reasonable question from someone who hasn't done C. I've up-voted you to get you back to "1".

fgets() bug/hack/exploit? You mean gets(), right?
Er, probably. Brain fried, too much multi-tasking. Sorry.
Yes, C strings are \0 terminated and that is what you'd end up with.

Memory management is between you, malloc() and free().

Assuming this were malloc()d memory, if you wanted to free up the unused bytes, you'd probably:

  char *condensed = strdup(z_terminated);
  if (condensed)
    free(z_terminated);
  else
    // out of memory
Using realloc would be better, since it allows the memory allocator the option of not copying the string.
Indeed. I wasn't sure you could realloc to a smaller block. Good to know.
(comment deleted)
It's convention for C strings to be \0 terminated, and the standard library expects this just about everywhere (along with the kernel and just about everything else you might want to interface with). If you wanted to store a string as a (length, data) pair you could do that too, but you'd have to write most of your own string manipulation functions.
Interestingly, if you did store your string as (length, data) pairs, strlen would be an O(1) rather than an O(n), which would also impact strcat. Also, strtok could possibly become non-destructive.

The length value of the pair could be an 8 bit character value at the beginning of the array (which is how Pascal did it). That limits you to strings of length 255. If you represented strings as a struct containing an integer length and a char * pointing to the head of the array, the size limit of a string would in fact be no less than the limit of the length of any addressable array anyway. (Assuming that the head of the array is at memory address 1, the farthest it could possibly stretch is to the highest addressable memory value for an n-bit pointer. So an n-bit integer for the length value would clearly suffice.)

I haven't touched C in quite a number of years, so I was happy I got something working in pretty quick order that matched my initial brain-boarded algorithm. Years of Java apparently can't wash away C.

As in the article, I started simple and condensed to the version that I actually submitted. In hindsight, I wonder if that is the best approach when answering an interview question via email. In person, there's obvious benefit to walking through optimization (of all sorts, including visual), but if all the other party sees is the final version, they lose out on that demonstration of the process. If you were to receive just the more condensed version from an interviewee, would that be a benefit or detriment to the person? Does it make sense to send an analysis, or is that just risking overwhelming the interviewer?

I've asked for non-specific code samples in interviews before and that did serve to weed out an applicant once, but only because they sent on very easily searched for code (public API example code from a big developer). Asking for any sort of code example or coding example from an interviewer who isn't in the room always introduces the opportunity for cheating. Getting an analysis (I started here and ended up here) would probably help assuage my suspicions.

I'm nowhere near an experience C coder, but doesn't the in place part mean that you don't create any additional variables or strings and just modify z_terminated? Is that even possible? This is an honest question, because the solution is obvious this way, but a bit harder if you can't create additional variables.
The solution in the link only uses pointers to the string. "In place" as I understood it means not allocating additional memory for the string. His code does that, but it does need two pointers (the one passed in, and the one created to keep track of secondary position) in order to traverse the string.

Because the resulting string will only be shorter (never longer), this works. You simply keep track of where you're copying to and where you're copying from, and move character by character until you reach the end. The resulting string may have extra stuff at the end, but since it's null terminated the standard C string functions will not see that cruft as they will stop at the first null.

"In place" usually means that the result is in the same place as the input, and you don't create an intermediate copy. Creating a fix, known number of additional variables to assist with the calculation is OK.

Some people allocated memory, copied out the input, then put back the bits they wanted to keep. That is not "in place".

Your routine should modify the given zero-terminated string in place, removing all instances of the given char.

The main point of the whole exercise is to see if the candidate can write any code - anything after that is a bonus.

Now if the input and output of a function are correct I think you where simply less clear than you may have thought. If you had said "string in place (don't allocate any memory)," I suspect far more people would have given you the output you wanted.

(comment deleted)
The main point is as you quote. Secondary points are to see if the candidate can understand common expressions and idioms, and if not, either to look them up, or to ask.

As it says elsewhere, the purpose is to get some code, then use it as a start for the discussion. If someone allocates memory then that's where I start. In that case they clearly they don't understand the usual meaning of the expression "in-place."

  > If you had said "string in place (don't allocate
  > any memory)," I suspect far more people would have
  > given you the output you wanted.
I suspect you're wrong, and I would be interested to see if anyone else comments on that point. I got nearly 100 submissions, and only one (from memory) allocated memory. All the others did the modification "in-place" as requested.
Some people allocated memory, copied out the input, then put back the bits they wanted to keep. That is not "in place". "Some people" implies more than one.

I would have done it in place because that was the simplest approach and not doing so would have been silly. However, just because it seems that way to us does not mean everyone knew you required more than just output in that fashion.

Anyway, I was more commenting on the style where you mix the requirements for an interface with the requirements for the algorithm. IMO "I need a function / API that does X efficiently" is much better than saying "does X using hash tables". AKA, if you don’t' want someone to use malloc then you can say so, but giving the reasons why you don't want malloc is more useful.

PS: It’s really a minor point and I thank you for doing this bit of research. I was simply trying to help you communicate in a more clear fashion.

A few things.

Firstly, thanks for being constructive. Your points are noted, although I don't necessarily agree with all of them.

To me, "some people" does not imply strictly more than one. That's possibly a mathematician thing.

But the point about mixing implementation detail and interface requirements, this is normal in much of the coding I do. I'm often working in a constrained environment, and performance requirements are very much a part of the specification. Stating that something must happen "in place" is a common requirement for me, and goes beyond saying "must not malloc" or whatever. More, it's having someone being able to cope with the mix that I was, in part, looking for.

I'm also well aware that many of the people here on HN do big machine programming, or web programming, and aren't accustomed to constrained programming. That's partly why I chose this particular question. My next challenge won't have that sort of constraint, and will be more in line with your comments. (Yes, sucker that I am, I'm planning another small exercise by way of comparison/contrast).

And again, these are the sorts of questions that I would expect to discuss during the de-brief. The very fact that you bring them up at all means you are in the top 0.01% of programmers (for some value of 0.01).

Usually "inplace" means "in constant memory space." In other words, running the algorithm on an input of size N requires the same amount of memory as running the algorithm on an input of size M.
I've just realised that you might think that z_terminated actually "contains" the string, and hence "in-place" would mean modifying z_terminated.

But z_terminated is a pointer to a lump of memory that contains bytes. In that context "in-place" means that you move things around in the lump of memory you are given (the one pointed to by z_terminated) rather than allocating some more memory and using that.

If I'm right, your misconception is by no means unique, but it's a dangerous one that shows that you don't understand what's going on in C with regards pointers, memory and strings.

If I'm wrong then I apologise.

"In-place" usually means "using only a constant amount of additional storage." You're using a constant amount of additional storage if you even use a function call (unless you're invoking certain compiler directives and/or optimizations), because C function calls usually involve placing a return address on the stack.

This is also why quicksort is considered to take up log(n) space: it calls itself recursively, and thus uses up a bit of the stack for each recursive call.

And I thought my solution was as simple as possible. If only I decoupled my two increments, I may have avoided this unreadable, inefficient crap:

  void remove_char(char *s ,char c)
  {
    int from = -1;
    int to   = -1;
    do {
      from++; to++;
      while (s[from] == c) from ++;
      s[to] = s[from];
    } while (s[from] != '\0');
  }
Maybe that's why simplicity doesn't actually rule: it's hard to find. Or, people are silly (including myself in this case).
Your code also fails (i.e., reads and writes past the end of the string) if c == '\0' and s is "\0".
I considered the `c=='\0'` case bogus anyway, so I was OK with a partial function. (I wouldn't have been for production code, though.)
Are you saying that you actually considered that case, and then wrote code that didn't deal with it?
Yes I did, and then yes I did.

I didn't fully investigated that case, but I suspected there could be a problem with `c=='\0'`. I suspected that the `while (s[from] == c) from ++;` line could shoot past the termination '\0' and trigger a buffer overflow.

But I didn't fully investigate, on the grounds that no sane programmer would want to remove a character that's never in a C string. In other words, I considered it was not part of the specification.

In production code, I would have either thrown an exception, or returned early. And I would have asked around to know which I would chose.

Could my attitude have influenced my chances, if I had applied?

'\0' is null-terminated NOT zero-terminated, no ? By definition, a string in C is always null-terminated.
'\0' is a character, not a string. The string is "\0".

Also, C strings are NUL-terminated, not NULL-terminated. (char)(0) is the NUL character; (void * )(0) is the NULL pointer (usually).

Actually it's the NULL pointer by definition. 0 is always equivalent to NULL in pointer context, even though you're working with some hypothetical crazy system where NULL is actually #define NULL 1337. It's mandated by the standard.

If (NULL == 0) isn't true you're not using C.

Whoops, I forgot the point I was trying to make. NULL is (void * )(0), but the code

  void * p;
  memset(&p, 0, sizeof(p));
  assert(p == NULL);
isn't necessarily valid, since the binary representation of NULL isn't necessarily as a sequence of zeroes -- even though everybody writes code which assumes that it is.
Yeah, that's unportable. Although I'm not aware of any system where it doesn't work.

For the record, regarding 0 in pointer context the C standard has this to say:

       An integral constant expression with the value 0, or such an
    expression cast to type void * , is called a null pointer constant.  If
    a null pointer constant is assigned to or compared for equality to a
    pointer, the constant is converted to a pointer of that type.  Such a
    pointer, called a null pointer, is guaranteed to compare unequal to a
    pointer to any object or function.

    -- C89/C90 section 3.2.2.3 Pointers
The standard then goes on to say that NULL is a macro that's guaranteed to expand to a null pointer constant.
Yeah, that's unportable. Although I'm not aware of any system where it doesn't work.

Right. I was considering changing this in FreeBSD last year in order to prevent map-at-NULL kernel exploits, but cooler heads prevailed (and pointed out to me how much code such a change would break).

The C standard exclusively uses the phrase "null-terminated" to refer to strings.
Huh, you're right. I wonder when they changed that.
NUL is in ASCII an abbreviation for null.
They didn't, it's there from at least ANSI C89 (ISO C90): "A byte with all bits set to 0, called the null character, shall exist in the basic execution character set; it is used to terminate a character string literal."
I think the rationale is in EBCDIC support; the C standard didn't want to/couldn't mandate ASCII, and therefore could not use the ASCII NUL character.

I honestly don't know whether POSIX/SUS mandates ASCII.

(comment deleted)
As it happens, '\0' translates to 0. Since they are composed of characters, C programmers tend to do the c == '\0' check instead of the shorted c == 0 check, just to make sure everyone knows we are still treating the char like a character in a string; it is just style.

  char c = 65

is equivalent to and just as legal as

  char c = 'A'

So yes, C strings ARE zero-terminated. In fact, it is a bit weird to say they are null-terminated. It is technically true, given that NULL often translates to 0, but NULL is a pointer value in the same way that zero is a number.

For some reason, I vaguely remember some security hack in one OS where NULL went to a special place in memory that was marked as invalid (1, 63, or something), so in that case, NULL was not defined as 0. Since the memory is vague, though, don't quote me on it.

>it is a bit weird to say they are null-terminated

Yeah, it would be. They're NUL-terminated.

Unfortunately, "null-terminated", although incorrect, is widely used. :(

http://googlefight.com/index.php?lang=en_GB&word1=%22nul...

I hate all the variations of nothing. undef, null, NULL, NIL, NUL...

How can "null-terminated" be incorrect when that is the language used by the C standard?
I stand corrected. I looked at my old ASCII tables. This is a silly semantic argument.

http://www.asciitable.com/

The character shorthand is "NUL", but the full name is "null". I still stand by my (silly semantic) point that the use of NULL is overloaded here. NULL is a pointer value, not a character value. The string does not terminate at a pointer; it terminates with a magic character. This has, in fact, bitten me in the past where '\0' was a legitimate member of my string, and I have to write my own accessors.

if '\0' was a legitimate member, it was not a string, it was a character/byte array, probably of fixed size.
I'm not convinced that NUL vs. C's "null character" is just a silly semantic argument. NUL is an ASCII abbreviation and the implementors probably wanted to avoid being tied to a particular character encoding - there is no mention of NUL in the C standard.

As for the NULL, it needn't be a pointer value, the standard permits it being "an integral constant expression with the value 0", so in certain implementations it can be the same as '\0'.

What people really mean is NUL terminated (NUL is the name of the '\0' character - ASCII value 0x0000). But it probably just mangled some time ago and people write it as NULL.
Sorry, but I can't help to nitpick: 65 is not an 'A' on systems that use EBCDIC, like z/OS and OS/400. It's not even a valid character as such, I think.
I'm sure RiderOfGiraffes doesn't want a third entry from me at this point, so I figure I might as well just post it here:

  #define C	char
  #define F	for
  #define R	condense_by_removing
  #define V	void

  V R(C*A,C B){F(C*J=A;*J=*A++;J+=*J!=B);}
or without #defines:

  void condense_by_removing(char*A,char B){for(char*J=A;*J=*A++;J+=*J!=B);}
By all means send it in - it will be interesting to compare it against the others.
Here is the same code, without syntactic obfuscation:

  void condense_by_removing(char* s, char c)
  {
    char* d = s;
    while (*d = *s++)
      d += *d != c;
  }
I would have sworn I found a bug. There is none. Brilliant.

Now, I wonder if we could further optimize it. For instance by accessing memory several bytes at a time, in a fashion similar to strcmp().

Optimizing for performance, sure. But I was trying to optimize for character count. :-)
Yes, but in doing so, you traded a memory write for a branch. And memory accesses are linear, so that's likely faster than the cannon. So surely we could go further? That's how I got the idea.
Many compilers will implement

      d += *d != c;
with a branch, and depending on the data pattern the branch predictor will be bamboozled. The branchless version would involve a pair of subtractions and some bit banging.
Gasp! I thought it would be easy to just transfer the compare flag to a general purpose register. I suppose x86 doesn't have such an instruction?
I lied! A quick test shows that GCC uses the x86 SETE instruction (set register on condition equal). Cool.

However, code like this:

    if (x == 14) {
        y = 10;
    } else {
        y = 20;
    }
uses branches, although I think x86-64 has conditional load instructions.
I just tossed together a quick branchless version, and was a little surprised to see that it was significantly slower than the straightforward pointer version on random data: about 1.5x the cycles. Presumably this gap would narrow if checking for char_to_remove was less predictable.

Here's the code I tested. I think it works, but I barely checked it. I'm sure there are better ways to calculate 'increment'. Not sure if these would be enough to close the gap, though.

  void condense_by_removing_branchless(char *z_terminated,  char char_to_remove) { 
    char *condensed = z_terminated;
    while (*z_terminated) {
        *condensed = *z_terminated;
        int diff = *condensed - char_to_remove;
        int increment = -((diff | -diff) >> ((sizeof(int) * 8) - 1)); 
        condensed += increment;
        z_terminated++;
    }
    *condensed = 0;
  }
I get access violation with VC, when trying to see how this works.

Works with gcc though.

Can somebody throw some light on how this works?

  void condense_by_removing(char* s, char c)
  {
    char* d = s;
    while (*d = *s++)
      d += *d != c;
  }
It always does the copy, then evaluates *d!=c. If what it just copied is equal to the forbidden character this evaluates as false, which in C is 0, and hence d is left unchanged.

If what it copied is not equal then this evaluates as true, which is 1, and hence d is incremented.

I believe there is a bug here. If the first character is an instance of c, then it will still be copied. For example, calling the function as condense_by_removing ("loup-vailiant", 'l'), will return "loup-vaiiant", where it should return "oup-vaiiant".

(I've not tested this though, I may be wrong.)

I got this exact same question in a Google on-site interview. It was one of those warm-ups :)

It does require someone to think like a C programmer: handling buffers directly, in-place modification and using NULL terminators to end strings that may have memory allocated beyond the NULL. I think it's a great question, but I don't know how well it would go over with CS undergrad students coming out with a Java only background.

Should I look for an equivalent simplistic Java question or stick to the guns and require candidates to know C? (I know the answer for my own team, but curious on others thoughts)

I always end up using perl when I have to do string manipulations and have let my c skills suffer as a result. Sad. Using perl regexp kills brain cells.

#!/usr/bin/perl

$in = <STDIN>;

$remove = <STDIN>;

chomp ($remove);

chomp ($in);

$in =~ s/$remove//g;

Bad boy!

    >hello|goodbye
    >| 
    >>Result is 'hello|goodbye'

    >That's a nice dog you have there.
    >.
    >>Result is ''

I usually handle this with:

    my $pattern = '\\'.substr($in,0,1);
    $in =~ s/$pattern//g;
but it still doesn't feel safe. String operations in perl usually do what you want, but be careful with them!

    $in =~ s/\Q$remove\E//g;
man perlre, search for \Q. When \Q and \E'ing a variable like that, a literal "\E" in the variable value can not escape from the outer \Q.
(comment deleted)

     perl -pe'BEGIN { $x = quotemeta(shift) } s/$x//g;'
should do what you want. `quotemeta' is the function that does the escaping for your properly.

I was hoping to be able to use tr///, since it seems like it was built for this, but it creates its conversion tables at compile time, so there's no chance to interpolate the argument without some nasty nasty eval'ing.

Yeah, tr was my instinct as well.. Thanks for quotemeta, it helps with something I'm working on right now!
No, you have to do it in c so that there are all sorts of obscure bugs.
(comment deleted)
I wonder if I'm alone in thinking that the original `while` version is easier to read and understand than the ending `for` version.

I've never quite understood C programmers' love of the `for` loop. It's just a `while` loop with the different parts stuck in different places (`init; while (cond) { ...; inc; }` is the same as `for(init; cond; inc) { ...; }`) and it doesn't (at least for me) result in any greater clarity or ease in reasoning.

Actually, they are not the same. See the trivia question in the lunk article.
I saw the trivia question in the link article, but it doesn't provide an answer, so I ignored it.

EDIT: K&R says (page 60 in my second edition):

""" The for statement

  for (expr1; expr2; expr3)
    statement
is equivalent to

  expr1;
  while (expr2) {
    statement
    expr3;
  }
except for the behavior of continue, which is described in Section 3.7. """

So apparently the trivia is the behavior of `continue`, which I think most C programmers (including myself) would know implicitly, even if we couldn't answer the question itself.

And it's because the knowledge is implicit and used when appropriate without thinking about it, that the question is, in my opinion, pointless as an interview question, except perhaps to see how the candidate reacts to it.
I think for loops are better than while loops for looping over arrays. I'll try and explain my reasoning:

Whenever you see a `for` loop, it tells you something that a `while` loop doesn't. It tells you the kind of loop you're about to do.

A `for` (usually) means you're going to be looping over an array, with a specific length, with a specific "step" (usually one). This is information you immediately get by seeing a `for`.

Moreover, you get all the information about how the loop looks (what you're looping over, size of the array, other actions you intend to perform in the loop) all in one place. This especially helps when you're looking at a large piece of code, in which case your example of "while (cond) {...;inc;}" would have a lot of lines between the condition and the increment.

But I observe that in C when iterating over a string you don't know in advance how big it is, you merely have to keep going until you find the '\0'. That's why some people will find it more natural to use a "while" when walking down a string.
> I think for loops are better than while loops for looping over arrays. I'll try and explain my reasoning: > Whenever you see a `for` loop, it tells you something that a `while` loop doesn't. It tells you the kind of loop you're about to do.

I won't disagree with that in theory, but I don't really think the "for" loop is the solution. The real solution, I think, is a "foreach" construct (whether it's a loop built into the language (á la Java/C#) or a higher-order function (á la C++'s std::for_each), I don't really care) for iterating over arrays and other collections.

In practice, I think the for loop is too often abused for too little gain, and I think C would be a better language without it.

As an aside, I wonder if anyone has provided a suitable Hoare triple for C's for loop. I have an idea of what it would look like, but would love to see someone else's efforts to verify my own internalized one.

I love higher language's foreach construct. Have to work with what we've got though, and in C, the `for` loop is it.

By the way, I don't know what a Hoare triple is, and reading a little on Wikipedia didn't enlighten me. Any chance for an explanation?

I assume you read http://en.wikipedia.org/wiki/Hoare_logic ?

Basically, the (relevant) state of the computation is represented as a set of logical propositions which are true before a statement, and a set of logical propositions which are true after the statement. For each kind of statement the language supports (`if` statements, `while` statements, etc.) there are natural ways to reason about those triples.

Do you understand the "While rule" on the link above? I can explain it in natural language, but I'd rather not expend the effort if it's not something you've already seen.

> I wonder if anyone has provided a suitable Hoare triple for C's for loop.

Basically the same as for a while loop, as given e.g. at http://en.wikipedia.org/wiki/Hoare_logic since (apart from the behaviour of continue)

  for (inits; cond; steps) { body }
and

  inits;
  while (cond) { body; steps }
are equivalent. And if you do want to deal with continue (and presumably also break) the Hoare stuff gets awfully cumbersome; you're probably better off using informal correctness proofs and writing down a lot of loop invariants.

Oh, all right, here's how you do it. Suppose you have the following three Hoare triples.

  {P & Q0 & !cond}
  body; steps
  {P & body never invoked break or continue}

  {P & Q1 & !cond}
  body
  {P & body invoked continue}

  {P & Q2 & !cond}
  body
  {R & body invoked break}
where Q0, Q1, Q2 are mutually exhaustive (but not necessarily mutually exclusive). And suppose you have

  {P0}
  inits;
  {P}
Then you have

  {P0}
  for (inits; cond; steps) body
  {R | (P & !cond)}
That's if you adopt the convention that any precondition is valid for code that doesn't terminate. Otherwise, you need to do some stuff with loop variants.

  for (char * rd=str; *rd; ++rd) {
    if (*rd != remove) *str++ = *rd;
  }
  *str = '\0';
We can take

P0: str points to a zero-terminated string. P: so does rd, rd and str point to tails of the original string, rd >= str, and what's between original_str and str consists of an appropriately crunched version of what was originally between original_str and rd. Q0,Q1,Q2: true,false,false. R: not needed.

We deduce that at the end of the for loop, rd points to a tail of the original string (P), it also points to a zero character (!cond), hence it points to the end of the original string. Hence what's between original_str and str consists of an appropriately crunched version of the entire original string. Hence all we need do is put on the final zero character, and we're finished.

You should write that up as a proper tutorial - I'm pretty sure a few people here would find it interesting.
Should I be concerned that I've never encountered these Hoare Triples before in my life?
His solution is so similar to mine that is scary. I also went thru the same optimization process. (e.g started with a while and changed that to a for later) I didn't go farther because I didn't want to loose readability.

  void condense_by_removing(
      char *z_terminated ,
      char char_to_remove
      ) {
        char *rptr = z_terminated; // read ptr
        char *wptr = z_terminated; // write ptr
        for(;*rptr; rptr++) {
                if (*rptr != char_to_remove) {
                        *wptr++ = *rptr;
                }
        }
        *wptr = 0;
  }
Turns out his solution is almost the same as mine except I skip the termination stage and instead read one character more in the loop which catches the \0 from the original end of the string automatically. As I'm not really a C programmer, was I doing something bad/unrecommended? It seemed to work.. :-)
Do you want to email me your reference number and I'll have a chat about it. It would be useful for me to clarify the next article on this point.
What happens if you pass '\0' as the character to remove?
Like the grandparent, I do the same. I have the copy routine also copy over the final 0.

If you pass in 0 as the character to remove, it still works fine. Nothing is shifted, so the final 0 is copied and the string remains null terminated. Technically this could be undesirable behavior (it's contrary to the spec), but I don't think the behavior if you do pass in a 0 to remove could be easily defined. What does it copy into the place where the null was? How does it know where to stop? Easier to just assume it's remove any non-null character and asking to remove null means remove nothing.

You can add a little twist to this test to make it slightly more difficult by changing the char to remove to be an array of char's to remove.
Interesting. I didn't try to do the assignment when I read the first part, but just tried it now before looking at the second part. When I started to write the main loop, I felt uneasy when I realized I'd be doing unnecessary copying in the common case when char_to_remove is never found. My code ended up a tad more complex than yours due to desire to avoid that:

  void condense_by_removing(char *z_terminated, char char_to_remove) {
    char *p = z_terminated;
    for (; *z_terminated != 0; ++z_terminated) {
      if (*z_terminated != char_to_remove) {
        if (z_terminated != p) *p = *z_terminated;
        p++;
      }
     }
     if (*p != 0) *p = 0;
  }
This is what I came up with, but I'm not a C programmer.

  void condense_by_removing(char* z_terminated, char char_to_remove) {
    int index;
    int write = 0;
    for (index = 0; z_terminated[index]; ++index) {
      if (z_terminated[index + write] == char_to_remove) {
        ++write;
      }
      if (write > 0) {
        z_terminated[index] = z_terminated[index + write];
      }
    }
  }
Or, getting rid of index:

  void condense_by_removing(char* z_terminated, char char_to_remove) {
    int write = 0;
    for (; *z_terminated; ++z_terminated) {
      if (*(z_terminated + write) == char_to_remove) {
        ++write;
      }
      if (write > 0) {
        *z_terminated = *(z_terminated + write);
      }
    }
  }
I see what you're trying to do, but it won't work this way. Since your termination condition is z_terminated[index], you must never look beyond index, yet you access [index+write]. This alone means the code's buggy. The second version is similarly flawed.

Note that your code instantly becomes much easier to understand if you rename the variable 'write' to 'gap'.

Ehh, good point. :-/

The second version is identical to the first, except that it ises the z_terminated pointer instead of a separate index.

  void condense_by_removing(char* z_terminated, char char_to_remove) {
    int gap = 0;
    for (; *(z_terminated + gap); ++z_terminated) {
      if (*(z_terminated + gap) == char_to_remove) {
        ++gap;
      }
      if (gap > 0) {
        *z_terminated = *(z_terminated + gap);
      }
    }
    *z_terminated = 0;
  }
Interestingly enough, if I try to remove the common *(z_terminated + gap) expression (which a good compiler should do for me..), I end up with a two pointer version similar to yours.
Have you sent it in? You should ...
I missed your post the first time around and only did that today, so I assumed it was too late to send in.
(comment deleted)
Replacing the 3 additions of gapin the loop with a single subtraction (and an increment):

  void condense_by_removing(char* z_terminated, char char_to_remove) {
    int offset = 0;
    for (; *z_terminated; ++z_terminated) {
      if (*z_terminated == char_to_remove) {
            ++offset;
            ++z_terminated;
      }
      if (offset > 0) *(z_terminated - offset) = *z_terminated;
    }
    *(z_terminated - offset) = 0;
  }
This might be a case where one should benchmark before making changes that make the code more complex. I just threw your code your code into a thrown together cycle counter, and on my system it performs significantly worse than the straightforward solution.

My test harness isn't perfect, but with "gcc -O3" on a Core i7, I get something like 20000 cycles to 'condense' a 4096 character random string with the suggested loop, versus 26000 with your more complex approach.

With a 4096 character string set up to never contain char_to_remove, I get a practically identical 19500 cycles with both approaches.

Thus there seems to be no benefit to the greater complexity.

So, I've never been involved in hiring, so I have no perspective on the state of the job market.

However, I have trouble believing that anyone who would be willing to rep themselves as a "C Developer" couldn't write this particular piece of code in a manner similar to the author's solution in a matter of minutes.

Do these FizzBuzz articles keep getting promoted to the front page to boost everyone's collective egos, or is the job market really this pitiful? Can someone who has been involved in hiring recently comment with some anecdotal evidence as to what they've seen?

It isn't that pitiful, per se, but there are a lot of people who want to be programmers trying to pass themselves off as actual programmers. Then again, there are a small handful of people who can't code that apply all over the place because no one will hire them. There's more of the unhireables than you'd think.
To be honest, the people we hire tend to fall down in ways other than coding skills. Sloppiness in following procedures or pure laziness are much bigger impacts. Does anyone have a test for that?
I've been involved in hiring, I've seen it, it's true. There are people who have excellent CVs who cannot write this code.
The problem isn't the job market --- although it is pitiful, and it is dreadfully hard to hire talented developers, particularly in C.

The problem is that the hiring process is necessarily ineffective. You have a very short period of time to come to a sweeping conclusion about the entirety of someone's capabilities. Dev interviewers do a notoriously crappy job at this.

The specific problem "FizzBuzz" tests solve is this: candidates who can talk about development (including people who know that the difference between otherwise equivalent for() and while() statements is whether the increment runs when you hit a "continue"), but who, when asked to sit down and code something, fumble horribly.

These people, all of whom are smart and extremely knowledgeable about software development, have not developed the real world programming survival skill of being able to sit down and just code when they need to. Most development shops want to avoid these people. Hence: a test that isolates specifically your ability to just start coding, apart from any domain knowledge or computer science.

This seems a little silly, or perhaps just overly optimistic. If I have a lot of real computer science knowledge and understanding, I should be able to get an entry-level software development job (assuming there are enough or close to enough to go around). I don't think this particular problem is too unrealistic to expect a CS graduate with zero experience to get right, but I think it's unrealistic for an employer to expect entry-level applicants to know a whole lot about how enterprise development works. That's why they have training (fairly well-structured and comprehensive training, from what I've read) for new hirees.
The exercise has very little to do with experience or knowledge. Think of it as a sort of personality test. It isn't, but it helps to think of it that way.
> I have trouble believing that anyone who would be willing to rep themselves as a "C Developer" couldn't write this particular piece of code in a manner similar to the author's solution in a matter of minutes

Believe it. A friend of mine once got a job as a Cobol programmer (he didn't know Cobol but said he did). He managed to bluff his way through the job until the company went bankrupt 6 months later!

I've personally interviewed way, way too many people with impressive-looking CVs that couldn't code worth anything. Some people probably exaggerate their experience and some people I've seen end up in manager/"architect" roles that don't require actual coding and seem to forget how.

Also, when you're hiring, you're getting a skewed picture of the overall programming population. Suppose (to make the math easy) that there are 100,000 programmers in the world, 90% of which are employed. So that leaves 10,000 programmers in the candidate pool. Most of the top 20% of those will be able to get a job from a referral and not go through a "normal" hiring process, so if you're screening resumes you're now down to the next 80%. But that's the bottom 80% of the 10% that's looking for a job. Of those, some are competent people between jobs or looking for a change, but a lot of them are just in the bottom 10% of the overall skill level, are basically unhirable, and are semi-permanently on the job market because of that.

To make another simplification, if you and I both screen 100 resumes in the same area, there's a reasonable chance that the 10 best candidates we see are different since they go off the market so quickly, but that the 10 worst candidates we see are the same, since they stay on the market. And unfortunately, some of those totally unhirable people have good-looking resumes.

And unfortunately, some of those totally unhirable people have good-looking resumes.

Often because they've had PLENTY of time to work on their resume and plenty of feedback on what works and doesn't (for small values of "works")

Funny how readable Python is:

def condense(s, remove): return "".join([c for c in s if c != remove])

Cheating (2.6+): s.translate(None, remove)

You are aware that this is, to a C programmer, horribly inefficient? (Specifically, it violates the "in-place" requirement, but the actual overhead of that is dwarfed by everything else.)
Either you do it correctly (as in list manipulation, in place) or do it pythonically. That code is somewhere in between.

The pythonic version would be s.replace(c, '')

Sadly, the code YOU wrote is suboptimal. It doesn't check for bad pointers, has no comments, doesn't take memory corruption into consideration, and assumes the replacement character is not '\0'.

You would get a C- if I graded your test.

Now you'll argue that it's just a test, but the fact is your best work should not come only when under unexpected scrutiny, as it does with impromptu tests, it should be a mental process that is not bypassed for any reason.

In this case you've thought about it for weeks and still botched it.

Kudos for trying. We'll call you if we are interested.

Interesting - thank you for your feedback. It's educational to see the different points of view.

However, I wonder if you've actually taken on board either the point of the exercise, or the fact that it isn't yet finished.

However, questions such as those you raise are, of course, of great interest in production code, and would be raised in the discussion this code is intended to start. If someone started to write correct code that took these things into consideration the test would be stopped - it would've served its purpose already.

I'd be interested to know:

* Do you think every routine, every piece of code should have comments?

* Do you think every routine should test its input parameters? Every time?

* Do you think every routine should be checking for memory corruption? All the time?

* How do you cope with memory corruption in the program code itself?

* Do you think the routine fails if the char to remove is '\0'? Are you sure? You seem to claim it does.

* Do you believe that all code should always be written to the same standard?

Having written code in an environment where any given memory location has a MTC (Mean Time to Corruption) of 12 hours, I have considered these issues. I'd be interested to hear your experiences in these matters.

I notice also that you created your username specifically to reply to this item.

Welcome to Hacker News.

It doesn't check for bad pointers

C is a foot-shooting language. Its basic library functions are expected to do what you tell them to do, even when it's a really bad idea. Checking for bad pointers (and how exactly you define "bad" is a rather interesting question) is clearly out of scope.

has no comments

True, but I'm not convinced there's much point on such a trivial piece of code.

doesn't take memory corruption into consideration

Given that there is no redundant information available, this is an impossible challenge.

and assumes the replacement character is not '\0'.

Try re-reading the code a bit more closely.

has no comments,

Does the copious prose not count?

But none of the things you mention were part of the requirement. Furthermore, I'd be interested to know how one checks for "bad pointers" and what exactly _you_ mean by that - just NULL, pointers to read-only memory/string literals, or something else (as far as C is concerned, there are no "bad pointers", the system underneath may disagree though).

Care to elaborate on how you would've prepared a similar test?

(comment deleted)
Coming late to the party, here is my version. Even though it uses some weird constructs, it compiles and runs with TurboC V2.01 from 1988:

    char *d,*s;
    for(d = s =  z_terminated; 
        *s; 
        s += (char_to_remove== *s)?1:((*d++ = *s++),0)) 
           {;}
     *d = *s;
Note this is the "fun version". For production code, I would use more conventional constructs, rather than the comma-delimited embedded expression with side effects.