2

I need to remove the recursion from the drawSquare method. There is a lot more I have to do after removing the recursion from that method however the rest I can figure out on my own. I just really need a working solution that does the exact same thing without recursion and I will figure out the rest.

Here is how I made the Square class:

import java.awt.Color;

public class Square {
    final int BLACK = Color.BLACK.getRGB();
    final int WHITE = Color.WHITE.getRGB();
    protected int center_x;
    protected int center_y;
    protected int side;
    protected int color;
    protected Square parentSquare;

    public Square(){
        this.center_x = 0;
        this.center_y = 0;
        this.side = 0;
        this.color = WHITE;
        this.parentSquare = null;
    }
    public Square(int center_x,int center_y,int side,int color){
        this.center_x = center_x;
        this.center_y = center_y;
        this.side = side;
        this.color = color;
        this.parentSquare = null;
    }
    public Square(int center_x,int center_y,int side,int color,Square parentSquare){
        this.center_x = center_x;
        this.center_y = center_y;
        this.side = side;
        this.color = color;
        this.parentSquare = parentSquare;
    }
    public void setX(int center_x){
        this.center_x = center_x;
    }
    public int getX(){
        return center_x;
    }
    public void setY(int center_y){
        this.center_x = center_y;
    }
    public int getY(){
        return center_y;
    }
    public void setSide(int side){
        this.side = side;
    }
    public int getSide(){
        return side;
    }
    public void setColor(int color){
        this.color = color;
    }
    public int getColor(){
        return color;
    }
    public void setParent(Square parentSquare){
        this.parentSquare = parentSquare;
    }
    public Square getParent(){
        return parentSquare;
    }
}

This is the original Tsquare.java that produces a fractal of squares branching from each squares 4 corners until the side = 0: (full TSquare.java class modified to use Square objects)

import java.awt.image.*;
import java.awt.Color;
import java.io.*;
import javax.imageio.*;
import java.util.*;
public class TSquare {
    static final int SIDE = 1000; // image is SIDE X SIDE
    static BufferedImage image = new BufferedImage(SIDE, SIDE, BufferedImage.TYPE_INT_RGB);
    static final int WHITE = Color.WHITE.getRGB();
    static final int BLACK = Color.BLACK.getRGB();
    static Scanner kbd = new Scanner(System.in);

    public static void main(String[] args) throws IOException{
        String fileOut = "helloSquares.png";
        System.out.print("Enter (x,y) coordinates with a space between: ");
        int x = kbd.nextInt();
        int y = kbd.nextInt();
        System.out.println(x+","+y);//TESTLINE TESTLINE TESTLINE TESTLINE
        // make image black
        for (int i = 0; i < SIDE; i++) {
            for (int j = 0; j < SIDE; j++) {
                image.setRGB(i, j, BLACK);
            }
        }
        Square square = new Square(SIDE/2,SIDE/2,SIDE/2,WHITE);
        drawSquare(square);



        // save image
        File outputfile = new File(fileOut);
        ImageIO.write(image, "jpg", outputfile);
    }

    private static void drawSquare(Square square){ // center of square is x,y length of side is s
        if (square.side <= 0){ // base case
            return;
        }else{
            // determine corners
            int left = square.center_x - (square.side/2);
            int top = square.center_y - (square.side/2);
            int right = square.center_x + (square.side/2);
            int bottom = square.center_y + (square.side/2);
            int newColor =square.color-100000;
            Square newSquareA = new Square(left,top,square.side/2,newColor);
            Square newSquareB = new Square(left,bottom,square.side/2,newColor);
            Square newSquareC = new Square(right,top,square.side/2,newColor);
            Square newSquareD = new Square(right,bottom,square.side/2,newColor);
            for (int i = left; i < right; i++){
                for (int j = top; j < bottom; j++){
                    image.setRGB(i, j, square.color);
                }
            }
            // recursively paint squares at the corners
            drawSquare(newSquareA);
            drawSquare(newSquareB);
            drawSquare(newSquareC);
            drawSquare(newSquareD);
        }

    }

}

I'm looking to reproduce the exact actions of this code just minus the recursion and everything I try doesn't seem to work. I cant even get a single white square to display on top of the original black canvas.

candied_orange
  • 7,036
  • 2
  • 28
  • 62
  • 2
    You can simulate recursion using a stack or a queue. I would think it makes your code less readable though. – DSOI__UNUNOCTIUM Sep 15 '19 at 03:32
  • Actually DSOI the next part of the program I have to deal with is implementing the same thing in a LInked Stack. If that's how to simplify doing it I would extremely appreciate some input on that subject. If you could show me how to implement it without recursion using a simple stack I can modify it to work with the Linked Stack – Spilled Milk Factory Sep 15 '19 at 03:41
  • Sorry for late comment, but you can use java.util.Vector. I don't have specific implementation, I can get one if you want though. – DSOI__UNUNOCTIUM Sep 16 '19 at 22:26

2 Answers2

1

If we want readability without compromising speed I suggest first making some additions to Square:

public int half() {
    return side/2;
}
public int left() {
    return center_x - half();
}
public int top() {
    return center_y - half();
}
public int right() {
    return center_x + half();
}
public int bottom() {
    return center_y + half();
}

public void draw(BufferedImage image) {

    int left = left();
    int top = top();
    int right = right();
    int bottom = bottom();

    for (int i = left; i < right; i++){
        for (int j = top; j < bottom; j++){
            image.setRGB(i, j, color);
        }
    }
} //End Square

Also moving I/O out to enable unit testing.

package com.stackoverflow.candied_orange;
import java.awt.image.*;
import java.awt.Color;
import java.io.*;
import javax.imageio.*;
import java.util.*;
public class FractalSquareIterative {

    public static void main(String[] args) throws IOException{

        final int SIDE = 1000; // image is SIDE X SIDE        
        BufferedImage image = new BufferedImage(SIDE,SIDE,BufferedImage.TYPE_INT_RGB);

        drawImage(SIDE, image);
        saveImage(image);
    }

    //Removed IO to enable unit testing
    protected static void drawImage(final int SIDE, BufferedImage image) {
        final int BLACK = Color.BLACK.getRGB();
        final int WHITE = Color.WHITE.getRGB();
        final int HALF = SIDE / 2;

        //Draw background on whole image
        new Square(HALF, HALF, SIDE, BLACK).draw(image);

        //Draw foreground starting with centered half sized square
        Square square = new Square(HALF, HALF, HALF, WHITE);
        drawFractal(square, image);
    }

Now that Square is dealing with all the square things the fractal code is a little easier on the eyes.

    private static void drawFractal(Square square, BufferedImage image){

        Queue<Square> squares = new LinkedList<>();
        squares.add(square);

        while (squares.size() > 0) {

            //Consume
            square = squares.remove();

            //Produce
            int half = square.half();
            if (half > 2) {

                int left = square.left();
                int top = square.top();
                int right = square.right();
                int bottom = square.bottom();
                int newColor = square.color - 100000;                

                squares.add(new Square(left, top, half, newColor));
                squares.add(new Square(left, bottom, half, newColor));
                squares.add(new Square(right, top, half, newColor));
                squares.add(new Square(right, bottom, half, newColor));
            }
            square.draw(image);
        }
    }


    protected static void saveImage(BufferedImage image) throws IOException {
        String fileOut = "helloSquares.png";        
        File outputfile = new File(fileOut);
        ImageIO.write(image, "jpg", outputfile);
    }
} //End FractalSquareIterative

Reliably faster than the recursive version but not significantly so at this size.

If you want a peek at my unit tests you'll find them here.

candied_orange
  • 7,036
  • 2
  • 28
  • 62
  • candied_orange you're the man. Your input above really helped TONS. I was able to convert my drawSquare method to use a LinkedStack without issue. I've been toying with modifying things to get it to display the new squares behind the existing ones rather than on top of them, I figured similarly to my recursive version it would depend on where I put the draw method within the while loop but it seems to do the same thing no matter where I put it. Any chance you'd have a quick fix for getting the squares to generate behind existing squares? – Spilled Milk Factory Sep 15 '19 at 17:04
  • @SpilledMilkFactory short of creating layers of transparent images no. That problem is typically solved by controlling the order of rendering (depth first vs breadth first traversal) I was very careful to produce the same image as your code here. If you can post code that does what your asking for using recursion I can convert it. Probably best to ask a new question. If that's not what you mean clue me in. – candied_orange Sep 15 '19 at 17:16
  • This is what I mean using recursive means, Basically the only difference between my original posted drawSquare method and this one is the placement of the for loop that draws it. If you move the for loop that actually draws each square from above the recursive calls to directly after the recursive calls it paints them behind eachother. I tried to paste the different code but it was too long for the comment box. the placement of the nested for loop is the only difference when using recursion. I suppose all things considered it is still technically painting them on top but starting at the end – Spilled Milk Factory Sep 15 '19 at 17:29
  • Show me the code in a different question. Should give you plenty of room. I need to see the results you're expecting. – candied_orange Sep 15 '19 at 17:45
  • https://stackoverflow.com/questions/57946906/how-do-i-make-my-squares-generate-behind-each-other-rather-than-on-top-of-eachot – Spilled Milk Factory Sep 15 '19 at 18:12
0

Here's one implementation using an ArrrayDeque ( https://docs.oracle.com/javase/7/docs/api/java/util/ArrayDeque.html ).

It's worth comparing ArrayDeque against some other Java types :

a) Stack is an interface, and the api page (https://docs.oracle.com/javase/8/docs/api/java/util/Stack.html) says

A more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class.

b) I originally wrote this using good old familiar ArrayList, with Square square = squares.remove(0); instead of pop. I am quite surprised at how much faster this ArrayDeque implementation appears to be than that ArrayList (not that I have run any formal benchmarks)

private static void drawSquare(Square startSquare){

    Deque<Square> squares = new ArrayDeque<Square>(400000);
    squares.push(startSquare);

    while (!squares.isEmpty()) {
        Square square = squares.pop();

        System.out.println(square);
        // center of square is x,y length of side is s
        if (square.side > 0){ // base case
            // determine corners
            int left = square.center_x - (square.side/2);
            int top = square.center_y - (square.side/2);
            int right = square.center_x + (square.side/2);
            int bottom = square.center_y + (square.side/2);
            int newColor =square.color-100000;
            addSquare(squares, left,top,square.side/2,newColor);
            addSquare(squares, left,bottom,square.side/2,newColor);
            addSquare(squares, right,top,square.side/2,newColor);
            addSquare(squares, right,bottom,square.side/2,newColor);

        }
    }
}
private static void addSquare(Deque<Square> squares, int x, int y, int side, int color) {
    // STRONGLY recommend having this "if" statement !
    // if (side > 0) {
           squares.push(new Square(x, y, side, color));
    // }
}

As noted in my comment, it is WELL worthwhile to not create squares of size 0 rather than creating them and simply ignoring them when their turn comes around. This would be true for the recursion-based operations as well - but especially so for these non-recursion based ones, since the multitude of squares would be really eating up memory and processing time.

racraman
  • 4,988
  • 1
  • 16
  • 16
  • Thank you so much for your response, your above code helps a lot. I'm required to use a LinkedStack interface but seeing this done with a Deque certainly helps to wrap my mind around the removal of the recursion nonetheless – Spilled Milk Factory Sep 15 '19 at 17:15