Sort Assignment 11 is due next Monday, February 22.
Lab Assignment 5 is due next Wednesday, February 24.
SubsetSum
Cube
CubeTower
Insane1
Insane2
Insane3
BuriedCount
Sometimes instead of finding a single path or a single solution we want to find all solutions. A systematic way of doing this is called backtracking. It is a variant of depth-first search on an implicit graph. The typical approach uses recursion. At every choice point, loop through all the choices. For each choice, solve the problem recursively. Succeed if you find a solution; otherwise fail and backtrack to try an alternative.
The basic approach is:
procedure backtrack(partialSolution) if partialSolution is a complete solution print partialSolution or add it to a list of solutions else For all ways to extend partialSolution by one "step" expand partialSolution by adding the next "step" to get partialSolution' if partialSolution' is valid backtrack(partialSolution') if necessary restore partialSolution to the state it was in before you updated it
Let's consider the N-queens problem. The goal is to place N queens on an N × N chessboard such that no two queens can capture one another. (Two queens can capture one another if they are in the same row, column, or diagonal.)
A truly brutish brute-force solution technique would be to consider all possible ways of placing N queens on the board, and then checking each configuration. This technique is truly awful, because for an N × N board, there are N2 squares and hence
Another, less brutish, brute-force solution takes advantage of the property that we must have exactly one queen per column. So we can just try all combinations of queens, as long as there's one per column. There are N ways to put a queen in column 1, N ways to put a queen in column 2, and so on, for a total of NN configurations to check. When N = 8, we have reduced the number of configurations to 16,777,216.
We can make our brute-force solution a little better yet. Since no two queens can be in the same row, we can just try all permutations of 1 through N, saying that the first number is the row number for column 1, the second number is the row number for column 2, and so on. Now there are "only" N! configurations, which is 40,320 for N = 8. Of course, for a larger board, N! can be mighty large. In fact, once we get to N = 13, we get that N! equals 6,227,020,800, and so things are worse than the most brutish brute-force solution for N = 8.
So let's be a little smarter. Rather than just blasting out configurations, let's pay attention to what we've done. Start by placing a queen in column 1, row 1. Now we know that we cannot put a queen in column 2, row 1, because the two queens would be in the same row. We also cannot put a queen in column 2, row 2, because the queens would be on the same diagonal. So we place a queen in column 2, row 3. Now we move onto column 3. We cannot put the queen in any of rows 1–4 (think about why not), and so we put the queen in row 5. And so on.
This approach is called pruning. At the kth step, we try to extend the partial solution with k − 1 queens by adding a queen in the kth column in all possible positions. But "possible" now means positions that don't conflict with earlier queen placements. It may be that no choices are possible. In that case, the partial solution cannot be extended. In other words, it's infeasible. And so we backtrack, undoing some choices and trying others.
Why do we call this approach pruning? Because you can view the search as a tree in which the path from the root to any node in the tree represents a partial solution, making a particular choice at each step. When we reject an invalid partial solution, we are lopping off its node and the entire subtree that would go below it, so that we have "pruned" the search tree. Early pruning lops off large subtrees. Effort spent pruning usually (but not always) pays off in the end, because of the exponential growth in number of solutions considered each time a choice is made.
In terms of the pseudocode above, the "all ways to extend a partial solution" consist of all ways to put the queen in the next available column. The "partial solution' is valid" consists of verifying that the queen just placed does not conflict with a queen in an earlier column.
Go through the backtracking for N = 4:
Here is one solution for N = 8, taken from Wikipedia:
We will now look at a program to solve subset sum. We are given a list of positive integers (with no repetitions) and a target number. The goal is to find all subsets of the integers that sum to the target.
So what is a "step"? It is to decide whether to add the next number in the list to the subset or not. So we will make two recursive calls, one where add the next number to the subset and one where we do not. A complete solution is one that sums to the target. A solution is still valid if the sum is at most the target. (Because the integers are all positive there is no way to extend a subset to get a solution if the sum is already too large.) The code for SubsetSum is:
public class SubsetSum {
/**
* Finds all subsets of the numbers in List that add up to target.
*
* @param numbers The list of positive integers (cannot have duplicates!)
* @param pos The current position in numbers to be considered
* @param subset The current subset
* @param sum The sum of the integers in subset
* @param target The goal number that the sum should equal
*/
public static void findSubsets(List numbers, int pos,
Set subset, int sum, int target) {
if(sum == target)
System.out.println("Subset: " + subset);
else if(sum < target && pos < numbers.size()) { // Do nothing if sum too large
Integer m = numbers.get(pos);
subset.add(m);
findSubsets(numbers, pos+1, subset, sum+m, target); // Include pos
subset.remove(m);
findSubsets(numbers, pos+1, subset, sum, target); // Don't include pos
}
}
We consider the puzzle called Instant Insanity. It came out well before Rubic's Cube and other really difficult puzzles. The puzzle has four cubes. For each cube each of its six sides is colored red, blue, green, or white. (Show the puzzle.) The puzzle is to make a tower of the cubes by stacking them one on top of another in a way that each side of the tower has each color appearing once.
We use an object-oriented approach to the puzzle. We want an
object type Cube
to hold an individual cube. It holds the colors of the six faces
in instance variables, and allows us to rotate the cube, tip it
90 degrees around the front-back axis so that the top becomes the left
face, and to flip it around the same axis so that the top becomes the
bottm.
Much of the work is done in the class
CubeTower
. This object
contains all four cubes. It supplies methods to rotate, flip, and
tip particular cubes and a validity test to see if there are any
conflicts.
The program Insane1
is a really stupid, brute-force way to solve the puzzle. It is clever
enough to note that flipping each piece simultaneously will give another
solution and that rotating the tower, starting with either the original solution
or the flipped solution, will give four solutions each. Therefore it only
places the bottom cube in three positions, one for each top-bottom pair.
The other cubes are each placed in 24 positions. (4 rotations,
flip, 4 rotations, flip back, rotate and tip. Repeat three times. This actually
gets you back to where you started.) There is no pruning.
Only when all 4 cubes are placed do we check for validity. This results
in 43,276 calls to placeCube
. Not bad for a computer, but slow by hand.
(Thirty years ago it took a while even on a computer).
Insane2
has the sense to
check for validity of the first k placements before proceeding to placement
k+1. This improves things a whole lot. It results in 1347 validity tests and
58 calls to placeCube
. This is not unreasonable to do by hand,
and takes virtually no time on a computer.
Insane3
adds additional pruning.
The idea is that there are partial solutions that have no conflicts on their sides that still
cannot be extended to a complete solution. We would like to prune these, also.
Whenever we place a cube we "bury" its top color and its bottom color, in the sense
that they are no longer available to appear on the sides. If we bury enough occurrances
of a color there will not be 4 copies of that color to appear on all four sides of the
tower, so the partial solution cannot be extended to a complete solution.
Insane3
uses the object
BuriedCount
to keep track of
how many of each color have been buried and whether a solution might still be possible.
It allows us to bury and unbury particular colors.
CubeTower
has methods to bury and
unbury the top and bottom faces of a cube and to ask BuriedCount
whether a solution is still possible. This reduces the number
of validity tests to 379 and the number of calls to placeCube
to 37. For
36 of these calls we bury, unbury, and test whether we have buried too much three times.
This is practical to do by hand.
Note - The idea of this backtracking is simple, but the details of how the cubes are placed and rotated gets complicated. The N-queens backtracking is much simpler. You should not be creating new classes or using data structures beyond an ArrayList
and some arrays.
The Maze program is an application, but it has a GUI. So far only applets have used GUIs. It also uses radio buttons, a concept that we have not seen yet. We will now see how to create applications with GUIs as well, and will look at a few more of the dozens of specialized components that are in Swing. Fortunately they all use the same basic way of communicating with your program (listeners), so once you have seen a few it is not hard to pick up new ones as you need them.
In a GUI for an application instead of an Applet window we use a frame. A frame has title bar (including a close box, which we will deal with, and the usual collection of other control boxes, which we will let Java deal with), and a content pane. (It also has other things that we don't need to worry about.)
The first task is to get the frame displayed, and to get it to go away
when we click the "close" box. The main program creates the frame,
but even after the main program is done the frame continues to
run. That's because when we open a frame window, the program
starts a new thread of execution that displays the
GUI. When the main
method completes, its main
thread is done, but the thread for the GUI is still running.
Therefore, we need a way to stop the program when the close box is
clicked.
We stop the program as in Fahrenheit.java and FahrenheitGUI.java. We have seen most
of the GUI components—JPanel
, JLabel
,
and JTextField
—before. What's new is the
JFrame
. In this example, the FahrenheitGUI
object creates a new JFrame
and sets the instance
variable frame
to reference it. The constructor includes
the string Temperature Conversion
that is displayed in
the frame's title bar.
The way that we indicate that the program gets stopped is with the line
setDefaultCloseOperation
says what should happen when
the frame's close button is clicked. Here, we specify that the
program should exit, or terminate.
In this example, the display
method of
FahrenheitGUI
calls frame.pack
and
frame.setVisible(true)
. The pack
method
sizes the frame to fit the components that have been added into it,
and the setVisible
method with a parameter of
true
tells the frame to actually display itself on the
screen. You should call pack
on a JFrame
if the setSize
method has not been called on the
JFrame
to set its size. In this example, we have called
setPreferredSize
on a JPanel
that is added
to the JFrame
, but we have not called
setSize
on the JFrame
itself. The
pack
method of JFrame
has the
JFrame
set its size to fit around the GUI components
within the frame.
JPanel
both for drawing and as a container
for grouping buttons, check boxes, or other components together. We
will add JPanel
objects to a frame or to another
container. You can build a whole hierarchy of components by
assembling JPanel
s, getting a lot of control over the
layout. When components are added to a JPanel
they use a
Flow Layout by default. We'll see later how to use a different layout
manager for a JPanel
.
As with the JApplet
objects we saw earlier, we do not add
components directly to a JFrame
. A frame has a number of
panes, one of which is the content pane. Therefore when adding
components to a frame, we write
contentPane
. Or, as in
the constructor for FahrenheitGUI
, we don't bother
storing the result of getContentPane
in a variable.
The content pane of an application uses a different layout scheme called Border Layout. We saw this before in PS-3, although we did not require you to program with it. The pane is partitioned into five regions: North, South, East, West, and Center. They appear in the pane as follows:
You can add at most one component to any one of these regions. Regions with no component are taken over by the Center region, which expands to fill the leftover area. We'll see that the North and South regions expand horizontally in ways that are sometimes a bit strange.
We can arrange the layout of a GUI using the content pane and as many
JPanel
s as we like.
ButtonTest.java
JPanel
objects: the program ButtonTest.java. When running it, we can
see that it has four buttons across the bottom and a large content
pane where a rectangle can be moved via button clicks. Try it out.
We will see how to set up this GUI.
First, consider the main
program. It is similar to the
frame example we looked at earlier. The only differences are that it
uses frame.setTitle
to set the title here rather than
doing it in the frame's constructor, and we call setSize
on the frame, thus obviating the need to call pack
.
The real work of setting up the GUI is in ButtonFrame.java. The
ButtonFrame
class has five private instance variables:
JButton
s to hold the buttons, and
RectanglePanel
, which inherits from
JPanel
. This is a panel on which we draw the
moving rectangle.
In the constructor for ButtonFrame
, we make sure that the
program will stop when the window is closed by calling
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
.
The ButtonFrame
constructor then assigns the instance
variable rectPanel
to a new RectanglePanel
.
It also creates the JPanel buttonPanel
to group the
buttons together. This variable is local, because we do not need it
after the constructor is done. The buttons themselves are needed
after the constructor finishes, but the panel that groups them is not.
All four buttons are created, with labels Up
,
Down
, Left
, and Right
. Unlike
the GUI in Lab Assignment 3, all four buttons are assigned the same
listener object. We will see later how we can do different things in
the method, depending on which button was pushed.
All we have done so far is create some objects. We have yet to place
them into the frame. That's what happens next. The program gets the
contentPane
from the frame and adds
rectPanel
, the drawing area, to the center region of the
contentPane
using the builtin constant
BorderLayout.CENTER
. (Alternatively, you can instead use
the string "Center"
as the second parameter to
add
.)
We want all four buttons in the South area of the frame. With the
variable layoutStyle
set to the value
flowLayout
(from the enumerated type
LayoutType
), we first add all four to the
JFrame buttonPanel
(using Flow Layout) and then add
buttonPanel
to the south part of the
contentPane
.
The method actionPerformed
in
DirectionListener
responds to the button clicks. The
ActionEvent event
has within it a reference to the object
that is the source of the event. A call to
event.getSource
returns this reference. By comparing
references we can decide which button was clicked in a series
of if-else statements. Then each of them moves the rectangle in the
rectPanel
in a different direction by calling the
moveRectangle
method of RectanglePanel
with
the appropriate parameter values. After moving the rectangle,
actionPerformed
has the RectanglePanel
draw
itself.
What remains is the RectanglePanel
class in RectanglePanel.java. This is the panel
for the drawing area. Therefore it has the methods to deal with
drawing on the panel (paintComponent
) and the information
about what is to be drawn there.
It has a private instance variable theRect
, which
references the Rect
object to be drawn. We've seen the
Rect
class, in Rect.java, before.
The rectangle is initialized in the constructor to be at the upper
left-hand corner of the panel. Each panel has its own coordinate
system.
To draw we call paintComponent
.
Important note: the first line of paintComponent
should always be a call to super.paintComponent
.
Doing so gives the superclass method a chance to do what it needs to
do with the panel.
Then it can do what we normally want. In this case, it draws
theRect
and then draws a grid of light grey lines in
multiples of the rectangle size. The calls to getWidth
and getHeight
are actually calling methods inherited from
JPanel
; these calls return the width and height of the
panel, so that we draw the grey lines at the correct lengths.
Another method is moveRectangle
. It simply shifts the
rectangle by dx
rectangle widths in the x
direction and dy
rectangle heights in the y
direction. Remember that the actionPerformed
method
called it to move the rectangle in response to button pushes.
The last method in RectanglePanel
is draw
,
which just calls repaint
. Strictly speaking, we didn't
need to define draw
in RectanglePanel
.
Instead, we could have replaced the call rectPanel.draw()
in the actionPerformed
method of ButtonFrame
by the call rectPanel.repaint()
. I chose to define and
call the draw
method because it is cleaner to hide that
rectPanel
has a repaint
method from the
classes that use it. In fact, just calling repaint
here
would also work.
Up
button
on the top, the Left
button on the left, the
Right
button on the right, and the Down
button on the bottom. Setting the variable layoutStyle
to wideBorderLayout
gets us closer.
Now our button layout is accomplished by the lines
Up
button on the North side, the Down
button on the South
side, the Left
button on the West side, and the
Right
button on the East side. The only aesthetic
problem is that with Border Layout, the North and South borders
stretch the entire horizontal span of the panel, so that the
Up
and Down
buttons appear much, much wider
than the Left
and Right
buttons.
We can solve that problem, too. If we set variable
layoutStyle
to niceBorderLayout
, we run the
lines
JPanel
objects, referenced by
northPanel
and southPanel
, each holding just
one JButton
. By putting upButton
and
downButton
each in its own little JPanel
, we
prevent these buttons from being stretched horizontally in a Border
Layout.
JCheckBox
class.
JRadioButton
class.
JComboBox
class.
At this point, many of the features of this program are probably self-explanatory. Some are not. We'll go through a few of them here.
The heavy lifting is done in ChoiceFrame.java. The
ChoiceFrame
constructor builds the GUI. As usual, we
attach a listener to each button or combo box. As in the
ButtonFrame
class in ButtonFrame.java, we use one listener for
all choice components. This listener's actionPerformed
method calls the method setSampleFont
, which gets the
value of each choice component and then displays the sample text
according to these values.
When constructing the JTextField
referenced by
sampleField
, we specify a text string to display. We
also set the editability of this text field to false
, so
that the user cannot edit it. The editability can be changed,
however, as we shall see.
All check boxes, from the Swing class JCheckBox
, may be
turned on and off individually. We use them for independent choices.
Here, we have three check boxes:
editableCheckBox
, which the user can click to make
the text field editable.
italicCheckBox
, which specifies that the text is
to be displayed in italics.
boldCheckBox
, which specifies that the text is to
be displayed in boldface.
isSelected
method. The setSampleFont
method
makes calls such as editableCheckBox.isSelected()
and
italicCheckBox.isSelected()
.
A combo box lets the user pick one of a list of items. Here, the items are font names. When one is chosen, an action event occurs, which is handled by the listener we attach to the combo box. Because we set the combo box to be editable, the user can type in other font names, too.
We use radio buttons to select the font size: small, medium, or large.
Radio buttons allow the user to make exactly one choice out of a set
of choices. The way we indicate each set of choices is with
ButtonGroup
objects. We create a new
ButtonGroup
for each set of choices, and we add the
buttons to the ButtonGroup
. A ButtonGroup
has nothing to do with the layout of the buttons on the screen; it
makes it so that when the user clicks one button of a set, Swing knows
which other buttons have to become unselected. (It takes care of
unselecting them.) We choose to start out with one radio button
being selected - if we did not none would be selected.
Here, we choose the one for the large text size.
We can also add tool tips to our check boxes and radio buttons. By
calling the setToolTipText
method on a GUI component, we
give text that will appear when the cursor is held over the component.
This method applies to pretty much any GUI component, including combo
boxes.
That takes care of setting up the objects. Next is the physical
layout. We make a JPanel
named sampleFrame
,
with Border Layout, to hold editableCheckBox
and
sampleField
, with the check box above the text field. By
adding sampleField
to the center region of the Border
Layout, we make it so that when the window is resized, the sample
text's panel grows and shrinks to fit the window. We then make a
panel for the font name combo box. Next, a panel for the size radio
buttons. We put the title "Size" and an etched border around this
panel. Similarly, we make a panel for the style check boxes, with the
title "Style" and an etched border.
The latter three panels—for the font name combo box, size radio
buttons, and style radio buttons—are aligned vertically using a
new layout manager: the Box Layout manager. A Box Layout allows us to
align components either vertically or horizontally. Unlike the other
layout managers, a BoxLayout
constructor takes as a
parameter a reference to the component that it is laying out. I don't
know why. But it means that if you were using a Box Layout to lay out
more than one component, you would have to create a new
BoxLayout
object for each one. Here, the component that
the Box Layout manager is working on is southPanel
.
The two panels that we have yet to place into a
container—sampleFrame
and
southPanel
—are put into the content pane. Recall
that in an application, the content panel by default uses Border
Layout. Again, by putting the panel containing the sample text in the
center region, the sample text's panel will grow and shrink as the
window is resized.
Finally, the ChoiceFrame
constructor calls
setSampleFont
to get the values of all the GUI components
and redisplay the sample text. The setSampleFont
method
asks whether each check box and radio button is selected, doing the
appropriate thing for each component that is selected. The call
fontnameCombo.getSelectedItem()
returns what appears in
the combo box. Here, we cast it to a String
, which is
what we'll be using.
The style is an integer that we set according to the check boxes. It
starts as a 0, indicating "plain," and we add into it according to
other styles that we want. Font.ITALIC
and
Font.BOLD
are constants that indicate the styles used
here. We set the font size according to which of the radio buttons is
selected.
Once we have the font name, style, and size, we call the
setFont
method on sampleField
with the font
name, style, and size. This call changes some of the text field's
display characteristics. A call to sampleField.repaint
causes the text field to actually redisplay.
Given this we can see how the maze program is laid out. The top level MazeSolver.java simply creates the maze frame, sets its size and title, and makes it visible.
The work is done in MazeFrame.java.
This sets up the frame and the buttons. Note that the stepButton
,
clearButton
,
and readButton
are local variables. That is because once they are
registered with a listener nothing more needs to be done with them. The
solveButton
has to change its text (alternates between "Solve" and
"Pause") and the radio buttons need to be tested to see which is selected.
Therefore they are instance variables. The button layouts are very similar
to ones we have seen before, so I will let you read them on your own.
The listeners are worth looking at. The StepListener
does
the work of actually performing a step in the maze. It is used by both the
Timer
and the stepButton
. If the maze is solved
it turns off the timer and sets the label on the solveButton
to
Solve. If not, it calls maze.stepMaze()
to do one more step
in solving the maze.
SolveListener
is
used to handle the solveButton
. It toggles the label on the
button and the state of the timer (if on, then turns it off, and vise versa).
The ReadListener
reads a new maze and creates it. The
RadioListener
creates a new maze, with createMaze
looking at the radio buttons. Creating a new maze is what we want to do
when we clear the maze, so this works for the clearButton
also.
The createMaze
method sets up a new maze. It creates the
correct type of sequencer. (Remember a sequencer is basically an abstract queue -
it can add items and get the next item.) It makes a maze from the text representation
that has been read in (or does nothing if no text has been read or it has an error).
It passes this maze on to the mazeCanvas
and repaints.
The mazeCanvas
holds a MazePanel.java,
which basically draws the current maze. The code for display
figures out
the square size and the position of the upper left hand corner, and it draws each square
in the appropriate color.
Maze.java both initializes the maze in its
constructor and performs a step of solving the maze in stepMaze
.
Method stepMaze
is where the virutal graph is explored. It calls the
current sequencer to get the next
maze square to be processed. If the square to be processed is the target it traces back the path,
marking each square as on the final path. If not, and the square was not explored,
it tries to add each of its four potential
neighbors to the sequencer (if they exist and are either empty or the target) and marks
the current square as explored. This will prevent it from being explored again later.
The actual code for solving the maze would have been simpler if we were not creating an interactive GUI. The GUI requires spreading the solution out, separating out the code for taking a step and making it act on the current "state" of the maze. This step code would normally be part of a loop. But here the loop is provided by the GUI, either via the Step button or repeated calls from the Timer after clicking Solve.