83. Hailstone sequence

The following method returns the values in a number's hailstone sequence:

// Find this number's hailstone sequence.
private List<int> FindHailstoneSequence(int number)
{
List<int> results = new List<int>();
while (number != 1)
{
results.Add(number);
if (number % 2 == 0)
number = number / 2;
else
number = 3 * number + 1;
}
results.Add(1);
return results;
}

This method performs the actual hailstone simulation. It creates a list to hold the sequence's values and then enters a loop that continues as long as the number is not 1. Within the loop, the method adds the current number to the sequence and then uses the current value to calculate the next one.

When the loop ends, the method adds 1 to the list and returns the list.

The rest of the program displays the sequence. When you click the Go button, the following code draws the graph and shows the sequence's values:

private void goButton_Click(object sender, EventArgs e)
{
resultTextBox.Clear();
lengthLabel.Text = "";
Refresh();

// Find the hailstone sequence lengths.
int number = int.Parse(numberTextBox.Text);
List<int> sequence = FindHailstoneSequence(number);

// Display the results.
resultTextBox.Text = string.Join(" ", sequence.ToArray());
lengthLabel.Text = sequence.Count().ToString();

// Graph the results.
int wid = sequencePictureBox.ClientSize.Width;
int hgt = sequencePictureBox.ClientSize.Height;
Bitmap bm = new Bitmap(wid, hgt);
using (Graphics gr = Graphics.FromImage(bm))
{
gr.SmoothingMode = SmoothingMode.AntiAlias;
gr.Clear(Color.White);

// We cannot graph it if there's only one point.
if (number < 2) return;

// Set up a transformation.
RectangleF graphRect = new RectangleF(
0, 0, sequence.Count(), sequence.Max());
const int margin = 5;
RectangleF bitmapRect = new RectangleF(
margin, margin, wid - 1 - 2 * margin, hgt - 1 - 2 *
margin);
SetTransformation(gr, graphRect, bitmapRect, false, true);

using (Pen pen = new Pen(Color.Black, 0))
{
// Draw axes.
gr.DrawLine(pen, 0, -10000, 0, 10000);
gr.DrawLine(pen, -10000, 0, 10000, 0);

// Draw the sequence.
pen.Color = Color.Red;
List<PointF> points = new List<PointF>();
for (int x = 0; x < sequence.Count; x++)
points.Add(new PointF(x, sequence[x]));
gr.DrawLines(pen, points.ToArray());
}
}
sequencePictureBox.Image = bm;
}

This method clears the result text box and length label, and then parses the starting number that you entered. It calls the FindHailstoneSequence method to get the sequence, joins the values together in a string, and displays them. It also displays the sequence's length.

Next, the code builds the graph. It gets the size of the form's PictureBox control, makes a Bitmap object to fit, and creates an associated Graphics object. The code then sets the Graphics object's SmoothingMode property to draw a smooth curve and clears the image.

Now the method sets up a transformation to map the sequence's values onto the Bitmap object's surface. It creates a rectangle that holds the data's bounds. It then creates a second rectangle that gives the Bitmap object's bounds, minus a margin around the edges. The code then passes those rectangles to the SetTransformation method described shortly to make the Graphics object map the data area onto the Bitmap.

At this point, the method is ready to draw. It first creates a pen with a thickness of 0. It then uses the pen to draw the axes, x = 0 and y = 0.

A pen with a thickness of 0 always draw the thinnest possible line even if the Graphics object includes a transformation that would otherwise scale the pen.

Next, the code loops through the hailstone sequence and creates points to represent the sequence's values. It then calls the Graphics object's DrawLines method to draw lines connecting the points. The Graphics object's transformation automatically maps the graph onto the Bitmap.

The final interesting piece of the application is the following SetTransformation method:

// Map from world coordinates to screen coordinates.
private void SetTransformation(Graphics gr,
RectangleF graphRect, RectangleF bitmapRect,
bool invertX, bool invertY)
{
PointF[] bitmapPoints =
{
new PointF(bitmapRect.Left, bitmapRect.Top), // Upper left.
new PointF(bitmapRect.Right, bitmapRect.Top), // Upper right.
new PointF(bitmapRect.Left, bitmapRect.Bottom), // Lower left.
};

if (invertX)
{
bitmapPoints[0].X = bitmapRect.Right;
bitmapPoints[1].X = bitmapRect.Left;
bitmapPoints[2].X = bitmapRect.Right;
}
if (invertY)
{
bitmapPoints[0].Y = bitmapRect.Bottom;
bitmapPoints[1].Y = bitmapRect.Bottom;
bitmapPoints[2].Y = bitmapRect.Top;
}

gr.Transform = new Matrix(graphRect, bitmapPoints);
}

A Graphics object's Transform property is a Matrix object that describes the desired transformation. One of the Matrix class's constructors takes two parameters. The first is a rectangle giving an area in drawing coordinates where you want to draw. The second is an array containing three points that define the coordinates on the Bitmap where the rectangle's upper left, upper right, and lower left corners should be mapped.

The method initializes the bitmapPoints array to define those corners. If the method's invertX parameter is true, the method switches the roles of the rectangle's left and right sides. Then, if the invertY parameter is true, it performs a similar operation for the rectangle's top and bottom sides. The example program uses this feature because Y coordinates increase upward in mathematical space but downward on a Bitmap.

Having defined the Bitmap area points, the method creates a Matrix object and assigns it to the Graphics object's Transform property. Now the Graphics object is ready to draw and automatically map points in the drawing rectangle onto the correct part of the Bitmap.

Download the HailstoneSequence example solution to see the results and to see additional details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset