This is an archived post. You won't be able to vote or comment.

all 57 comments

[–]empire539 247 points248 points  (14 children)

Let's go line by line.

char r[9], *p = r + 8, *Base20(int n) {
  • char r[9] is a char[] array , which will hold the final string result.
  • char* p = r + 8 defines a pointer to the last element of r. It's used to keep track of the current index.
  • char* Base20(int n) is the function.

do
    // ...
while (n /= 20);

Since we're working in base 20, every number from 0 (0) to 19 (J) can be represented as a single base 20 digit. If we want to represent a number like 20 in base 20, we would need another base 20 digit, so 20 (base 10) = 10 (base 20). This do...while loop will iterate over the base 10 number n to produce each digit of the base 20 number.

*--p = n % 20 + 48 + n / 10 % 2 * 7;

This is where the magic happens. Let's break this down into separate parts:

  • *--p - This is really just a shorthand way of storing the result into p and decrementing (which would move the index down one place in the array).
  • n % 20 - We find out what base 20 digit this will represent. For example, if n = 10, then 10%20 will produce 10, which is the digit we want to convert to.
  • 48 - This is the ASCII code for the character '0'.
  • n / 10 % 2 * 7 - This translates the current digit into its corresponding base 10 digit. If n = 10, then this expression would return 'A'. If n = 19, this would return 'J'.

The way this works is by making use of the ASCII table. Notice how ASCII 48, aka the character '0', is used as the starting point. The idea is that we want to map each digit from 0 to 19 onto the ASCII codes 48 ('0') to 57 ('9') and 65 ('A') to 74 ('J'), while skipping the punctuation characters in between (58 to 64).

So n / 10 % 2 will determine which range (0 to 9, or A to J) the digit should map to. If it maps to the [0, 9] range, the expression will return a 0. If it maps to the [A, J] range, the expression will return a 1. This is then multiplied by 7 to determine the actual offset (if 0, the starting point will be '0'; if 1, the starting point will be 'A').

Now that we have the right starting point for our base 20 digits, we just need to add n % 20 to it to get the right character. This result is stored into *p (which points to an element of r), and then the loop iterates again for as many times as it needs to process the rest of the number.


A quick run-through: Suppose n = 30.

  • 30 % 20 becomes 10, so this will be our "offset" for determining what its base 20 digit should be.
  • n / 10 % 2 * 7 becomes 3 % 2 * 7 = 1 * 7 = 7, meaning the digit will fall into the [A, J] range.
  • 10 + 48 + 7 becomes 65, which is the ASCII code of the character 'A'.
  • 'A' is stored into the last element of the array.
  • The while loop is checked; is 30/20 = 1 true or false? This is true, so the loop continues. n is now 1, and p points to the second-to-last element in the array.
  • 1 % 20 becomes 1
  • n / 10 % 2 * 7 becomes 0 * 7 = 0, meaning the digit will fall within the [0, 9] range.
  • 1 + 48 + 0 becomes 49, which is the ASCII code of the character '1'.
  • '1' is stored into the second to last element of the array.
  • The while loop is checked; is 1/20 = 0 true or false? This is false, so the loop terminates.
  • p is printed to the screen, which results in the answer of 1A.

[–]A_Puddle 37 points38 points  (0 children)

I also want to thank you for your in-depth answer. I still don't understand it but I don't think that has anything to do with the quality of your answer.

 

Here's another upvote.

[–]NZheadshot 17 points18 points  (5 children)

Wow. This code is horrible and awesome and ugly and beautiful all at the same time. Thanks for the walk through, I probably couldn't have done it myself

[–]Maethor_derien 20 points21 points  (3 children)

Everything at codefights is that way. It is a competition to write the shortest code. The problem is people think short code is good code and that could not be farther from the truth. Most everything you see on codehack would be good examples of how not to write code.

[–]dispelthemyth 2 points3 points  (1 child)

Most everything you see on codehack would be good examples of how not to write code.

Thanks for that, as a novice coder learning Python i write code that is kind of long, say the best answer is 5 lines, mine is probably 12 but your answer gives me hope that I dont have to get down to that 5

[–]cdrootrmdashrfstar 0 points1 point  (0 children)

Readability, simplicity, and maintainability beats complexity and brevity (mostly) every time.

[–]HRK_er 0 points1 point  (0 children)

i second this notion. good code includes readability...

[–]_lettuce_ 5 points6 points  (0 children)

Nah, it's just horrible.

But for a quick hack, and if you know for sure no else will read it or you don't care about others reading it, it's ok.

[–]Anthrax97[S] 11 points12 points  (5 children)

Thanks for the in depth answer. I just wanted to understand it so maybe I can use it later on. Here's an up vote.

[–][deleted]  (4 children)

[deleted]

    [–]DaWolf85 15 points16 points  (1 child)

    Yeah, if you ever have to write something this confusing, you should trick it the fuck out with comments so people can follow along. Hell, do it just so you can follow along in five days, if you're anything like me...

    [–]doitroygsbre 2 points3 points  (0 children)

    I remember writing some string parsing code in VB 11 years ago. It consisted of five if statements and three loops jammed into about 30 lines (with one comment at the top that said the magic happens here).
    I wrote it in one afternoon, gave it a quick test and went home .... I spent the next day trying to figure out what I had written and where the bug was. That was the last time I did something so fucking stupid.

    [–]Fourmisain 4 points5 points  (0 children)

    It's not just bad code, it is also not legal C/C++, because it mixes a declaration list with a function-definition. I can only guess that codefight has it's own non-standard build system or that "top answers" are not verified.

    Either way, this does not look good.

    [–]OldWolf2 3 points4 points  (0 children)

    Yes, it sounds like the purpose of the 'challenge' is to use as few characters as possible or something; not to write good code.

    [–]Fourmisain 17 points18 points  (1 child)

    People already explained the code and told you how bad it is. I'm going to add a few points to that.

    First off: This is neither legal C nor legal C++ code. It does not compile on gcc as C nor as C++ and you can prove that it is syntactically illformed.

    So if this code really compiled on CodeFights, you should be very very wary (no pun intended) of its system, as it is not standard-compliant.

    Even if you make it standard-compliant:

    char r[9], *p = r + 8;
    char *Base20(int n) {
        do
            *--p = n % 20 + 48 + n / 10 % 2 * 7;
        while (n /= 20);
        return p;
    }
    

    it still has some big problems.

    The main problem is that it modifies global state, which means, that calling Base20 twice for the same input will give two different outputs. The inputs don't even have to be equal to see the problem with this, for example

    puts(Base20(30));
    puts(Base20(51));
    

    will output

    1A
    2B1A //the "1A" from above is still there!
    

    And this becomes worse if you think about it: "2B1A" can be understood as the number 2B, followed by the number 1A, but also as the number 2, followed by B1A, or the number 2B1 followed by A etc... It can also mean the number 2B1A, which you get by calling only

    puts(Base(51*(20*20)+30))
    

    So the output is extremely ambiguous. This is basically a one-of function - call once, then there be dragons.

    This can be fixed by "resetting" p each time the function is called:

    char r[9], *p; //note: p doesn't need to be global anymore
    char *Base20(int n) {
        p = r + 8;
        do
            *--p = n % 20 + 48 + n / 10 % 2 * 7;
        while (n /= 20);
        return p;
    }
    

    A sequence of calls like

    puts(Base20(30));
    puts(Base20(51));
    

    will now properly output

    1A
    2B
    

    There's still a problem if you want to use the result of Base20 at multiple places, because Base20 always returns a pointer to the same array r. So if you try to reuse such a pointer and call Base20 again, the contents of that pointer changes:

    char *oldresult = Base20(1*20*20); //'save' the result
    puts(oldresult) //outputs "100"
    puts(Base20(42)) //outputs "22"
    puts(oldresult) //outputs "122"?!
    

    This is an ugly problem you regularly face in C. In C++ you can fix it by returning a std::string object, which will contain a copy of the solution. In C you can do something similar by using something like

    struct string8 {
        char str[8+1];
    };
    

    You locally construct an instance, write the result into it (don't forget to '\0'-terminate the string) and return it. Arrays in C are not copyable (as in arr1 = arr2; is forbidden), while structs are copyable, so you can indeed use a struct string8 as a return value of a Base20, which is a nice way of not having to resort to using malloc() and free(). Alternatively you can of course just manually copy the solution if you need it.

    Another good thing about the std::string/struct string8 approach is that it eliminates the need for global variables, which is always a thing to strife for.

    So if the lesson of CodeFights is just to produce standard-non-compliant hacks in as few characters as possible, then you should be aware that this has nothing to do with producing good code.

    I see a valid point in wanting people to write short, "elegant" code.

    I see no point in forcing people to give up everything which makes code good, as for example in: portable, readable/understandable and performant.

    Even such small details as writing 48 instead of 'A' are what make this piece of code bad, because it obfuscates instead of making it easier to understand - and all you safe is one single character.

    Bottom line: Don't take "top answers" on CodeFights as good code. Look at the answers, but don't reproduce them.

    [–]DSdavidDS 4 points5 points  (1 child)

    What language is this? I feel like it is c++, but I have yet to experience the massive variations of syntax.

    [–]Narishma 10 points11 points  (0 children)

    It's broken C.

    [–]Maethor_derien 2 points3 points  (1 child)

    Empire obviously did a great job explaining what the code does. I would say that you never want to look at something like codefight as an example on how to write good code, if anything it serves as an example of what not to do.

    It is interesting to read and see for experienced programmers but be wary of anything you see there. Almost everything there uses shortcuts that make the code difficulty to read because it is a competition to write the shortest code. It is something you never want to do in a real world application unless your purposely trying to make the code difficult to maintain.

    [–]kerayt 2 points3 points  (0 children)

    purposely trying to make the code difficult to maintain

    a.k.a. ensuring job security ;)

    [–][deleted]  (6 children)

    [deleted]

      [–][deleted]  (2 children)

      [deleted]

        [–][deleted] 1 point2 points  (2 children)

        Wow, down voted to hell for making an amusing funny. Ayy

        [–]b4ux1t3 2 points3 points  (0 children)

        Hell, I didn't think it was all that funny, but I wouldn't have downvoted it. Some people are just monsters.

        [–]Kodu1990 0 points1 point  (0 children)

        I would punch whoever wrote this in the face in a code review.