Renan
Renan

Reputation: 71

C - Printing a specific pattern

The exercise asks me to create a program which receives an integer number n and prints the following pattern with a 2*n height.

\    *    /
 \  ***  /
  \*****/
   \***/
    \*/
    /*\
   /***\
  /*****\
 /  ***  \
/    *    \

What I've already done is:

void arte(int n)
{
    int i, barrasE, aux, espacobarrasE, ebE, espaco;
    aux = n;

    for(i = 1; i <= n; i++)
    {
       if(aux < n) //Prints the spaces on the superior part.
       {
           espacobarrasE = n - aux;
           for(ebE = 0; ebE < espacobarrasE; ebE++)
               printf(" ");
       }
       for(barrasE = 1; barrasE <= aux; barrasE++) //Prints the backslashes on the superior part.
       {
           printf("\\");
           break;
       }
       for(espaco = 1; espaco < n; espaco++)
       {
           printf(" ");
       }
       aux = aux - 1;
       printf("\n");
   }
}

This only prints the backslashes on the top and I don't know how to continue the code. I would like to know if it's a good way to do it and what is the better way to continue the code.

Upvotes: 3

Views: 148

Answers (2)

Jonathan Leffler
Jonathan Leffler

Reputation: 753675

I'd develop the solution in stages, roughly like this. My solution insists on the n parameter (for which I use N in the code) is an odd number. The question doesn't show how to present the result if it is even. The insistence is backed up by assertions.

Stage 1 — print line function

#include <assert.h>
#include <stdio.h>

enum { FSLASH = '/', BSLASH = '\\' };

static inline void repeat_char(int num, char c) { for (int i = 0; i < num; i++) putchar(c); }

static void print_line(int l_blanks, char c1, int i_blanks, char c2, int nc, char c3)
{
    assert(i_blanks % 2 == 0);
    assert(nc % 2 == 1);
    repeat_char(l_blanks, ' ');
    putchar(c1);
    repeat_char(i_blanks, ' ');
    repeat_char(nc, c2);
    repeat_char(i_blanks, ' ');
    putchar(c3);
    //repeat_char(l_blanks, ' ');
    putchar('\n');
}

int main(void)
{
    print_line(0, BSLASH, 4, '*', 1, FSLASH);
    print_line(1, BSLASH, 2, '*', 3, FSLASH);
    print_line(2, BSLASH, 0, '*', 5, FSLASH);
    print_line(3, BSLASH, 0, '*', 3, FSLASH);
    print_line(4, BSLASH, 0, '*', 1, FSLASH);
    print_line(4, FSLASH, 0, '*', 1, BSLASH);
    print_line(3, FSLASH, 0, '*', 3, BSLASH);
    print_line(2, FSLASH, 0, '*', 5, BSLASH);
    print_line(1, FSLASH, 2, '*', 3, BSLASH);
    print_line(0, FSLASH, 4, '*', 1, BSLASH);
    putchar('\n');

    return 0;
}

The repeat_char() function is a very simple little loop that prints the specified character the specified number of times. By making it static inline, there's a good chance that the compiler will not make a function call but will place the body of function into the calling code.

The print_line() function characterizes each line as:

  • zero or more leading blanks
  • one occurrence of c1
  • zero or more inner blanks (that must be an even number)
  • nc occurrences of c2 (that must be an odd number)
  • zero or more inner blanks again
  • one occurrence of c3
  • in other circumstances, it might be useful to add trailing blanks; that's commented out
  • in those other circumstances, the function wouldn't print a newline

The code in main() calls that function with appropriate arguments written out manually. It generates the output:

\    *    /
 \  ***  /
  \*****/
   \***/
    \*/
    /*\
   /***\
  /*****\
 /  ***  \
/    *    \

This looks like what you're after for N=5. But those argument lists to the the function have an awful lot of regularity to them. There must be a way to generate the calls with those numbers replaced by expressions. This observation leads to stage 2.

Stage 2 — two loops

#include <assert.h>
#include <stdio.h>

enum { FSLASH = '/', BSLASH = '\\' };

static inline void repeat_char(int num, char c) { for (int i = 0; i < num; i++) putchar(c); }

static inline int max(int x, int y) { return (x > y) ? x : y; }
static inline int min(int x, int y) { return (x < y) ? x : y; }

static void print_line(int l_blanks, char c1, int i_blanks, char c2, int nc, char c3)
{
    assert(i_blanks % 2 == 0);
    assert(nc % 2 == 1);
    repeat_char(l_blanks, ' ');
    putchar(c1);
    repeat_char(i_blanks, ' ');
    repeat_char(nc, c2);
    repeat_char(i_blanks, ' ');
    putchar(c3);
    //repeat_char(l_blanks, ' ');
    putchar('\n');
}

static void driver_1(int N)
{
    assert(N % 2 == 1 && N > 0);

    for (int i = 0; i < N; i++)
    {
        int nb = max(0, (N-1-2*i)/2);
        int db = min(2*i+1, 2*(N-i)-1);
        print_line(i, BSLASH, 2*nb, '*', db, FSLASH);
    }

    for (int i = N-1; i >= 0; i--)
    {
        int nb = max(0, (N-1-2*i)/2);
        int db = min(2*i+1, 2*(N-i)-1);
        print_line(i, FSLASH, 2*nb, '*', db, BSLASH);
    }
    putchar('\n');
}

int main(void)
{
    int N = 5;
    assert(N % 2 == 1);

    driver_1(N);
    driver_1(N+2);
    driver_1(N-2);

    return 0;
}

The repeat_char() and print_line() functions are unchanged from before. The new function, driver_1(), contains two loops, one to process the rows with 0, 1, … N-1 leading blanks, and the other to process the rows with N-1, N-2, … 0 leading blanks. The min() and max() functions are again static inline so that their use is unlikely to incur function call overhead. The loop index, i, controls the number of leading blanks. The expressions for nb and db calculate how many blanks and asterisks to output. Those expressions are the same in both loops; the differences are in the direction of counting (up vs down) and the order of the slash character arguments.

This generates the output:

\    *    /
 \  ***  /
  \*****/
   \***/
    \*/
    /*\
   /***\
  /*****\
 /  ***  \
/    *    \

\      *      /
 \    ***    /
  \  *****  /
   \*******/
    \*****/
     \***/
      \*/
      /*\
     /***\
    /*****\
   /*******\
  /  *****  \
 /    ***    \
/      *      \

\  *  /
 \***/
  \*/
  /*\
 /***\
/  *  \

This demonstrates that the functions work with different sizes of output requested.

Stage 3 — single loop

The final version of the code uses the symmetries of the two loops in driver_1() and uses a single loop over the range 0 .. 2* N - 1 to generate the correct call to print_line(). The only changes are in the driver function, renamed to driver_2() (in part because it was developed in a single executable which also had driver_1() in it). Again, the repeat_char() and print_line() functions are unchanged; and min() and max() are reused too.

The loop determines the value corresponding to i in driver_1() using the expression (strictly, that's a definition, not an expression, but it contains an expression) int i = min(j, 2*N-1-j);. The j term counts up; the 2*N-1-j term counts down; the value used is the smaller of those two. The test for j == i allows the correct choice of first and last character.

#include <assert.h>
#include <stdio.h>

enum { FSLASH = '/', BSLASH = '\\' };

static inline void repeat_char(int num, char c) { for (int i = 0; i < num; i++) putchar(c); }

static inline int max(int x, int y) { return (x > y) ? x : y; }
static inline int min(int x, int y) { return (x < y) ? x : y; }

static void print_line(int l_blanks, char c1, int i_blanks, char c2, int nc, char c3)
{
    assert(i_blanks % 2 == 0);
    assert(nc % 2 == 1);
    repeat_char(l_blanks, ' ');
    putchar(c1);
    repeat_char(i_blanks, ' ');
    repeat_char(nc, c2);
    repeat_char(i_blanks, ' ');
    putchar(c3);
    //repeat_char(l_blanks, ' ');
    putchar('\n');
}

static void driver_2(int N)
{
    assert(N % 2 == 1 && N > 0);
    for (int j = 0; j < 2*N; j++)
    {
        int i = min(j, 2*N-1-j);
        int nb = max(0, (N-1-2*i)/2);
        int db = min(2*i+1, 2*(N-i)-1);
        char c1 = (j == i) ? BSLASH : FSLASH;
        char c3 = (j == i) ? FSLASH : BSLASH;
        print_line(i, c1, 2*nb, '*', db, c3);
    }
    putchar('\n');
}

int main(void)
{

    int N = 5;
    assert(N % 2 == 1);

    driver_2(N);
    driver_2(N+2);
    driver_2(N+4);
    driver_2(N-2);
    driver_2(N-4);

    return 0;
}

This generates the output (note the almost degenerate case for N=1):

\    *    /
 \  ***  /
  \*****/
   \***/
    \*/
    /*\
   /***\
  /*****\
 /  ***  \
/    *    \

\      *      /
 \    ***    /
  \  *****  /
   \*******/
    \*****/
     \***/
      \*/
      /*\
     /***\
    /*****\
   /*******\
  /  *****  \
 /    ***    \
/      *      \

\        *        /
 \      ***      /
  \    *****    /
   \  *******  /
    \*********/
     \*******/
      \*****/
       \***/
        \*/
        /*\
       /***\
      /*****\
     /*******\
    /*********\
   /  *******  \
  /    *****    \
 /      ***      \
/        *        \

\  *  /
 \***/
  \*/
  /*\
 /***\
/  *  \

\*/
/*\

So, there you have it. A complete development cycle in 3 stages. Get something working and demonstrate that it works. Then make the solution more general.

Upvotes: 3

miraklis
miraklis

Reputation: 71

There are many ways to achieve this. One of them is the following

void print_pattern(int n) {

    int padding=0;
    int width=0;
    char char_begin='\\', char_end='/';

    assert(n>0);
    for(int x=0; x<n*2; x++) {
        if(padding==n) {
            padding--;
            char_begin='/';
            char_end='\\';
        }
        width=(n*2)-padding;
        for(int y=0;y<=width; y++) {
            if(y==padding)
                printf("%c",char_begin);
            else
                if (y==width)
                    printf("%c",char_end);
                else 
                    if (y>padding && y>=n-padding && y<=n+padding)
                        printf("*");
                    else
                        printf(" ");
        }
        printf("\n");
        if(x<n)
            padding++;
        else
            padding--;
    }
}

Upvotes: 2

Related Questions