// Notes: // It was tricky to get the algorithm to display one step at a time. I ended up running the // alg from the beginnign to the desired (goal) step each time. // In this setup, the entire thing is redrawn every time, so you can't just add the last thing. // I think the MergerSorter inner class was a good idea. import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.Graphics; import java.awt.Dimension; import java.awt.Color; import java.awt.event.MouseEvent; import java.awt.Point; import javax.imageio.ImageIO; import javax.swing.JButton; import javax.swing.JSlider; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JFrame; import javax.swing.event.MouseInputAdapter; import javax.swing.event.ChangeListener; import javax.swing.event.ChangeEvent; import java.util.*; /** * Creates a window with two text fields, two buttons, and a large * display area. The app then tracks the mouse over the display area * and follows the mouse with a circle. **/ public class MergeSortViz extends JFrame { /** holds the drawing canvas **/ private MergeSortVizPanel canvas; /** height of the main drawing window **/ int height; /** width of the main drawing window **/ int width; // All these fields control the state of the simulation private int state = 0; private MergeSorter mergeSorter; private boolean runMode = false; private boolean doStep = false; private int updateStep = 0; private int skipSteps = 50; /** * Creates the main window * @param height the height of the window in pixels * @param width the width of the window in pixels **/ public MergeSortViz( int height, int width ) { super("MergeSortViz Display"); this.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); this.canvas = new MergeSortVizPanel( height, width ); this.add( this.canvas, BorderLayout.CENTER ); this.pack(); this.setVisible( true ); //this.fieldX = new JLabel("X"); //this.fieldY = new JLabel("Y"); JLabel skipLabel = new JLabel( "Speed" ); JSlider skipSlider = new JSlider( 1, 100, this.skipSteps ); Hashtable labels = new Hashtable(); labels.put( 2, new JLabel("Fast") ); labels.put( 99, new JLabel("Slow") ); skipSlider.setLabelTable( labels ); skipSlider.setPaintLabels(true); JButton stepButton = new JButton("Step"); JButton runButton = new JButton("Run"); JButton resetButton = new JButton("Reset"); JButton quit = new JButton("Quit"); JPanel panel = new JPanel( new FlowLayout(FlowLayout.RIGHT)); //panel.add( this.fieldX ); //panel.add( this.fieldY ); panel.add( skipLabel ); panel.add( skipSlider ); panel.add( stepButton ); panel.add( runButton ); panel.add( resetButton ); panel.add( quit ); this.add( panel, BorderLayout.SOUTH); this.pack(); Control control = new Control(); SliderChangeListener sliderChangeListener = new SliderChangeListener(); this.addKeyListener(control); this.setFocusable(true); this.requestFocus(); skipSlider.addChangeListener( sliderChangeListener ); stepButton.addActionListener( control ); runButton.addActionListener( control ); resetButton.addActionListener( control ); quit.addActionListener( control ); this.width = width; this.height = height; this.mergeSorter = new MergeSorter(); } // -------------------------------------------------------------------------------- // MergeSorter inner class private class MergeSorter { private int[] orig_array; private int goalStep; private int curStep; private boolean doneSorting; public MergeSorter() { this.orig_array = new int[8]; this.orig_array[0] = 4; this.orig_array[1] = 6; this.orig_array[2] = 10; this.orig_array[3] = 3; this.orig_array[4] = 12; this.orig_array[5] = 9; this.orig_array[6] = 1; this.orig_array[7] = 7; this.curStep = -1; this.goalStep = 0; this.doneSorting = false; } public void reset() { this.curStep = -1; this.goalStep = 0; this.doneSorting = false; } public String intArrayToString( int[] a ) { String ret = "["; if (a.length >= 1) ret += Integer.toString(a[0]); for (int i = 1; i < a.length; i++) ret += "," + Integer.toString(a[i]); ret += "]"; return ret; } // merge and make a new array. public int[] merge( int[] a, int[] b ) { int[] merged = new int[a.length+b.length]; int ai = 0, bi = 0, mi = 0; for (mi = 0; mi < merged.length; mi++) { if (ai == a.length || bi == b.length) break; if (a[ai] <= b[bi]) { merged[mi] = a[ai]; ai++; } else { merged[mi] = b[bi]; bi++; } } // take care of the left-overs if (ai < a.length) { for (; mi < merged.length; mi++) { merged[mi] = a[ai]; ai++; } } else if (bi < b.length) { for (; mi < merged.length; mi++) { merged[mi] = b[bi]; bi++; } } return merged; } // merge a[start] .. a[mid] // with a[mid+1] .. a[end] public void merge( int[] a, int start, int mid, int end ) { int[] merged = merge( Arrays.copyOfRange( a, start, mid+1 ), Arrays.copyOfRange( a, mid+1, end+1 ) ); System.arraycopy( merged, 0, a, start, merged.length ); } private void drawBox(int[] a, int start, int end, int level, Graphics g, boolean atEnd) { int x = 80+start*40; int y = 100 + level * 40; int w = (end-start+1)*40; int h = 40; g.setColor( Color.LIGHT_GRAY ); g.fillRect(x, y-h, w-3, h-5 ); // x y w h if (!atEnd) g.setColor( Color.BLUE ); g.drawRect(x, y-h, w-3, h-5 ); // x y w h g.setColor( Color.BLACK ); g.drawString( intArrayToString(Arrays.copyOfRange( a, start, end+1 )), x+5, y-20 ); } //merges (in place) a[start]..a[end] // returns true if aborted. private boolean mergeSort(int[] a, int start, int end, int level, Graphics g) { if (this.curStep == this.goalStep) return true; // we need to stop early, because we reached the step we were going to. this.curStep++; if( start == end ) { drawBox( a, start, end, level, g, true ); return false; } drawBox( a, start, end, level, g, false ); boolean aborted; aborted = mergeSort(a, start, (start+end)/2, level+1, g); if (!aborted) aborted = mergeSort(a, (start+end)/2+1, end, level+1, g); if (!aborted) { if (this.curStep == this.goalStep) return true; // we need to stop early, because we reached the step we were going to. this.curStep++; merge(a, start, (start+end)/2, end); } if (!aborted) drawBox( a, start, end, level, g, true ); return aborted; } public void mergeSortStep(Graphics g) { this.curStep = -1; int[] a = new int[this.orig_array.length]; System.arraycopy(this.orig_array, 0, a, 0, this.orig_array.length); boolean aborted = mergeSort(a, 0, a.length-1, 0, g); if (!aborted) this.doneSorting = true; } } // end class MergeSorter // -------------------------------------------------------------------------------- public void update() { if (this.mergeSorter.doneSorting) this.runMode = false; this.updateStep = (this.updateStep+1) % this.skipSteps; if (this.updateStep != 0) return; if (this.runMode && !this.mergeSorter.doneSorting) this.mergeSorter.goalStep++; if (this.doStep) { this.mergeSorter.goalStep++; this.doStep = false; } this.repaint(); } private class MergeSortVizPanel extends JPanel { /** * Creates the drawing canvas * @param height the height of the panel in pixels * @param width the width of the panel in pixels **/ public MergeSortVizPanel(int height, int width) { super(); this.setPreferredSize( new Dimension( width, height ) ); this.setBackground(Color.white); } /** * Method overridden from JComponent that is responsible for * drawing components on the screen. The supplied Graphics * object is used to draw. * * @param g the Graphics object used for drawing */ public void paintComponent(Graphics g) { super.paintComponent(g); if (mergeSorter == null) return; // do whatever drawing is necessary mergeSorter.mergeSortStep(g); } } // end class MergeSortVizPanel private class Control extends KeyAdapter implements ActionListener { public void keyTyped(KeyEvent e) { if( ("" + e.getKeyChar()).equalsIgnoreCase("q") ) { state = 1; } } public void actionPerformed(ActionEvent event) { if( event.getActionCommand().equalsIgnoreCase("Run") ) { runMode = true; } if( event.getActionCommand().equalsIgnoreCase("Reset") ) { mergeSorter.reset(); } if( event.getActionCommand().equalsIgnoreCase("Step") ) { doStep = true; } else if( event.getActionCommand().equalsIgnoreCase("Quit") ) { state = 1; } } } // end class Control private class SliderChangeListener implements ChangeListener { public void stateChanged(ChangeEvent e) { JSlider slider = (JSlider)e.getSource(); skipSteps = slider.getValue(); } } public static void main(String[] argv) throws InterruptedException { MergeSortViz w = new MergeSortViz( 300, 470 ); while(w.state == 0) { w.update(); Thread.sleep(33); } w.dispose(); } } // end class MergeSortViz