Bubble Sort 动画

huangapple go评论73阅读模式
英文:

Bubble sort animation

问题

最近我注意到有关于使用循环算法的代码动画的问题。例如:

  1. https://stackoverflow.com/questions/64141417/java-how-to-use-swing-timer-for-delaying-actions
  2. https://stackoverflow.com/questions/64164347/how-to-slow-down-a-for-loop

现有的代码可以工作,但只会显示最终结果。因此,人们希望能够动画显示算法的每个步骤。问题之所以被提出,是因为:

  1. 他们不知道如何做到这一点,或者
  2. 在算法中添加了Thread.sleep(...),以实现间歇性绘制,但绘制仍然只有在循环完成后才会更新。当然,我们知道问题在于不能在EDT上使用Thread.sleep(...)。

在这两种情况下,建议使用Swing Timer,并且问题被关闭为重复。但是真的那么简单吗?

下面的代码演示了一个简单的冒泡排序算法。排序逻辑简单且自包含在一个单独的方法中,指示了动画应该发生的位置。

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.Timer;

public class BubbleSort extends JPanel
{
    // ...(略去部分代码)...

    public void sort()
    {
        int n = items.length;
        int temp = 0;

        for (int i = 0; i < n; i++)
        {
            for (int j = 1; j < (n - i); j++)
            {
                if (items[j-1] > items[j])
                {
                    temp = items[j - 1];
                    items[j - 1] = items[j];
                    items[j] = temp;
                    
                    // 绘制当前状态以进行动画
                    repaint();
                    try { Thread.sleep(100); } catch (Exception e) {}
                }
            }
        }
    }

    // ...(略去部分代码)...
    
    private static void createAndShowGUI()
    {
        // ...(略去部分代码)...
    }

    public static void main(String[] args) throws Exception
    {
        EventQueue.invokeLater( () -> createAndShowGUI() );
    }
}

所以问题是:

  1. 如何修改代码以使用Swing Timer?您有什么提示/指南吗?
  2. 除了Swing Timer,还有其他可以使用的方法吗?
英文:

Lately I've noticed questions asking about animation of code that uses a looping algorithm. For example:

  1. https://stackoverflow.com/questions/64141417/java-how-to-use-swing-timer-for-delaying-actions
  2. https://stackoverflow.com/questions/64164347/how-to-slow-down-a-for-loop

The existing code works but only displays the final result. So people want to animate each step of the algorithm. A question is asked because:

  1. they don't know how to do this, or
  2. a Thread.sleep(...) has been added to the algorithm, to allow for intermittent painting, but the painting is still only updated once the looping finishes. Of course we know the problem is that you can't use Thread.sleep(...) on the EDT.

In both cases the suggestion was to use a Swing Timer and the question was closed as a duplicate. But is it that easy?

The code below demonstrates a simple Bubble Sort algorithm. The sorting logic is simple and self contained in a single method and indicates where the animation should occur.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.Timer;

public class BubbleSort extends JPanel
{
	private final static int BAR_WIDTH = 30;
	private final static int BAR_HEIGHT_MAX = 400;

	private int[]items;

	public BubbleSort(int[] items)
	{
		this.items = items;
	}

	public void setItems(int[] items)
	{
		this.items = items;
		repaint();
	}

	public void sort()
	{
		int n = items.length;
		int temp = 0;

		for (int i = 0; i &lt; n; i++)
		{
			for (int j = 1; j &lt; (n - i); j++)
			{
				if (items[j-1] &gt; items[j])
				{
					temp = items[j - 1];
					items[j - 1] = items[j];
					items[j] = temp;
					
					// paint current state for animation
					    					    					    
					repaint();
					try { Thread.sleep(100); } catch (Exception e) {}
				}
			}
		}
	}

	@Override
	protected void paintComponent(Graphics g)
	{
		super.paintComponent(g);

		for (int i = 0; i &lt; items.length; i++)
		{
			int x = i * BAR_WIDTH;
			int y = getHeight() - items[i];

			g.setColor( Color.RED );
			g.fillRect(x, y, BAR_WIDTH, items[i]);

			g.setColor( Color.BLUE );
			g.drawString(&quot;&quot; + items[i], x, y);
		}
	}

	@Override
	public Dimension getPreferredSize()
	{
		return new Dimension(items.length * BAR_WIDTH, BAR_HEIGHT_MAX + 20);
	}

	public static int[]generateRandomNumbers()
	{
		int[] items = new int[10];

		for(int i = 0; i &lt; items.length; i++)
		{
			items[i] = (int)(Math.random() * BubbleSort.BAR_HEIGHT_MAX);
		}

		return items;
	}

	private static void createAndShowGUI()
	{
		BubbleSort bubbleSort = new BubbleSort( BubbleSort.generateRandomNumbers() );

		JButton generate = new JButton(&quot;Generate Data&quot;);
		generate.addActionListener((e) -&gt; bubbleSort.setItems( BubbleSort.generateRandomNumbers() ) );

		JButton sort = new JButton(&quot;Sort Data&quot;);
		sort.addActionListener((e) -&gt; bubbleSort.sort());

		JPanel bottom = new JPanel();
		bottom.add( generate );
		bottom.add( sort );

		JFrame frame = new JFrame(&quot;SSCCE&quot;);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.add(bubbleSort, BorderLayout.CENTER);
		frame.add(bottom, BorderLayout.PAGE_END);
		frame.pack();
		frame.setLocationByPlatform( true );
		frame.setVisible( true );
	}

	public static void main(String[] args) throws Exception
	{
		EventQueue.invokeLater( () -&gt; createAndShowGUI() );
	}
}

So the questions are:

  1. How can the code be changed to use a Swing Timer? What tips/pointers do you have?
  2. Are there other approaches that can be used instead of the Swing Timer?

答案1

得分: 2

另一种方法可能是使用 SwingWorker。

SwingWorker 在其自己的线程中运行,并允许您“发布”间歇性的结果以进行绘制。

下面的方法的关键是将数据的副本传递给 SwingWorker。这允许工作线程在数据重新绘制时对数据进行排序,因此您不必担心数组中的数据处于不一致状态。

在循环代码的每次迭代之后,数组的新状态会被更新,以便可以绘制出来。

这使您可以轻松地将排序逻辑移入 SwingWorker 而无需进行重构。

import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.List;
import javax.swing.*;
import javax.swing.Timer;

public class BubbleSortWorker extends JPanel
{
    // ... 省略其他代码 ...

    class SortWorker extends SwingWorker<Void, int[]>
    {
        // ... 省略其他代码 ...

        @Override
        protected Void doInBackground()
        {
            // ... 省略其他代码 ...

            return null;
        }

        @Override
        protected void process(List<int[]> list)
        {
            int[] items = list.get(list.size() - 1);
            setItems(items);
        }

        @Override
        protected void done() {}
    }

    // ... 省略其他代码 ...

    public static void main(String[] args) throws Exception
    {
        EventQueue.invokeLater( () -> createAndShowGUI() );
    }
}

有关 SwingWorker 的更多信息,请参阅 Worker Threads and Swing Worker

英文:

Another approach might be to use a SwingWorker.

The SwingWorker creates runs in its own Thread and allows you to "publish" intermittent results to be painted.

The key to the approach below is that a copy of the data is passed to the SwingWorker. This allows the worker to sort the data while the data is being repainted so you don't have to worry about the data in the array being in an inconsistent state.

After each iteration of the looping code the new state of the array is updated so it can be painted.

This allows you to easily move the sorting logic into the SwingWorker without refactoring.

import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.List;
import javax.swing.*;
import javax.swing.Timer;

public class BubbleSortWorker extends JPanel
{
	private final static int BAR_WIDTH = 30;
	private final static int BAR_HEIGHT_MAX = 400;

	private int[]items;
	private boolean everySwap;

	public BubbleSortWorker(int[] items, boolean everySwap)
	{
		this.items = items;
		this.everySwap = everySwap;
	}

	public void setItems(int[] items)
	{
		this.items = items;
		repaint();
	}

	public void sort()
	{
		new SortWorker(items).execute();
	}

	@Override
	protected void paintComponent(Graphics g)
	{
		super.paintComponent(g);

		for (int i = 0; i &lt; items.length; i++)
		{
			int x = i * BAR_WIDTH;
			int y = getHeight() - items[i];

			g.setColor( Color.RED );
			g.fillRect(x, y, BAR_WIDTH, items[i]);

			g.setColor( Color.BLUE );
			g.drawString(&quot;&quot; + items[i], x, y);
		}
	}

	@Override
	public Dimension getPreferredSize()
	{
		return new Dimension(items.length * BAR_WIDTH, BAR_HEIGHT_MAX + 20);
	}

	class SortWorker extends SwingWorker&lt;Void, int[]&gt;
	{
		private int[] items;

	 	public SortWorker(int[] unsortedItems)
	 	{
	 		items = Arrays.copyOf(unsortedItems, unsortedItems.length);
	 	}

		@Override
		protected Void doInBackground()
		{
			int n = items.length;
			int temp = 0;

			for (int i = 0; i &lt; n; i++)
			{
				for (int j = 1; j &lt; (n - i); j++)
				{
					if (items[j-1] &gt; items[j])
					{
						temp = items[j - 1];
						items[j - 1] = items[j];
						items[j] = temp;

						//  Update after every swap is done

						if (everySwap)
						{
							publish( Arrays.copyOf(items, items.length) );
    						try { Thread.sleep(100); } catch (Exception e) {}
    					}
					}
				}

				//  Update once per iteration

				if (!everySwap)
				{
					publish( Arrays.copyOf(items, items.length) );
    				try { Thread.sleep(500); } catch (Exception e) {}
    			}
			}

			return null;
		}

        @Override
        protected void process(List&lt;int[]&gt; list)
        {
            int[] items = list.get(list.size() - 1);
            setItems( items );
        }

		@Override
		protected void done() {}
	}

	public static int[]generateRandomNumbers()
	{
		int[] items = new int[10];

		for(int i = 0; i &lt; items.length; i++)
		{
			items[i] = (int)(Math.random() * BubbleSortWorker.BAR_HEIGHT_MAX);
		}

		return items;
	}

	private static void createAndShowGUI()
	{
		BubbleSortWorker bubbleSort = new BubbleSortWorker(BubbleSortWorker.generateRandomNumbers(), true);

		JButton generate = new JButton(&quot;Generate Data&quot;);
		generate.addActionListener((e) -&gt; bubbleSort.setItems(BubbleSortWorker.generateRandomNumbers() ) );

		JButton sort = new JButton(&quot;Sort Data&quot;);
		sort.addActionListener((e) -&gt; bubbleSort.sort());

		JPanel bottom = new JPanel();
		bottom.add( generate );
		bottom.add( sort );

		JFrame frame = new JFrame(&quot;SSCCE&quot;);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.add(bubbleSort, BorderLayout.CENTER);
		frame.add(bottom, BorderLayout.PAGE_END);
		frame.pack();
		frame.setLocationByPlatform( true );
		frame.setVisible( true );
	}

	public static void main(String[] args) throws Exception
	{
		EventQueue.invokeLater( () -&gt; createAndShowGUI() );
	}
}

Check out Worker Threads and Swing Worker for more information about SwingWorker.

答案2

得分: 0

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class BubbleSortTimer extends JPanel
{
    private final static int BAR_WIDTH = 30;
    private final static int BAR_HEIGHT_MAX = 400;

    private int[] items;
    private int i, j, n;
    private Timer timer;

    public BubbleSortTimer(int[] items)
    {
        this.items = items;

        timer = new Timer(100, (e) ->
        {
            if (!nextIteration())
            {
                timer.stop();
            }

            repaint();
        });
    }

    public void setItems(int[] items)
    {
        this.items = items;
        repaint();
    }

    public void sort()
    {
        i = 0;
        j = 1;
        n = items.length;

        timer.start();
    }

    public boolean nextIteration()
    {
        if (items[j - 1] > items[j])
        {
            int temp = items[j - 1];
            items[j - 1] = items[j];
            items[j] = temp;
        }

        j++;

        if (j >= n - i)
        {
            j = 1;
            i++;
        }

        return i != n;
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        for (int i = 0; i < items.length; i++)
        {
            int x = i * BAR_WIDTH;
            int y = getHeight() - items[i];

            g.setColor( Color.RED );
            g.fillRect(x, y, BAR_WIDTH, items[i]);

            g.setColor( Color.BLUE );
            g.drawString("" + items[i], x, y);
        }
    }

    @Override
    public Dimension getPreferredSize()
    {
        return new Dimension(items.length * BAR_WIDTH, BAR_HEIGHT_MAX + 20);
    }

    public static int[] generateRandomNumbers()
    {
        int[] items = new int[10];

        for(int i = 0; i < items.length; i++)
        {
            items[i] = (int)(Math.random() * BubbleSortTimer.BAR_HEIGHT_MAX);
        }

        return items;
    }

    private static void createAndShowGUI()
    {
        BubbleSortTimer bubbleSort = new BubbleSortTimer( BubbleSortTimer.generateRandomNumbers() );

        JButton generate = new JButton("Generate Data");
        generate.addActionListener((e) -> bubbleSort.setItems( BubbleSortTimer.generateRandomNumbers() ) );

        JButton sort = new JButton("Sort Data");
        sort.addActionListener((e) -> bubbleSort.sort());

        JPanel bottom = new JPanel();
        bottom.add( generate );
        bottom.add( sort );

        JFrame frame = new JFrame("SSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(bubbleSort, BorderLayout.CENTER);
        frame.add(bottom, BorderLayout.PAGE_END);
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static void main(String[] args) throws Exception
    {
        EventQueue.invokeLater( () -> createAndShowGUI() );
    }
}
英文:

A Swing Timer works on event driven code. Therefore the iterative code must be refactored to event driven code.

Some things to consider during this process:

  1. state must be added to the algorithm. This means that all local variables used to control the looping algorithm must be converted to instance variables of the class.

  2. the looping algorithm must be split into two methods, the first to set the initial state of the algorithm and start the timer, and the second method to update the state and stop the Timer.

Using the above suggestion the refactoring of your code might be something like:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class BubbleSortTimer extends JPanel
{
private final static int BAR_WIDTH = 30;
private final static int BAR_HEIGHT_MAX = 400;
private int[]items;
private int i, j, n;
private Timer timer;
public BubbleSortTimer(int[] items)
{
this.items = items;
timer = new Timer(100, (e) -&gt;
{
if (!nextIteration())
{
timer.stop();
}
repaint();
});
}
public void setItems(int[] items)
{
this.items = items;
repaint();
}
public void sort()
{
i = 0;
j = 1;
n = items.length;
timer.start();
}
public boolean nextIteration()
{
if (items[j - 1] &gt; items[j])
{
int temp = items[j - 1];
items[j - 1] = items[j];
items[j] = temp;
}
j++;
if (j &gt;= n - i)
{
j = 1;
i++;
}
return i != n;
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
for (int i = 0; i &lt; items.length; i++)
{
int x = i * BAR_WIDTH;
int y = getHeight() - items[i];
g.setColor( Color.RED );
g.fillRect(x, y, BAR_WIDTH, items[i]);
g.setColor( Color.BLUE );
g.drawString(&quot;&quot; + items[i], x, y);
}
}
@Override
public Dimension getPreferredSize()
{
return new Dimension(items.length * BAR_WIDTH, BAR_HEIGHT_MAX + 20);
}
public static int[]generateRandomNumbers()
{
int[] items = new int[10];
for(int i = 0; i &lt; items.length; i++)
{
items[i] = (int)(Math.random() * BubbleSortTimer.BAR_HEIGHT_MAX);
}
return items;
}
private static void createAndShowGUI()
{
BubbleSortTimer bubbleSort = new BubbleSortTimer( BubbleSortTimer.generateRandomNumbers() );
JButton generate = new JButton(&quot;Generate Data&quot;);
generate.addActionListener((e) -&gt; bubbleSort.setItems( BubbleSortTimer.generateRandomNumbers() ) );
JButton sort = new JButton(&quot;Sort Data&quot;);
sort.addActionListener((e) -&gt; bubbleSort.sort());
JPanel bottom = new JPanel();
bottom.add( generate );
bottom.add( sort );
JFrame frame = new JFrame(&quot;SSCCE&quot;);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(bubbleSort, BorderLayout.CENTER);
frame.add(bottom, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args) throws Exception
{
EventQueue.invokeLater( () -&gt; createAndShowGUI() );
}
}

huangapple
  • 本文由 发表于 2020年10月4日 23:01:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/64196198.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定