7
\$\begingroup\$

I'm not so good with recursion, so I've decided to tackle an exercise consisting of drawing this pattern using characters:

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

Here is my code so far. What could be improved? Furthermore, is recursion suitable for a task like this or not?

class Main
{
    public static void main (String[] args)
    {
        Main.drawDiamond("*", 0, 5 );
    }

    public static void drawDiamond( String seed, int turn, int centerLevel ){

        if( seed.length() <= 0 ){
            return;
        }

        char[] chars = new char[Math.abs(turn-centerLevel-1)];
        Arrays.fill(chars, ' ' );
        String spaces = new String(chars);

        System.out.print( spaces );

        System.out.println( seed );

        if( turn <= centerLevel ){
            seed += "**";
        } else {
            if( seed.length() >= 3 ) {
                 seed = seed.substring(1,seed.length()-1); 
            } else {
                seed = "";
            }
        }

        turn++;

        drawDiamond( seed, turn, centerLevel );
    }
}
\$\endgroup\$

3 Answers 3

3
\$\begingroup\$

You can make this a recursion exercise with two parts:

1) Write a recursive method to repeat a char n times.

Signature:

public static String getCharRepeatedTimes(final char c, final int times)

(Read at end to see a solution)

2) Write a recursive method to create a diamond pattern.

Signature for recursive start method:

public static String getDiamondWithSize(final int size)

Signature for recursive "work" method:

private static String getDiamondWithSize(final int size, final int currentSize)

Hint 1: You can use the method from 1)
Hint 2: Think about a pattern in the diamon and how to exploit it.

(Read at end to see a solution)


Let us start with 1):

public static String getCharRepeatedTimes(final char c, final int times) {
    if (times == 0)
        return "";
    return String.valueOf(c) + getCharRepeatedTimes(c, times - 1);
}

And continue with 2):

public static String getDiamondWithSize(final int size) {
    return getDiamondWithSize(size, 2 - (size & 1));
}

The start method can be done in two ways. Starting from the outer area to the inner area or vice-versa. I have done the out->in way. If you allow even and odd diamonds, then we have to manage the start or end size in the correct way. There the 2 - (size & 1) part, which is 2 for even size and 1 for odd size.
The idea is now to create the outer lines first and append it to the next inner part then. Until the inner part is the full part.

private static String getDiamondWithSize(final int size, final int currentSize) {
    if (currentSize == size)
        return getCharRepeatedTimes('*', size) + "\n";
    final int nonDiamondSpace = (size - currentSize) / 2;
    final String currentLine = getCharRepeatedTimes(' ', nonDiamondSpace) + getCharRepeatedTimes('*', currentSize) + "\n";
    return currentLine + getDiamondWithSize(size, currentSize + 2) + currentLine;
}

The + concatenation could be replaced by StringBuilder , insert and append. For clarity and the exercise purpose, it is not done here.


Suggestions for your code:

public static void drawDiamond(String seed, int turn, final int centerLevel) {
    ...
    if (turn <= centerLevel)

Noone would expect this if the method is called. At least for me, it is unclear how the argument centerLevel is related to the size of the diamond. You should add at least some JavaDoc, I would prefer to change the behavior.

    turn++;
    drawDiamond(seed, turn, centerLevel);

In general, you should avoid to change arguments, if possible. In this case, we can go with this:

    drawDiamond(seed, turn + 1, centerLevel);

This looks a bit complex:

    if( turn <= centerLevel ){
        seed += "**";
    } else {
        if( seed.length() >= 3 ) {
             seed = seed.substring(1,seed.length()-1); 
        } else {
            seed = "";
        }
    }

You should add at least some comments. And you can decrease the branch depth by one:

    if (turn <= centerLevel) //until the mid is reached, increase by two stars
        seed += "**";
    else if (seed.length() >= 3) //until the end is reached, reduce by 2 chars
        seed = seed.substring(1, seed.length() - 1);
    else //finished
        seed = "";

Rest is more a subjective view. I would rather introduce a method to generate a char/String repeated n times than doing it inside the diamond method. And I would avoid the seed argument as shown above.

\$\endgroup\$
1
\$\begingroup\$

The trick to getting this simple is expressing the start index of the stars and the number of stars as a mathematical function of the line they are on. Math.abs() is your friend for creating that turning point.

I basically played around in excel to tune for the right formulas.

As a kicker you can fill a String of stars and a String of spaces and use substring to avoid filling arrays over and over again. substring() does not copy the underlying array.

public class DrawDiamond {

    public static void main(String[] args) {
        new DrawDiamond().draw(7);
    }

    private void draw(int max) {
        String maxStars = filledString(max, '*');
        String maxSpaces = filledString(max, ' ');
        for (int i = 0; i < max; i++) {
            String spaces = maxSpaces.substring(0, startIndex(max, i));
            String stars = maxStars.substring(0, width(max, i));
            System.out.println(spaces + stars);
        }
    }

    private int width(int max, int line) {
        return  -Math.abs(line - (max - 1) / 2) * 2 + max;
    }

    private int startIndex(int max, int line) {
        return Math.abs(line - (max + 1) / 2 + 1);
    }

    private String filledString(int width, char val) {
        char[] characters = new char[width];
        Arrays.fill(characters, val);
        return new String(characters);
    }

}
\$\endgroup\$
4
  • 1
    \$\begingroup\$ But then it is not recursive any more ;) \$\endgroup\$
    – konijn
    Commented Aug 8, 2013 at 21:46
  • 2
    \$\begingroup\$ @tomdemuyt That the OP attempted to solve it with recursion, does not imply it is the most elegant solution. \$\endgroup\$
    – bowmore
    Commented Aug 8, 2013 at 22:16
  • \$\begingroup\$ It's not recursive but I like how the problem has been separated into different tasks. I have changed the title to suit. \$\endgroup\$
    – James P.
    Commented Aug 9, 2013 at 8:09
  • 1
    \$\begingroup\$ It's not a problem that lends itself to tackling with recursion, unless you find an easy way to draw an n size diamond starting from an n-2 size diamond \$\endgroup\$
    – bowmore
    Commented Aug 9, 2013 at 9:41
1
\$\begingroup\$

My few cents,

  1. Probably this is not the best exercise for recursive coding.
  2. the caller should not have to know about the middle parameter, ideally it would just be Main.drawDiamond("*", 5 );
  3. seed += "**"; this should use the seed, which might not be "*" but "#"

4.

char[] chars = new char[Math.abs(turn-centerLevel-1)];
Arrays.fill(chars, ' ' );
String spaces = new String(chars);
System.out.print( spaces );

Could have been replaced with a loop that prints a space turn-centerLevel-1 times.

Finally, if you think about the progresses of spaces and stars, then you see that you can do something with recursion.

              Stars Spaces
      *        1    6
     ***       3    5
    *****      5    4
   *******     7    3
  *********    9    2
 ***********   11   1
*************  13   0
 ***********   11   1
  *********    9    2
   *******     7    3
    *****      5    4
     ***       3    5
      *        1    6

Now if you consider the spaces as a negative number past 0, you can do this:

public class Diamond{

  public static void main( String[] args ) {
    draw( '*' , 5 );
  }

  static void draw( char c  , int size ) {
    recurse( c , 1 , size++ );
  }

  static void recurse( char c , int count , int spaces ) {
    for( int i = 0 ; i < Math.abs( spaces ) ; i++ )
      System.out.print(" ");
    for( int i = 0 ; i < count ; i++ )
      System.out.print( c );
    System.out.println("");
    spaces--;
    count = count + ( spaces >= 0 ? +2 : -2 );
    if( count > 0 )
      recurse( c , count , spaces );.
  }

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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