
Interviewing programmers: coding test example explained - RiderOfGiraffes
http://www.solipsys.co.uk/Writings/TestsForProgrammers_Part_2.html?HN
======
roel_v
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?

~~~
ComSubVie
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.

~~~
jemfinch
In that case, I'd suggest `p_rd` (well, IMO, just `rd` would suffice) and
`p_wr`. Then they're both fairly unambiguous abbreviations.

~~~
dkersten
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.

~~~
nickelplate
"aligning the length of variable names is, in my humble opinion, retarded"

I feel the same way.

------
rottencupcakes
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?

~~~
tptacek
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.

~~~
baddox
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.

~~~
tptacek
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.

------
jemfinch
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.

~~~
edanm
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.

~~~
jemfinch
> 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.

~~~
gjm11
> 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.

~~~
RiderOfGiraffes
You should write that up as a proper tutorial - I'm pretty sure a few people
here would find it interesting.

~~~
smcl
Should I be concerned that I've never encountered these Hoare Triples before
in my life?

------
cperciva
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);}

~~~
loup-vaillant
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().

~~~
cperciva
Optimizing for performance, sure. But I was trying to optimize for character
count. :-)

~~~
loup-vaillant
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.

~~~
Daniel_Newby
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.

~~~
loup-vaillant
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?

~~~
Daniel_Newby
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.

------
ydant
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.

------
cperciva
So when do we get to read part 3? I want to see what "creative" solutions
people came up with.

~~~
RiderOfGiraffes
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.

~~~
cperciva
Awww. I was hoping that with an audience of hackers and such a trivial problem
I wouldn't be alone in my creativity. :-/

~~~
levesque
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.

------
gabeiscoding
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)

------
djb_hackernews
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?

~~~
RiderOfGiraffes
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".

~~~
tptacek
fgets() bug/hack/exploit? You mean gets(), right?

~~~
RiderOfGiraffes
Er, probably. Brain fried, too much multi-tasking. Sorry.

------
grogers
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.

~~~
aplusbi
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.

~~~
RiderOfGiraffes
You will be surprised.

~~~
aplusbi
I like surprises! Looking forward to part 3...

------
sram
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;

~~~
nevinera
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!

~~~
shabble

         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.

~~~
nevinera
Yeah, tr was my instinct as well.. Thanks for quotemeta, it helps with
something I'm working on right now!

------
JoachimSchipper
With regards to your trivia question, where you talking about
<http://decode.org/?q=pbagvahr>?

~~~
RiderOfGiraffes
If I understand you correctly, yes.

------
chrisbuchino
I have hired (and fired) a lot of software developers and cannot stress enough
the importance of having candidates write code up-front. Having made poor
hiring decisions myself by not doing this well and having "inherited" team
members who whose coding skills left something to be desired, I would argue
that few things are as crippling to an organization as bad programmers.
Someone can be the nicest person ever, but if they don't write code well -
they will cost you in time, money, and unnecessary technical debt.

I don't agree that FizzBuzz tests are the ways to screen developers though.
These types of questions may give insight into IQ but I've seen really bright
people be really sloppy coders. The way the problem is solved is just as
important as getting the problem right and making it fast. If it isn't
readable, you better hope that person does not get hit by a bus because if you
need to modify it later, you're in trouble.

------
anatoly
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;
      }

~~~
dkersten
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);
          }
        }
      }

~~~
anatoly
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'.

~~~
dkersten
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.

~~~
RiderOfGiraffes
Have you sent it in? You should ...

~~~
dkersten
I missed your post the first time around and only did that today, so I assumed
it was too late to send in.

------
huherto
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;
      }

------
loup-vaillant
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).

~~~
jemfinch
Your code also fails (i.e., reads and writes past the end of the string) if c
== '\0' and s is "\0".

~~~
loup-vaillant
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.)

~~~
RiderOfGiraffes
Are you saying that you actually considered that case, and then wrote code
that didn't deal with it?

~~~
loup-vaillant
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?

------
rayval
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.

------
js2
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>

:-)

~~~
JoachimSchipper
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).

~~~
js2
Bah, I learned my C from K&R 2nd ed. I guess that's not K&R C, is it? :-)

~~~
JoachimSchipper
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.

------
leftnode
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.

~~~
RiderOfGiraffes
"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".

~~~
Retric
_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.

~~~
RiderOfGiraffes
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.

~~~
Retric
_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.

~~~
RiderOfGiraffes
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).

------
petercooper
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.. :-)

~~~
zb
What happens if you pass '\0' as the character to remove?

~~~
ydant
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.

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

~~~
Xurinos
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.

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

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

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

[http://googlefight.com/index.php?lang=en_GB&word1=%22nul...](http://googlefight.com/index.php?lang=en_GB&word1=%22null-
terminated%22&word2=%22nul-terminated%22)

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

~~~
tedunangst
How can "null-terminated" be incorrect when that is the language used by the C
standard?

~~~
Xurinos
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.

~~~
nuxi
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'.

------
RiderOfGiraffes
Taster of the analysis to come: <http://news.ycombinator.com/item?id=1404347>

------
logicalmind
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.

------
bkz
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)

~~~
JoachimSchipper
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.)

------
Vocalspace
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.

~~~
RiderOfGiraffes
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.

