
Interviewing programmers: coding test results. - RiderOfGiraffes
http://www.solipsys.co.uk/Writings/TestsForProgrammers_Part_1.html
======
RiderOfGiraffes
Please can I make a request:

If you want to try this you're welcome, but I ask:

* Please no HTML emails - it's easier to extract from plain text

* Please remember the anti-spam measure - more than one person has been caught in the spam trap.

* Please put BEGIN before and END after you code so I can auto-extract it.

I've had 20 submissions in the last few hours, so these will help a lot.

Thanks.

========================================================

EDIT: And another 10 in the last 12 minutes. Thank you to those doing as I ask
- it's making things easier. I'm going off to do some work now - I'm a little
concerned about what I'll come back to ... although it is interesting. As I
said earlier, I'll collect submissions until there's a 48 hour gap, then I'll
start processing them.

~~~
jemfinch
> * Please remember the anti-spam measure - more than one person has been
> caught in the spam trap.

What antispam measure? Neither this page nor
[http://www.solipsys.co.uk/Writings/TestsForProgrammers_Part_...](http://www.solipsys.co.uk/Writings/TestsForProgrammers_Part_1.html)
says anything about spam (except this note).

~~~
bdonlan
Nor does <http://www.solipsys.co.uk/new/ColinWright.html?Personal> \- though
I'm not sure that Colin Wright even wrote that, given I've yet to find a link
to the article from the root, and the article doesn't have the author's name
...

------
scorchin
Thanks for this write-up RiderOfGiraffes.

I'm one of the HNers that sent in code for the test. The feedback provided by
RiderOfGiraffes was useful and the follow up questions were food for thought.

At the time I'd just failed a coding test for a systems programmer role and
felt pretty low, which is why I decided to take this on.

In the interview I failed, I fared well in most of the questions, apart from
one set involving heavy binary maths — I pretty much forgot basic binary
computation from when I did my undergrad course. Following this, it prompted
me to buy "Hacker's Delight" by Henry S. Warren. It's a great read if you're
into high-performance algorithms <http://amzn.com/0201914654>

I can't wait to read the next part!

------
ggruschow
You're testing whether active participants on HN that volunteer to do a
problem are worth talking to?

I've done tests like this in the past couple of years for everyone _responding
to a job ad_ for all sorts of positions. The job ads usually include the
problem(s), so I'm getting the same self-selection you are. Combining our
results for a _freakonomics_ style conclusion:

 _HN readers are an order of magnitude more skilled than job ad readers._

~~~
RiderOfGiraffes
I'm not really testing them - I'm providing them with information about the
tests I do, why I do them, what I do with them, and the discussions that arise
from them. If you don't try it for yourself then you haven't given yourself
the chance to think of the issues that I then raise in subsequent discussions.

These test aren't for everyone, they are examples of what I specifically do.

We already know that self-selected from HN already means to 0.1%.

------
RiderOfGiraffes
OK - I've had a flood of emails trying the challenge, so I'm going to delay
the next post about this to let more people have a go. It's not automated so
I'm dealing with it by hand, but feel free to have a go. I'll do the next
posting when I've received no submissions for 48 hours.

And when I get time.

------
jacquesm
What a great thing to do, RoG, really, that's a very useful thing.

Maybe a bit less trivial challenge next time ?

(though I can see how in an interview setting this would be all you might be
able to do).

~~~
RiderOfGiraffes
It's not trivial till you've done it.

If it really _is_ trivial then it shouldn't take you long.

If it _isn't_ trivial then you'll learn something.

Care to try it before I publish Part II ??

<grin>

~~~
axod
It _seems_ very trivial :/

I don't really do much C, but here's x86:

    
    
      // esi -> asciiz string
      // cl = character to remove
      // NB cl=0 won't work well ;)
    
      mov edi, esi
      cld
      loop1:
          lodsb      // Load the byte from [esi] into al
          cmp al, cl
           je loop1  // Skip this byte
          stosb
          or al, al
           jnz loop1
    
    

Did I miss the "gotcha"s?

edit: condensed code a bit

~~~
JoachimSchipper
Please don't post the solution, people would like to think for themselves.

~~~
axod
The worrying thing is that you're suggesting there may be people on hackernews
who can't bang this out in a few seconds :/

Does it even really require much thinking? I'm not trying to be arrogant here,
but it seems like a good definition of "trivial".

~~~
jacquesm
> The worrying thing is that you're suggesting there may be people on
> hackernews who can't bang this out in a few seconds :/

My sentiment exactly.

And by the way, you do have a bug in that code.

~~~
axod
Really? give me a clue...?

~~~
jacquesm
You are making some scary assumptions about the validity of that pointer...

~~~
axod
I didn't include all the boring error checking code, I included just the
'meat' ;) If you call it with valid inputs, it produces valid outputs.

IMHO That's not a 'bug'. That's a lack of boring gruntwork error checking.

~~~
jacquesm
Yes, that's more or less the problem with lots of code.

I'm not saying your code is invalid or that it won't work, but 'defensive'
programming says:

\- assume your input is total garbage

\- handle all correct situations correctly

\- handle all garbage gracefully or throw an error depending on what the
circumstances dictate

I'm also missing the stackframe handling, but as you said, this is the 'meat',
that meat needs a bit of scaffolding to work.

~~~
NickPollard
Defensive programming is not always the correct assumption though.

~~~
jacquesm
_any_ assumption is wrong, but when given the choice between two assumption
(safe vs unsafe) safe is best.

I did note in my 'answer' that the problem wasn't specified fully.

------
btilly
If you're interviewing and are interested in filtering out the idiots with
little work on your part, <http://www.ziprecruiter.com/> may be useful.
(Disclaimer. I know the people who created it. But it still is a useful piece
of work reduction.)

------
jemfinch
I'm really amazed at the variation I'm seeing in the solutions that just my
friends are providing. One said his first try would be quadratic, and his
second would require allocation; another used a strange mix of indexing and
pointer arithmetic. Even those who commit to either indexing or pointer
arithmetic have significant variation in their solutions.

I actually think there's a distinct _best_ solution to this problem, so it's
remarkable to me that there's so much variation between people that I consider
to be good programmers.

------
petercooper
That seems pretty poor. I'm a pretty crappy programmer and haven't written any
C in 18 years, but despite tripping over my feet for fifteen minutes, I have a
working 4 line function. I despair for what passes as a professional
programmer in shops that _don't_ bother to check their people out properly..

 _Added: In the interests of "put up or shut
up":<http://gist.github.com/415975> \- no idea if it's OK by today's standards
but hey, it works_

~~~
sethg
The program also seems to work if, in the loop header, you replace “i <=
strlen(z_terminated)” with “z_terminated[j] != 0”.

I don’t understand why I have to put j in the index there, rather than j-1:
after the string-ending null is copied, j is incremented, right?

~~~
nkurz
I don't think 'j' nor 'j-1' would make sense here. 'i' would make sense, if
you also terminate the condensed string.

Maybe it would be clearer if the names of the index variables better described
their purpose?

<http://gist.github.com/416298> (no peaking if you're submitting!)

I think these names make it clearer that neither "z_terminated[condensed]" nor
"z_terminated[condensed - 1]" are reliable ways to find the end of the
original string.

------
mcu
#include <stdio.h> #include <string.h>

void condense_by_removing (char *z_terminated, char char_to_remove) { int i,
index;

    
    
    		for (i = 0, index = 0; i <= strlen(z_terminated); ++i) { 
    			if(z_terminated[i] != char_to_remove) {
    				z_terminated[index++] =	z_terminated[i];
    			}	
    		}
    	}
    
    	int main () 
    	{
    		char dt[] = "Now is the winter of our discount tents";
    		condense_by_removing (dt, 'u');
    		printf ("%s\n", dt);
    	}

~~~
jacquesm
Points for the <= copying of the termination character :)

You do make two passes over the string (one for strlen and one for the copy
loop), also you assume the compiler will optimize the caching of the return
value of the strlen function, if it doesn't your performance will be horrible.

No points for posting the solution...

------
tmountain
Seems simple enough (formatted a little funny due to comment quirks):

void condense_by_removing(char *z_terminated, char char_to_remove) {

    
    
        int i;
        int write_index = 0;
    
        for (i = 0; i < strlen(z_terminated); i++) {
            if (z_terminated[i] == char_to_remove) {
                continue;
            }
    
            z_terminated[write_index] = z_terminated[i];
            write_index++;
        }
    
        z_terminated[write_index] = '\0';
    }

~~~
petercooper
You don't need all that logic in there.

    
    
      for (i = 0; i <= strlen(z_terminated); i++)
        if (z_terminated[i] != char_to_remove)
          z_terminated[write_index++] = z_terminated[i];
    

That even removes the need to add the \0 later on.

~~~
dalke
For extra performance, use strchr to find the first char_to_remove.

    
    
      /* Find the first character to remove */
      /* This doubles the performance when there are no
         remove characters, I assume because it uses a
         builtin and because I don't do unneeded writes. */
      src = strchr(z_terminated, char_to_remove);
      if (src == NULL)
        return;
    
      /* src is the position in the old string, with
           the possible characters to remove.
         dest is the end position of the new string,
           where non-removed characters are added */
      dest = src++;
    
      for (;;) {
        /* Skip additional remove characters */
        while (*src == char_to_remove)
          src++;
    
        /* Copy (including copying the terminal NUL) */
        if (! (*dest++ = *src++)) {
          /* copied a NUL - end of string */
          break;
        }
      }

------
dpritchett
I haven't touched C in the better part of a decade but the algorithm seemed
straightforward to me. I'll fake it with some pseudo-C:

<http://pastebin.com/UpLNgxKC>

Note: I read the entire thread several hours before making this attempt so my
results don't represent a good lab environment. Please let me know if I made
any major errors.

~~~
RiderOfGiraffes
Please email it using the instructions - I've got too many submissions to go
chasing them by hand.

Thanks.

~~~
dpritchett
Done! Thanks for taking the time to read this. My hope in posting the pastebin
link was that other commenters might chime in if they saw glaring errors.

------
jbarciauskas
I'm not afraid, or proud, to admit I couldn't get past the n^2 solution until
I talked it out with a friend. Guess it's time to go back and read my K&R...

------
dusklight
uh, so not a single person has mentioned memory allocation issues yet?

the pointer to the 0 terminated string could have been to a statically
allocated array or memory allocated from the heap using malloc. If you just
shorten the string without reallocating the memory, the extra bytes that got
condensed are still marked as allocated and are wasted.

~~~
jacquesm
That's pretty obvious, but since that was the requirement that's what you do.

For all you know the length of the original string is kept around somewhere
for future reference when the buffer is re-used, it's not uncommon for buffers
used like this to be limited to some fixed size larger than the maximum
expected string length + 1 for the terminating nul char, and to have a string
copied in to a buffer for processing.

You're in deep trouble when you try to insert stuff into strings like that
though...

The bytes that got condensed and are wasted would be wasted even worse in the
way you describe, if you re-allocate the memory then you'll have to free the
original one, leaving the larger portion sitting unused, instead of just a few
bytes...

Sure, that could be re-used by some other call to the allocator by a different
part of the program, but the most important issue here is that that was not
what was specified.

Whoever spec'd it needs it like that, so that's what gets built.

------
logic
Interesting that so many solutions posted thus far use strlen().

My C is beyond rusty, but this is what I came up with; do/while is so
underappreciated these days. (HN needs a "spoiler" formatting tag, methinks.
:)

    
    
      void condense_by_removing(char *z_terminated, char char_to_remove) {
          char *pos = z_terminated;
          do {
              if (*z_terminated != char_to_remove)
                  *pos++ = *z_terminated;
          } while (*z_terminated++ != 0);
      }

------
Maro
With standard C style my solution is 12 LOC.

------
mdg
I am curious to what the Python solution was, since the C requirement was to
do it "in place".

~~~
masterj
I suppose you could first convert the string to a list of characters and use
integers as a replacement for pointers. You could then modify the list in
place.

~~~
SMrF
I submitted the python solution. I changed the problem to accept a list. I was
guessing this preserved the integrity of the problem, although I don't really
know C so I couldn't be certain. I explained as much when I emailed my
solution.

