0

I am trying to animate the partition part of the quick sort algorithm using the JavaFX library. I got all the graphics portion of the program down, but I am having trouble with my step method. There is a logic error in my step method, and I can't seem to figure out what it is. The step method is supposed to do one step every time the step button is clicked.

private void quickSort(int lowerIndex, int higherIndex) {

    int i = lowerIndex;
    int j = higherIndex;
    // calculate pivot number, I am taking pivot as middle index number
    int pivot = array[lowerIndex+(higherIndex-lowerIndex)/2];
    // Divide into two arrays
    while (i <= j) {
        /**
         * In each iteration, we will identify a number from left side which
         * is greater then the pivot value, and also we will identify a number
         * from right side which is less then the pivot value. Once the search
         * is done, then we exchange both numbers.
         */
        while (array[i] < pivot) {
            i++;
        }
        while (array[j] > pivot) {
            j--;
        }
        if (i <= j) {
            exchangeNumbers(i, j);
            //move index to next position on both sides
            i++;
            j--;
        }
    }
    // call quickSort() method recursively
    if (lowerIndex < j)
        quickSort(lowerIndex, j);
    if (i < higherIndex)
        quickSort(i, higherIndex);
}

The code above is the source code for the quick sort algorithm, the step method should do just one step of the quick sort algorithm. I tried to implement the same logic in the step method but my logic is wrong. The correct output of this program is at this link http://www.cs.armstrong.edu/liang/animation/web/QuickSortPartition.html for reference.

Below is the code that I am working with

 import java.util.Arrays;
 import javafx.application.Application;
 import javafx.geometry.Pos;
 import javafx.scene.Scene;
 import javafx.scene.control.Button;
 import javafx.scene.layout.BorderPane;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.Pane;
 import javafx.scene.paint.Color;
 import javafx.scene.shape.Line;
 import javafx.scene.shape.Rectangle;
 import javafx.scene.text.Text;
 import javafx.stage.Stage;

 public class AnimateQuickSort extends Application {
    public final static int ARRAY_SIZE = 20;
    private int[] array = new int[ARRAY_SIZE];
    private int leftP = 1;
    private int rightP = array.length - 1;
    private int pivot = 0;

public void start(Stage primaryStage) {
    AnimationPane pane = new AnimationPane();

    Button btStep = new Button("Step");
    Button btReset = new Button("Reset");

    HBox hBox = new HBox(5);
    hBox.getChildren().addAll(btStep, btReset);
    hBox.setAlignment(Pos.CENTER);

    BorderPane borderPane = new BorderPane();
    borderPane.setCenter(pane);
    borderPane.setBottom(hBox);

    // Create a scene and place it in the stage
    Scene scene = new Scene(borderPane, 700, 225);
    primaryStage.setTitle("QuickSort");
    primaryStage.setScene(scene);
    primaryStage.show();

    initializeArray();

    pane.repaint();

    btStep.setOnAction(e -> {
        if (step()) {
            pane.repaint();
        } else {
            pane.repaint();
        }
    });

    btReset.setOnAction(e -> {
        reset();
        pane.repaint();
    });
}

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

public void initializeArray() {

    for (int i = 0; i < array.length; i++) {
        array[i] = (int) (Math.random() * 999 + 1);
    }

    // Arrays.sort(array);

}

public void reset() {
    leftP = 1;
    rightP = array.length - 1;
    pivot = 0;
    initializeArray();
}

public boolean step() {
    // quicksort(array);
    int begin = 0;
    int pivotVal = array[0];
    int array1;

    if (leftP <= rightP && array[leftP] <= pivotVal) {
        leftP++;
        array1 = array[leftP];
        array[leftP] = array[rightP];
        array[rightP] = array1;
    }

    if (rightP >= leftP && array[rightP] > pivotVal) {
        rightP--;
    }
    if (rightP > leftP) {
        if (rightP != begin) {
            array1 = array[rightP];
            array[rightP] = array[begin];
            array[begin] = array1;
        }
    }
    // Return the index of the pivot element.
    return true;
}

class AnimationPane extends Pane {
    private int startingX = 20;
    private int startingY = 20;
    private int boxWidth = 30;
    private int boxHeight = 20;

    protected void repaint() {
        this.getChildren().clear();

        int x = startingX + 10;
        int y = startingY + 40;

        // Display array
        x = startingX + 10;
        getChildren().add(new Text(x - 15, y + 55, "Array "));
        x += 20;
        getChildren().add(new Text(x + pivot * boxWidth, y + 120, "Pivot"));
        drawArrowLine(x + 15 + pivot * boxWidth, y + 100, x + 15 + pivot * boxWidth, y + 40 + boxHeight);

        getChildren().add(new Text(x - 14 + leftP * boxWidth, startingY, "Left Pointer"));
        drawArrowLine(x + 15 + leftP * boxWidth, startingY, x + 15 + leftP * boxWidth, y + 40);

        getChildren().add(new Text(x - 14 + rightP * boxWidth, startingY, "Right Pointer"));
        drawArrowLine(x + 15 + rightP * boxWidth, startingY, x + 15 + rightP * boxWidth, y + 40);

        for (int k = 0; k < array.length; k++) {
            Rectangle rectangle = new Rectangle(x, y + 40, boxWidth, boxHeight);
            rectangle.setFill(Color.WHITE);
            rectangle.setStroke(Color.BLACK);
            getChildren().add(rectangle);
            if (array[k] != 0) {
                getChildren().add(new Text(x + 5, y + 55, array[k] + ""));
            }
            x = x + boxWidth;
        }
    }

    public void drawArrowLine(double x1, double y1, double x2, double y2) {
        getChildren().add(new Line(x1, y1, x2, y2));

        // find slope of this line
        double slope = ((((double) y1) - (double) y2)) / (((double) x1) - (((double) x2)));

        double arctan = Math.atan(slope);

        // This will flip the arrow 45 off of a
        // perpendicular line at pt x2
        double set45 = 1.57 / 2;

        // arrows should always point towards i, not i+1
        if (x1 < x2) {
            // add 90 degrees to arrow lines
            set45 = -1.57 * 1.5;
        }

        // set length of arrows
        int arrlen = 15;

        // draw arrows on line
        getChildren().add(new Line(x2, y2, (x2 + (Math.cos(arctan + set45) * arrlen)),
                ((y2)) + (Math.sin(arctan + set45) * arrlen)));

        getChildren().add(new Line(x2, y2, (x2 + (Math.cos(arctan - set45) * arrlen)),
                ((y2)) + (Math.sin(arctan - set45) * arrlen)));
    }
}
 }
CodeDon
  • 38
  • 7

2 Answers2

1

You can't single-step a recursive function without coroutines (there are several libraries for coroutines in Java, but most of them tweak JVM or bytecode, so not really standard stuff).

You basically have two choices:

  • Invert responsibilities, if you can: instead of animation calling single-step of QuickSort, have QuickSort call animation redraw.

  • Or, in order to implement a function that does a single-step of QuickSort, you will need to unroll the recursion, which will require you to keep track of the "depth" using an explicit stack of from-to pairs. The stack starts with "whole array"; at the top of the function, you pop a state and sort that range; when you would have recursed you push the smaller range onto the stack; you are done when the stack is empty.

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • I need to unroll the recursion but I need to do it without using a stack, there must be a simpler way. – CodeDon May 01 '16 at 02:25
0

The "hacky" solution

This requires accessing the com.sun.javafx.tk.Toolkit, which isn't especially good practice, since it resides in the com.sun packages (see It is a bad practice to use Sun's proprietary Java classes? ).

This however allows you to "pause" the execution of a method called from the fxApplication thread until a event occurs.

Use

Toolkit.getToolkit().enterNestedEventLoop(loopId);

to "pause" the method and

Toolkit.getToolkit().exitNestedEventLoop(loopId, value);

to resume the method.

loopId needs to be the same Object.

enterNestedEventLoop returns the value passed to exitNestedEventLoop.

Using these methods you can simply add the animations to the quicksort method and "pause" when a step is complete and "resume" execution from the Button event handlers.

The clean solution

You basically save every state of the animation to a list so that it can be executed step by step:

Change your AnimationPane to support the following methods:

  • setUpperArrowPosition(int), setPivotArrowPosition(int) and setLowerArrowPosition(int) that change the position of the upper, pivot and lower arrows respectively.
  • swap(int, int) this method swaps the visual representations of 2 elements of the array.

This way you not only have a cleaner design, but it's also easier to "save" the change operations to a List:

Create a field List<Comsumer<AnimationPane>> steps. Every element in this list changes the state of the AnimationPane to represent the state after each step using the new methods defined above. Before actually doing the sorting you initialize the AnimationPane. Then during the sorting you simply add all the changes that need to be done to the UI as Consumers to the list for later execution, e.g. instead of

while (array[i] < pivot) {
    i++;
}

use

while (array[i] < pivot) {
    i++;
    steps.add(animationPane -> {
         animationPane.setLowerArrowPosition(i);
    });
}

You may need to adjust the algorithm a bit to correctly group the ui representations, but that should't be too hard.

After the quicksort method has finished, you now have a List of Comsumers available, that change the UI to it's next state each. You can simply use a Iterator to get through the animation step by step:

Iterator<Consumer<AnimationPane>> iter = steps.iterator();
Button nextButton = new Button("Step");
button.setOnAction(evt -> {
    if (iter.hasNext()) {
         iter.next().accept(animationPane);
    }
});
Community
  • 1
  • 1
fabian
  • 80,457
  • 12
  • 86
  • 114