2
\$\begingroup\$

By this method I am able to solve my problem, but is there any other way that I can make this type of triangle? Please correct my code if I am doing this the long way.

public class Pyramid{
    public static void main(String [] args){
        for (int i=0;i<= 6 ;i++) {
            for (int x=0;x < 6-i;x++) {
                System.out.print(" ");
                }
                for (int k=1;k<=i;k++) {
                        if (k <= 1) {
                            System.out.print(" *");
                        }
                        else if(k <= 2) {
                            System.out.print("  ");
                        }
                        else if(k <= 3) {
                            System.out.print("  ");
                        }
                        else if(k <= 4) {
                            System.out.print("  ");
                        }
                        else if(k <= 5) {
                            System.out.print("  ");
                        }
                        else if(k <= 6) {
                            System.out.print("  ");
                        }

                }
                for (int l=0;l<=i;l++) {
                        if (l <= 0) {
                            System.out.print(" *");
                        }

                }
                System.out.println();

    }   
    System.out.print("* * * * * * * *");    
}
}
\$\endgroup\$
  • 1
    \$\begingroup\$ (Welcome to CR!) (I find the indentation misleading.) Is there anything specific you are not quite satisfied with? What if the number of layers was 42 instead of 6? \$\endgroup\$ – greybeard Mar 18 '18 at 15:55
  • \$\begingroup\$ I am not satisfied because i think i hard-code this problem instead of dynamic that why i am seeking help so i can find my mistake and learn from that mistake in future @greybeard \$\endgroup\$ – Raza Khan Mar 18 '18 at 16:11
  • \$\begingroup\$ if the number of layers was 42 then i have to put 42 layers and its so wrong i need solution i am working on it for 5 hours and i am new in java \$\endgroup\$ – Raza Khan Mar 18 '18 at 16:14
  • \$\begingroup\$ Can you provide the result of what this outputs? \$\endgroup\$ – Simon Forsberg Mar 18 '18 at 17:59
1
\$\begingroup\$

Your logic is completely static and will be impossible to maintain for bigger shape, but that kind of shape have a specific pattern.

The pattern

For a height (4), the output should be : (space replace by a dot for readability).

...* 
..*.*
.*...*
* * * * 

We can see that the left spaces start at 3 (height - 1) and decreased by one each time.

When we have two stars, the spaces between those is odd, incremented by two each row. If we use a bigger triangle, we see the following pattern :

  • 1 space //Row 1 (using an 0-index based, first row don't have that spaces)
  • 3 spaces //Row 2
  • 5 spaces //Row 3
  • 7 spaces //Row 4

This can be calculated with (#row * 2 - 1)

The last row is a bit different, we need to print 4 stars (height) separated by a space

Let's code

First, we need a way to easily print a number of spaces (the only variable in that pattern).

 public static String blankString(int length);

The method can be quite verbose using a loop but I like to use String.format here. This formatting allow use to add space padding to a String easily. More information can be found at How can I pad a String in Java?

public static String blankString(int length){
    if(length == 0) 
         return ""; //%0s is not supported
    else 
         return String.format("%" + length + "s", "");
}

A one-line method using a ternary operator, because it is nice to use

return (length == 0) ? "" : String.format("%" + length + "s", "");

The triangle

   * 
  * *
 *   *
* * * * 

Let's use the previous method to print the triangle like explained before

System.out.println(blankString(3) + "*"); // ...*
System.out.println(blankString(2) + "*" + blankString(1) + "*"); // ..*.*
System.out.println(blankString(1) + "*" + blankString(3) + "*"); // .*...*
for(int i = 0; i < 4; ++i){
    System.out.print("* "); //*.*.*.*.
} 

Great, this works, know let's update the code with a variable height.

int height = 4;
System.out.println(blankString(height - 1) + "*");      //Row 0
System.out.println(blankString(height - 1 - 1) + "*" + blankString((1 * 2) - 1) + "*");  //Row 1
System.out.println(blankString(height - 1 - 2) + "*" + blankString((2 * 2) - 1) + "*");  //Row 2
for(int i = 0; i < height; ++i){
    System.out.print("* ");
}

Note that we find the row# in the parameter send to blankString

int height = 4;

//Row 0
System.out.println(blankString(height - 1) + "*"); 

// Following rows
for(int row = 1; row < height -1 ; ++row){
    System.out.println(blankString(height - 1 - row) + "*" + blankString((row * 2) - 1) + "*");  //Row 1
}

//Last row
for(int i = 0; i < height; ++i){
    System.out.print("* ");
}

Of course, we end up with a method two allow us to change height

public static void buildTriangle(int height){
    //Row 0
    System.out.println(blankString(height - 1) + "*"); 

    // Following rows
    for(int row = 1; row < height -1 ; ++row){
        System.out.println(blankString(height - 1 - row) + "*" + blankString((row * 2) - 1) + "*");  //Row 1
    }

    //Last row
    for(int i = 0; i < height; ++i){
        System.out.print("* ");
    }
}

And with buildTriangle(8) :

       *
      * *
     *   *
    *     *
   *       *
  *         *
 *           *
* * * * * * * * 

Note, most of the code need to be protected against negative parameters, but since this pattern was not easy to explain, I preferred to focus the scope on the logic, not the parameter safety.

\$\endgroup\$
  • \$\begingroup\$ It's been a while since my last review, so if I messed something in my post, let me know! \$\endgroup\$ – AxelH Mar 19 '18 at 13:32
  • \$\begingroup\$ yeah its working and its understandable i am confused about is there any way that i can make it dynamic i think that's not possible but your method is also simple for beginners @AxelH \$\endgroup\$ – Raza Khan Mar 20 '18 at 6:32
  • \$\begingroup\$ @RazaKhan What do you want to be dynamic ? Here I have only thought about the height (layers as you called it) of the shape to be a parameter... \$\endgroup\$ – AxelH Mar 20 '18 at 7:04
3
\$\begingroup\$

Your method certainly works, but I agree that using nested/multiple loops and inner if/else blocks is inelegant, if only because it makes following the code somewhat harder. Also, as you point out in a comment, hard-coding the structure is a nightmare if you suddenly want a pyramid which is 46 stars high.

As an alternative, I've rewritten the code to break it out into separate methods which hopefully make the code easier to understand, and also to allow a variable pyramid height (any value from zero or above).

public static void main(String[] args) {
    drawStarPyramid2(8);
}

private static void drawStarPyramid2(int height) {
    if (height < 0) {
        throw new IllegalArgumentException("Height cannot be negative.");
    }
    StringBuilder sb = new StringBuilder(height * height);
    appendSides(sb, height);
    appendBaseline(sb, height);
    System.out.println(sb.toString());
}

private static void appendSides(StringBuilder sb, int height) {
    int baselineWidth = height * 2 - 1;
    int midLine = baselineWidth / 2;
    for (int rowIndex = 0; rowIndex < height - 1; ++rowIndex) {
        int leftStarOffset = midLine - rowIndex;
        appendSpacesThenStar(sb, leftStarOffset);
        if (rowIndex > 0) {
            int rightStarOffset = midLine + rowIndex - 1;
            appendSpacesThenStar(sb, rightStarOffset - leftStarOffset);
        }
        sb.append("\n");
    }
}

private static void appendSpacesThenStar(StringBuilder sb, int spaces) {
    for (int spaceIndex = 0; spaceIndex < spaces; ++spaceIndex) {
        sb.append(' ');
    }
    sb.append('*');
}

private static void appendBaseline(StringBuilder sb, int stars) {
    boolean first = true;
    for (int starIndex = 0; starIndex < stars; ++starIndex) {
        if (!first) {
            sb.append(' ');
        }
        sb.append('*');
        first = false;
    }
}

Because the sides need to be rendered differently to the baseline, each chunk of code goes into its own method, with a name which clearly describes the different purpose of each.

The appendSides method is responsible only for drawing the left and right sides of the triangle. This calculates the baseline width (the number of characters on the console counting stars and spaces) and this will be equal to height stars (because the number of stars on the baseline must equal the number of stars along the height of each side) plus height - 1 spaces, which gives 2 * height - 1. Then it calculates the mid-way point (in terms of characters on the console line) and then places the first star at this point.

For all subsequent lines, the "left" side star is simply placed at the position which is one additional character further left of the previous position (starting at the mid-way position), and the "right" side star at the position which is one additional character further right of the previous position. The rowIndex is a convenient way to calculate how many places to move away from the mid-way point on each line. The mathematics being used to calculate all of these offsets is fairly simple, but I recommend you sketch it out on a bit of paper, one line at a time, to get a feel for how the numbers work together.

The appendSpacesThenStar method simply appends the specified number of space characters to the StringBuilder and then a star. This method does not append a newline, because this method is often called twice for the same line, to write the "left" and then the "right" stars.

The appendBaseline method just appends (to the StringBuilder) the required number of stars, separated by spaces.

A StringBuilder is being used to make assembly of the final string sequence more efficient, allowing the building of each line to be split across multiple method calls, rather than trying to finish each line in a single statement.

(Also note that the initial capacity passed to the StringBuilder constructor can be improved by giving exactly the number of characters that will be needed, once spaces, stars, and newlines have been taken into consideration, but I'll leave this as an exercise for you.)

\$\endgroup\$
  • \$\begingroup\$ Sir i am new in java and in the learning process i am understanding your code little bit but its hard for me to understand clearly and i cant explain it to my Professor because there are many thing i am not understanding \$\endgroup\$ – Raza Khan Mar 18 '18 at 16:20
  • \$\begingroup\$ I think appendBaseline() could be as straightforward as appendSpacesThenStar() using sb.append('*'); for (int done = 1; done < stars; ++done ) sb.append(' ').append('*'); \$\endgroup\$ – greybeard Mar 18 '18 at 16:30
  • \$\begingroup\$ @RazaKhan I've added some narrative about how the primary three methods work, so it's hopefully easier to understand what they're doing. \$\endgroup\$ – Bobulous Mar 18 '18 at 16:37
  • \$\begingroup\$ @greybeard the appendBaseline method could be written a few ways, but don't forget that this code currently permits a height of zero, so a zero-check would be needed before switching to your approach. If performance was critical then your approach is probably more efficient, though. \$\endgroup\$ – Bobulous Mar 18 '18 at 16:39
  • \$\begingroup\$ Things I liked: readability over brevity; sizing the StringBuilder. \$\endgroup\$ – greybeard Mar 18 '18 at 16:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.