I used the debugger to examine this code but not understanding a couple areas.

  1. Why does the for loop repeat after it exits to print a new line? If it exits the loop, shouldn’t it be done with it?
  2. Why is n incremented and not i as stated with i++?

int main(void)
{
    int height = get_int("Height: ");

    draw(height);
}

void draw(int n)
{
    if (n <= 0)
    {
        return;
    }

    draw(n - 1);

    for (int i = 0; i < n; i++)
    {
        printf("#");
    }
    printf("\n");
}
  • dneaves@lemmy.world
    link
    fedilink
    arrow-up
    13
    ·
    1 year ago

    Why does the for loop repeat after it exits to print a new line? If it exits the loop, shouldn’t it be done with it?

    There’s the new line after the for loop to make sure that the next recursion starts on a fresh line. Otherwise the next recursion would print on the same line, right where it left off, and you’d just have a line of “#”'s. The for loop itself is just for printing “#”'s.

    Why is n incremented and not i as stated with i++?

    I think this is a confusion with the recursion. Look at the line with draw(n - 1); this happens before any printing of hashes happens, and only continues after its done. And it calls itself as long as it’s not less than or equal to 0. To psuedo-code the order of operations here:

    draw 3 {
        draw 2 {
            draw 1 {
                draw 0 {};
                print "#" * 1;
            };
            print "#" * 2;
        };
        print "#" *3;
    };
    

    so n is never incremented as you think, it just calls decremented versions of the draw function before the current one finishes. The i’s are purely involved in the for loop, which only prints hashes. Does that make sense?

    • milon@lemm.eeOP
      link
      fedilink
      arrow-up
      2
      ·
      1 year ago

      Yes - I finally caught that part about n as it’s just moving in reverse so it gets decremented. Now I’m not sure about i. In the debugger when the program gets to the for loop both n and i are equal to 1. The n I understand but i?

      • dneaves@lemmy.world
        link
        fedilink
        arrow-up
        3
        ·
        1 year ago

        i starts at 0, checks if i is less than n (the first time it will be, no matter what), prints a “#”, then increments i by 1 and loops

  • Mac@programming.dev
    link
    fedilink
    arrow-up
    8
    ·
    edit-2
    1 year ago

    This code has a recursive call (function calls itself) within the function so that has to be taken into account when tracing it

    This would make the function execute multiple times so the for loop would end up executing multiple times.

    Lets say main calls draw with a height value of 10 (draw(10)). First it sees that n is greater than 0 so it keeps going. Then it calls the draw function with a value of 10 - 1 aka 9. Now its executing in the draw(9) function. Greater than 0 so continues and calls draw(8). etc. all the way down to draw(0) where it sees that n is equal to 0 so returns out of the function due to the return statement.

    Now that draw(0) finished executing draw(1) can keep going and goes to the for loop. Here it prints 1 # and then prints a new line (and then returns since it hit the end of the function). Now that draw(1) is done draw(2) can keep going and prints 2 #'s and then prints a new line (and then returns). This keeps going all the way up to the initial draw call, draw(10) which prints 10 #'s and then a new line, returns, and then the main function keeps going but theres nothing after that so it returns and the execution ends.

    The effect from coming back after the recursive calls makes it seem like n is increasing but its just different calls to the same function. i is taken into account for but printing the amount of #'s since thats whats within that loop

    • milon@lemm.eeOP
      link
      fedilink
      arrow-up
      2
      ·
      1 year ago

      Why does the for loop return when it hits the end of the function? Isn’t the recursive portion already completed in draw(n - 1)? The rest of it is just normal non-recursive code if I understand it correctly.

      • jrbaconcheese@yall.theatl.social
        link
        fedilink
        English
        arrow-up
        15
        ·
        edit-2
        1 year ago

        No, the moment that draw(9) is called, draw(10) goes on pause while draw(9) runs, which pauses when it calls draw(8) … which repeats (or recurses) until draw(0) gets called. Then it returns which returns to draw(1). The draw(1) un-pauses and does the #\n bit and returns to draw(2), which un-pauses and does ##\n and so forth until draw(10) does ##########\n

      • Mac@programming.dev
        link
        fedilink
        arrow-up
        5
        ·
        edit-2
        1 year ago

        When the draw function calls itself it yields control to that new function its calling. When that function ends it takes back control and continues doing what it was doing.

        This means all of the for loops in all of the functions will execute. Draw(1) will do the for loop and then return as it hits the end of the function (standard behaviour when you reach the end of a function even if theres no return statement). Then draw(2) will do the for loop as it gets back control now that draw(1) is done and then return, etc. all the way up

        All parts of a function are recursive, theres no such thing as a non recursive part

      • Excrubulent@slrpnk.net
        link
        fedilink
        English
        arrow-up
        3
        ·
        edit-2
        1 year ago

        The for loop doesn’t return, it’s just that when control flows to the end of the function, returning is the standard behaviour. It has nothing left to do, so control is returned to the calling code.

        The recursive portion is begun with draw(n - 1), but then you have a new for loop, because the same function has been called again. That’s what recursion is. Nothing in this function is non-recursive. It calls itself, so all of the code it contains could be happening from the initial call, or within a recursive call. When draw(3) is called, you will get 3 for loops. You will actually get 4 draw calls, but the last one will be draw(0) which returns immediately.

        It’s confusing slightly because it works in reverse to what you’d expect. The operational part of the code - the part that does the drawing - only completes after the recursion is finished. That’s why it does draw(1) first, to make the pyramid right way up.

        I don’t know that I’ve ever done recursion like this. It seems deliberately fancy and somewhat confusing for a new learner.

        Imagine if you put the for loop before the recursive call. What would happen? You would draw three first, then decrease, so you would have an inverted pyramid. That would be easier to understand, but it would also not make a pyramid.

      • supernicepojo@lemmy.world
        link
        fedilink
        arrow-up
        2
        ·
        1 year ago

        This is all a really great example of how The Stack works. As the loop recurses it continually adds to the program stack in memory and then plays the next “item” in the stack. There is a specific limit to recursion as well based on this principle.

  • xmunk@sh.itjust.works
    link
    fedilink
    arrow-up
    6
    ·
    edit-2
    1 year ago

    So you’re drawing a triangle of # that starts skinny in the top left and grows to the right with a height and width of n?

    As an example, Height 3 would be:

    #
    ##
    ###
    

    I’m not following your questions, but the code seems pretty reasonable… though the use of recursion feels pretty unnecessary when you could easily just use a nested for loop.

    • milon@lemm.eeOP
      link
      fedilink
      arrow-up
      2
      ·
      1 year ago

      It’s supposed to be a pyramid but not my code. It’s an example of a recursive function from a CS50 lecture and I’m just trying to understand how the code works line by line.

          • xmunk@sh.itjust.works
            link
            fedilink
            arrow-up
            2
            ·
            1 year ago

            I wrote an equivalent version just using nested loops - reading it might help you understand why the recursion works the way it does.

            • milon@lemm.eeOP
              link
              fedilink
              arrow-up
              1
              ·
              1 year ago

              Thanks. I did see that. I have a general understanding of how recursion works I think where the function calls itself again and again but I don’t get why the code (for loop) below the draw(n - 1) is recursive.

              • xmunk@sh.itjust.works
                link
                fedilink
                arrow-up
                5
                ·
                1 year ago

                The code below the draw(n - 1) isn’t recursive… the call to draw(n - 1) is the recursion.

                Sometimes, it can be helpful to invert recursion. Think about what draw(0) would be and write it down… then compute draw(1) using the value you previously computed for draw(0).

  • anton@lemmy.blahaj.zone
    link
    fedilink
    arrow-up
    5
    ·
    1 year ago

    Recursion is often unintuitive for beginners, to understand this code we should simply it a bit

    
    int main(void)
    {
        int height = get_int("Height: ");
    
        draw(height);
    }
    
    void draw(int n)
    {
        if (n <= 0)
        {
            return;
        }
    
        draw(n - 1);
        printf("%d",  n);
        
        printf("\n");
    }
    

    Inputting 3 should now give us a output like

    1
    2
    3 
    

    Try to understand that case first and than muddle it up with the loop.

    If you examine it in a debugger look at the Stack trace. If you use gdb its bt in a visual debugger it’s probably the list of function names next to the variables

  • Merwyn@sh.itjust.works
    link
    fedilink
    arrow-up
    3
    ·
    edit-2
    1 year ago

    You are looking at a recursive method, as you can see with the line draw(n-1) inside the draw(n) method. You can search for “recursive function” on internet for a better understanding.

    Basically, the method draw is called a first time n = a user input, but then this method call itself with n-1 until it reach 0. So you can think as if function draw(6) will call draw(5) and wait for it to return before continuing, draw(5) call draw(4), ect until draw(0) that return immediately.

    So then the order of execution will be draw(1) that print " #\n" and return, then draw(2) will proceed to print “##\n” and return, then draw(3), ect until draw(n).

    • milon@lemm.eeOP
      link
      fedilink
      arrow-up
      1
      ·
      1 year ago

      Right. I was aware it was recursion as stated in the title of my post. I had two questions specific to where the for loop returns after printing #.

      • Merwyn@sh.itjust.works
        link
        fedilink
        arrow-up
        2
        ·
        edit-2
        1 year ago

        Yes, as I wrote when the method draw(n=1) finish the for loop that print one “#”, this call of the method draw return. Then the process start again from the after the line draw(n-1) of the method draw(n=2), which execute the for loop to print “##” and return. Then again you come back to after the line draw(n-1) of inside the method draw(n=3), ect.

        You should keep in mind that everytime a draw(n-1) is called, the current method is “paused” until this call return.

        • milon@lemm.eeOP
          link
          fedilink
          arrow-up
          1
          ·
          1 year ago

          I see. I guess my understanding was that the recursion was over after the recursive call, but it’s actually for all the code in draw().

          • Merwyn@sh.itjust.works
            link
            fedilink
            arrow-up
            2
            ·
            edit-2
            1 year ago

            Yes, to better understand this you have to understand the “flow” of the program. Meaning the order at which the instructions are executed and not written.

            Here you have the flow of the program starting from n =3 until the recursion reach draw(0), note that none of the for loop have been executed yet. At this point it reach the first “return” instruction and go finish the call to draw(0).

            Then the flow go back to where it previously was: inside the draw(1) call just after the line calling draw(0). And it start executing the next lines of the draw(1): the for loop.

            Then it reach the second “return” and proceed again until the whole program is over.

            • milon@lemm.eeOP
              link
              fedilink
              arrow-up
              1
              ·
              1 year ago

              Yes, that helps. Thanks. I see now how n goes from 1 to 2 to 3…etc. Now not so sure how i = 1 when the for loop starts.

              • Merwyn@sh.itjust.works
                link
                fedilink
                arrow-up
                1
                ·
                1 year ago

                When called with n=1 ? It’s from i=0 to i<1, so it will do only one iteration with i=0 and print one #.

  • Beej Jorgensen@lemmy.sdf.org
    link
    fedilink
    arrow-up
    1
    ·
    1 year ago

    Another approach to thinking about it is that draw() does two things. 1) it draws the line that’s 1 shorter than itself, then 2) it draws itself.

    The for loop happens after it draws the line that’s 1 shorter than itself.

  • abhibeckert@lemmy.world
    link
    fedilink
    arrow-up
    5
    arrow-down
    4
    ·
    edit-2
    1 year ago

    Recursion is a little easier to understand if you use goto instead of functions. Functions are a high level concept in the C language (and most other languages) but it gets compiled down to (essentially) the older goto style of programming.

    Most modern languages don’t even have goto support, since functions are generally more reliable, however as a programmer you should be aware what’s going on under the hood. Here’s your code rewritten to use goto (I also generally rewrote the whole thing to be a bit easier to grok):

    int main(void)
    {
        int height = get_int("Height: ");
        int row = 1;
        int col = 0;
    
    draw:
        if (row > height)
        {
            goto end;
        }
    
        if (col &lt; row)
        {
            printf("#");
            col++;
            goto draw;
        }
        
        // Move to the next row
        printf("\n");
        row++;
        col = 0;
        goto draw;
    
    end:
        return 0;
    }
    
    
  • abhibeckert@lemmy.world
    link
    fedilink
    arrow-up
    4
    arrow-down
    4
    ·
    edit-2
    1 year ago

    Recursion is much easier to understand if you use goto instead of functions. Functions are a high level concept in the C language (and most other languages) but it gets compiled down to (essentially) the older goto style of programming which is much easier to understand.

    goto 42 will move execution to line 42 of the code. goto x will move to the line of code labeled x.

    Most modern languages don’t even have goto support, since functions are cleaner and tend to produce more easily maintained code, however as a programmer you should be aware what’s going on under the hood. Here’s your code rewritten to use goto:

    int main(void)
    {
        int height = get_int("Height: ");
        int row = 1; // Starting row
        int col = 0; // Starting column
    
    draw:
        if (row > height) // Exit condition
        {
            goto end;
        }
    
        if (col &lt; row)
        {
            printf("#");
            col++;
            goto draw; // Repeat the same row
        }
        
        // Move to the next row
        printf("\n");
        row++;
        col = 0; // Reset column for the next row
        goto draw;
    
    end:
        return 0;
    }
    
    

    PS: goto is also how for loops, if statements, switch blocks and others work under the hood.