charlesreid1.com blog

CSE 143 Final Project: Hilbert Sort: 3. The Code

Posted in Computer Science

permalink

Table of Contents

This is the third in a series of three posts detailing the Hilbert Sort problem, its solution, and its implementation. This post deals with the code to solve the Hilbert Sort problem.

Hilbert Sort: Pseudocode

From our prior post, here is the psudocode for our Hilbert Sort function:

define hilbert_sort( unsorted queue, square dimension ):
    create southwest queue
    create northwest queue
    create northeast queue
    create southeast queue
    for each point:
        if in southwest:
            create new point using X -> Y, Y -> X
            add to southwest queue
        if in northwest:
            create new point using X -> 2X, Y -> 2Y - S
            add to northwest queue
        if in northeast:
            create new point using X -> 2X - S, Y -> 2Y - S
            add to northeast queue
        if in southeast:
            create new point using X -> S - 2Y, Y -> 2S - 2X
            add to southeast queue

        hilbertsort(southwest queue, square dimension)
        hilbertsort(northwest queue, square dimension)
        hilbertsort(northeast queue, square dimension)
        hilbertsort(southeast queue, square dimension)

        create new results queue
        add points from southwest into results queue
        add points from northwest into results queue
        add points from northeast into results queue
        add points from southeast into results queue
        return results queue

Because we are manually sorting, and we want order to be preserved, we should be using a queue to organize points as we sort them. That way, we add them in sorted order, and we are then able to remove them in sorted order.

Hilbert Sort: Code

We begin by covering a utility class used by the Hilbert Sort method to store \((X,Y)\) points. This is a simple example of a composition design pattern. Next, we cover the bulk of the problem solution: the recursive sort method that partiions points into quadrants. Finally, we cover the main method, which demonstrates reading data from an input file and passing it to the sort method.

Hilbert Sort: Utility Classes

To organize \((X,Y)\) point data, we use a simple class using composition. This is defined next to the HilbertSort class.

/**
 * An (x,y) Point class. 
 */
class Point {
    int x, y; // (x,y) point.
    String name; // Each (x,y) point has a name in the file. Used for output.
    /** Constructor. */
    public Point(int x, int y, String name) { 
        this.x = x; this.y = y; this.name = name;
    }
    /** String representation (x,y). */
    public String toString() { 
        return "("+this.x+","+this.y+","+this.name+")";
    }
}

Recursive Sort Function

Following is the recursive sort method, which (like merge sort) consists of a split step, which partitions an \(S \times S\) square into quadrants and distributes points in the square into their corresponding quadrants, and a merge step, which stitches together each quadrant in the correct order.

    /** Recursive implementation of a Hilbert sort. */
    public static Queue<Point> hilbertSort(Queue<Point> inputP, int S) {
        // Recursive method:
        // Apply the Hilbert geometrical quadrant division 
        // to sort points by when they are visited by a Hilbert curve.
        //
        // Base case: 
        // There are 1 or fewer points in each quadrant.
        // Keep splitting into quadrants until we reach the base case. 
        if(inputP.size()<1) {
            return new LinkedList<Point>();
        } else if(inputP.size()==1) {
            return inputP;
        }

        // split by quadrant
        Queue<Point> qSW = new LinkedList<Point>();
        Queue<Point> qNW = new LinkedList<Point>();
        Queue<Point> qNE = new LinkedList<Point>();
        Queue<Point> qSE = new LinkedList<Point>();

        // Sort points by dividing into quadrants
        for(Point p : inputP) { 

            // Prepare for the tricky part.
            //
            // Rotate the quadrant, and points in it,
            // so that everything is now translated to fit
            // how the template of the Hilbert curve is being drawn.
            // (SW->NW->NE-SE)

            boolean inSWquadrant = (2*p.x <= S) && (2*p.y <= S);
            boolean inNWquadrant = (2*p.x <= S) && (2*p.y >= S);

            boolean inNEquadrant = (2*p.x >= S) && (2*p.y >= S);
            boolean inSEquadrant = (2*p.x >= S) && (2*p.y <= S);

            // Each time we sort (x,y) points into quadrants,
            // we also transform each coordinate point 
            // in such a way that it rescales to an S x S square,
            // but does not modify the order of the points. 
            //
            // Note that we can keep everything as integers by
            // continuing to look at an S x S square,
            // and double the x and y values to shift them over/up.
            //
            // Two easy cases:
            if(inNWquadrant) {
                // Northwest quadrant: 
                // - shift y down by S/2
                // - keep x and y in same order
                qNW.add( new Point(2*p.x, 2*p.y-S, p.name) );

            } else if(inNEquadrant) {
                // Northeast quadrant:
                // - shift x and y down by S/2
                // - keep x and y in same order
                qNE.add( new Point(2*p.x - S, 2*p.y - S, p.name) );

            } else if(inSWquadrant) { 
                // Southwest quadrant:
                // - x and y need to swap places 
                // - that's it.
                qSW.add( new Point(2*p.y, 2*p.x, p.name) );

            } else if(inSEquadrant) { 
                // Southeast quadrant:
                // - trickiest quadrant.
                // - We want to preserve S - x, distance from right side
                // - we want to use it as the new y coordinate
                qSE.add( new Point(S - 2*p.y, 2*(S - p.x), p.name) );

            }

        }
        // Sort til you reach the base case.
        qSW = hilbertSort(qSW, S); 
        qNW = hilbertSort(qNW, S); 
        qNE = hilbertSort(qNE, S); 
        qSE = hilbertSort(qSE, S);

        Queue<Point> result = new LinkedList<Point>();
        for(Point q : qSW) result.add(q);
        for(Point q : qNW) result.add(q);
        for(Point q : qNE) result.add(q); 
        for(Point q : qSE) result.add(q);

        return result;
    }

Main Method

The last part of the code is the portion that loads the points and their labels from a file, and populates a Queue of Point objects from it. This queue of points is then sorted and returned in order.

    /** Main driver. */
    public static void main(String[] args) { 

        Scanner stdin = new Scanner(new BufferedReader(new InputStreamReader(System.in)));

        int n = stdin.nextInt();
        int S = stdin.nextInt();

        // n lines of 3 tokens each
        Queue<Point> inputPoints = new LinkedList<Point>();
        for(int i=0; i<n; i++) { 
            int x0 = stdin.nextInt();
            int y0 = stdin.nextInt();
            String label = stdin.next();
            inputPoints.add(new Point(x0,y0,label));
        }
        Queue<Point> sortedPoints = hilbertSort(inputPoints, S);
        for(Point p : sortedPoints) { 
            System.out.println(p.name);
        }
    }

References

  1. "ACM Pacific Region Programming Competition." Association of Computing Machinery. Accessed 19 June 2017. <http://acmicpc-pacnw.org/>

  2. "Hilbert Sort." Git repository, git.charlesreid1.com. Charles Reid. Updated 16 June 2017. <https://git.charlesreid1.com/cs/finalproject-143/src/master/hilbert/HilbertSort.java>

CSE 143 Final Project: Hilbert Sort: 2. The Solution Algorithm

Posted in Computer Science

permalink

Table of Contents

This is the second in a series of three posts detailing the Hilbert Sort problem, its solution, and its implementation. This post solves the problem.

Hilbert Sort Problem

In the prior post, we covered the Hilbert Sort problem, but we state it once more succinctly here before detailing a solution to the problem.

The Hilbert Sort problem asks the following: given a set of labeled \((x,y)\) points, how can we sort the points according to the order in which they are visited by a space-filling Hilbert curve?

Revisiting the example input and output provided, the input provides the number of points and size of the grid on the first line, followed by each point's coordinates and label.

Input:
    14 25
    5 5 Honolulu
    5 10 PugetSound
    5 20 Victoria
    10 5 Berkeley
    10 10 Portland
    10 15 Seattle
    10 20 Vancouver
    15 5 LasVegas
    15 10 Sacramento
    15 15 Kelowna
    15 20 PrinceGeorge
    20 5 Phoenix
    20 10 SaltLakeCity
    20 20 Calgary

Output:
    Honolulu
    Berkeley
    Portland
    PugetSound
    Victoria
    Vancouver
    Seattle
    Kelowna
    PrinceGeorge
    Calgary
    SaltLakeCity
    Sacramento
    LasVegas
    Phoenix

Space is the Place

To solve the Hilbert Sort problem, we have to avoid the temptation to think about the Hilbert curve and the way that it is constructed. While we spent quite a bit of time talking about the Hilbert curve and how it is constructed, the curve itself is not what we are interested in - we are interested in the order in which the points are visited.

Also remember, the motivation of solving the Hilbert Sort problem is to arrange spatial \((x,y)\) points so closer points are nearer together.

No matter how many iterations of the Hilbert curve we end up drawing, we always apply the same procedure: cut the square into four quadrants, reflect the southwest corner about the bottom left to top right diagonal, and reflect the southeast corner about the bottom right to top left diagonal.

We will always visit points in the southwest quadrant before we visit points in the northwest quadrant; we will always visit points in the northwest corner before we visit points in the northeast corner; etc.

The Reflections

The trickiest part of the Hilbert Sort problem is the reflection that happens to the lower left and lower right quadrants.

Reflected Quadrants

Start with the first step of the Hilbert sort - take a square with points contained in it. Split the square into four quadrants (with the intention of creating four sub-problems). However, to conform to the Hilbert Curve construction process, the lower left and lower right squares must be reflected. The lower left square is reflected about the bottom left to upper right diagonal, while the lower right square is reflected about the bottom right to upper left diagonal.

Convince yourself of this by studying the curve construction procedure as illustrated by Hilbert himself in his 1890 paper (a.k.a., Hilbert Illustrates A Hilbert Curve):

Hilbert Illustrates Construction of Hilbert Curve

We are working toward a recursive method - and recursive methods call themselves repeatedly, apply to subproblems that are trivially similar. However, to translate this into a recursive problem, we have to deal with the rotations within the current recursive step, in such a way that we don't need to know the orientation of the prior square to know the order in which to visit the squares - it is always southwest, northwest, notheast, southwest.

After we split the squares into quadrants, after we toss out any quadrants with no points, we walk through each of the four quadrants in order (southwest, northwest, northeast, southwest). If there is a single point in the quadrant, we add it to the priority queue.

It is here that we take care of the rotation - before we recursively call the Hilbert sort method on the quadrant itself.

Scaling

We have a prescribed order for the four quadrants in the current recursive level, and the current recursive level is working its way through each of those four quadrants. But remember, our algorithm only cares about the order of points. It does not care about the \((x,y)\) location. So we can ireflect \((x,y)\) points by changing their \((x,y)\) coordinate locations. Ultimately we are only changing the program's internal representation of each point, not the original data on disk, so we can think of \((x,y)\) as mutable for our purposes.

This is an important part of our solution: scaling (and reflecting) each quadrant before recursively calling the Hilbert sort method on the points contained in it.

If we are considering a single quadrant of dimensions \(\frac{S}{2} \times \frac{S}{2}\), containing points \((x,y)\), we may be able to pass in the corners of our square, plus the \((x,y)\) points contained in it. However, as our squares get smaller, the distance between points gets smaller as well, so this has an upper limit as to how many points it can sort.

On the other hand, we can avoid passing all that information around and using doubles, by just rescaling everything to the given quadrant. We want each recursive level to completely forget about where in the recursive stack it is, how large its square is relative to the original, etc. All it should be doing is solving the same problem repeatedly - which is what recursion is best at. If we double the sides of the square, we get a shape with original size \(S \times S\). To keep the points shifted correctly we double their \((x,y)\) coordinates to \((2x, 2y)\).

Once this transformation is performed, we are ready to call the Hilbert Sort function recursively - for the northwest and northeast quadrants only. The southwest and southeast quadrants still have a ways to go.

Reflection

In addition to the scale-up transformation, southwest and southeast qaudrant points must be reflected about their diagonals.

Here's an example of what the process looks like in action:

Hilbert Sort Poster Flowchart

Solving the Reflection Problem

The above section described where in the process the reflection of the \((x,y)\) points should happen. The process of applying the reflection differs between the southwest and southeast quadrants.

In the southwest quadrant, points are being reflected about the diagonal line \(y=x\), so the reflection of \((x,y)\) points in the southwest quadrant can be performed by swapping the \(x\) and \(y\) values of all of the points in that quadrant.

In the southeast quadrant, the points are refelected about the diagonal \(y = -x\), but it is not quite \(y = -x\), given that there is an offset of a half-quadrant width on the left.

After an \((x,y)\) point is transformed, it has a height equal to the distance from the point's x coordinate to the start of the qudarant. In equations,

$$ y = S - x $$

Further, after an \((x,y)\) point is transformed, the distance from the top of the bounding box to the former y coordinate is the new x coordinate,

$$ x = \frac{S}{2} - y $$

The relative x coordinates of each point (relative meaning, 0 starts at the beginning of the curent square, rather than the whole square) are the x coordinates minus the half-quadrant width.

Once these reflections are performed, we pass the resulting \((x,y)\) points on to a new Hilbert sort. The new Hilbert sort will be operating on an \(S x S\) square, as before. Importantly, the \((x,y)\) points have been transformed in such a way that the order in which the Hilbert curve visits each point has not been affected.

Hilbert Sort Procedure

The implementation strategy is, obviously, recursive. What we want to do at each level is: * Start with a square and points contained in the square. * Cut the square under consideration into four quadrants. * Apply a transformation to each square so that it is re-oriented in a manner that matches our original Hilbert curve.

Once each of those squares goes through all of its respective recursive calls, it will return a sorted list of points. Then we will know what to do - we collect each of the sorted points from each of the four quadrants in order, maintain that order, and return those sorted quadrants.

To nail down the details, treat the square under consideration as ranging from \((0,0)\) to \((S,S)\).

Each time we cut a square into quadrants, we re-orient ourselves as to where \((0,0)\) is located and which quadrants will be visited in which order. If we are in the lower left quadrant, \(x\) is below \(\frac{S}{2}\) and \(y\) is below \(\frac{S}{2}\), so we rotate and reflect by swapping x and y:

        X -> Y
        Y -> X

If we are in the upper left quadrant, x is below \(\frac{S}{2}\), y is above \(\frac{S}{2}\), so subtract \(\frac{S}{2}\) from y and we're done.

        X -> X
        Y -> Y-(S/2)

If we are in the upper right quadrant, x is above \(\frac{S}{2}\), y is above \(\frac{S}{2}\), so subtract \(\frac{S}{2}\) from both

        X -> X - S/2
        Y -> Y - S/2

If we are in the lower right quadrant, our x and y values are now relative to the quadrant bounding box. The distance to the top of the bounding box to the y coordinate becomes our new x coordinate, while the distance from the right of the bounding box S to the x coordinate becomes our new y coordinate:

        X -> S/2 - Y
        Y -> S - X

Recursion always requires a base case and a recursive case. Our "base case" is the simple comparison of one or no points in each of our four quadrants. If we get to this base case, we know the order in which the Hilbert Curve will visit each of those points.

If we are not at the base case, if we have a large number of points to sort, we can bin together all the points in a given quadrant, and consider the order in which those points go with an additional level of finer granularity.

Pseudocode

set square dimension S

create unsorted queue
load points into unsorted queue

create sorted queue
sorted queue = hilbert_sort( unsorted queue, square dimension )

Now here is the Hilbert sort function:

define hilbert_sort( unsorted queue, square dimension ):
    create southwest queue
    create northwest queue
    create northeast queue
    create southeast queue
    for each point:
        if in southwest:
            create new point using X -> Y, Y -> X
            add to southwest queue
        if in northwest:
            create new point using X -> 2X, Y -> 2Y - S
            add to northwest queue
        if in northeast:
            create new point using X -> 2X - S, Y -> 2Y - S
            add to northeast queue
        if in southeast:
            create new point using X -> S - 2Y, Y -> 2S - 2X
            add to southeast queue

        hilbertsort(southwest queue, square dimension)
        hilbertsort(northwest queue, square dimension)
        hilbertsort(northeast queue, square dimension)
        hilbertsort(southeast queue, square dimension)

        create new results queue
        add points from southwest into results queue
        add points from northwest into results queue
        add points from northeast into results queue
        add points from southeast into results queue
        return results queue

References

  1. "ACM Pacific Region Programming Competition." Association of Computing Machinery. Accessed 19 June 2017. <http://acmicpc-pacnw.org/>

  2. "Über die stetige Abbildung einer Linie auf ein Flächenstück." D. Hilbert. Mathematische Annalen 38 (1891), 459–460. (pdf)

  3. "Hilbert Curve." Wikipedia: The Free Encyclopedia. Wikimedia Foundation. Edited 29 April 2017. Accessed 23 June 2017. <https://en.wikipedia.org/wiki/Hilbert_curve>

CSE 143 Final Project: Hilbert Sort: 1. The Problem

Posted in Computer Science

permalink

Table of Contents

This is the first in a series of three posts detailing the Hilbert Sort problem, its solution, and its implementation. This post sets up the problem.

Hilbert Sort: Motivation

In the next few series of posts, we will cover the Hilbert Sort problem, how it works, and how to implement it.
However, before we describe the problem further, let's start with some motivation for solving this problem.

Suppose we're dealing with a very large number of independent objects on a 2D coordinate grid, each with a coordinate location \((x,y)\). (For example, a large population of particles moving in a fluid, or a large number of characters on a map in a game.)

Here is our box of particles:

Box of Particles

Now, suppose that we have more data than can be handled by a single computer, and we want to arrange the data on different computers. However, we want to arrange the data in such a way that we preserve the spatial characteristics of the data.

If we implement a naive sort method for Cartesian points that sorts points by x coordinate value, breaking ties with the y coordinate value, we end up with points that are neighbors in space, but far away in the data container's storage (like an array). This is particularly true if there are large crowds of points in the grid.

Here is an illustration of a set of points and their resulting storage schema in an array that sorts points by x coordinate. It shows two purple particles, close by in space, but with
several green particles further away distance-wise but not with respect to the x coordinate. The spatial locality of points is not preserved in the data container, so clearly, a better method of sorting and organizing points is needed.

Bad Schema

An alternative to this schema that yields better locality properties, but that leads to a much higher cost for sorting points, involves iterating over each point, and for each point, finding the closest points to it in space. However, this itself requires iterating over each point. This approach ends up walking over each point (the point whose nearest neighbors we are finding), and doing a second nested walk over each point (checking if a point is a nearest neighbor to this one). The overall cost of doing this is \(O(N^2)\).

It is a deceptively tricky problem: how to organize a large group of points in a way that preserves spatial locality?

But first, we'll cover the topic of space filling curves, then return to this topic.

Space Filling Curves

Mathematician Giuseppe Peano was a prolific teacher and researcher known for many things, but one of his more curious ideas is known as the Peano Curve. Peano was attempting to answer the question of whether a continuous curve could be bounded by a finite space, and he invented a counter-example: a recipe for breaking a curve into parts that can be replicated and repeated and applied to the copies as many times as desired, and always result in a continuous curve.

The way that space-filling curves in general are constructed is to create a pattern, then scale it down and repeat it, attaching subsequent scaled-down, repeated curves. Peano simply invented the first curve; there are many variations on the original space-filling curve idea (including the Hilbert Curve - more on that in a moment).

The original 1890 paper by Giuseppe Peano is entitled "Sur une courbe, qui remplit toute une aire plane", published in 1890 in Mathematische Annalen I, Issue 36. Unfortunately, it has no pictures, but here is a rendering from Wikimedia Commons:

Peano Curve

(Link to original on Mediawiki Commons)

Now, the Peano curve was nice, but it had some mathematical properties that made it difficult to deal with. So in 1890, David Hilbert published a follow-up paper in Mathematische Annalen I Issue 38, entitled "Ueber die stetige Abbildung einer Linie auf ein Flächenstück", which slightly modified the recipe to create a curve with more elegant mathematical properties.

Also, he included pictures.

Here is the first set of figures from Hilbert's original 1890 paper:

Hilbert Curve

And here is a slightly cleaner rendering of the Hilbert Curve pattern repeated six times:

Hilbert Curve

(Link to original on Wikimedia Commons)

From the abstract of Hilbert's paper:

Peano has recently shown in the Mathematical Annals, 2 by an arithmetical observation, how the points of a line can be mapped continuously to the points of a surface part. The functions required for such a mapping can be produced more clearly by using the following geometrical view. Let us divide the line to be represented-about a straight line of the length 1-into four equal parts 1, 2, 3, 4, and the surface which we assume in the form of a square of the side length 1 Straight into 4 equal squares 1, 2, 3, 4 (Fig. 1). Secondly, we divide each of the partial sections 1, 2, 3, 4 again into 4 equal parts so that we obtain on the straight the 16 partial sections 1, 2, 3, ..., 16; At the same time, each of the 4 squares 1, 2, 3, 4 is divided into 4 equal squares, and the numbers 1, 2, ..., 16 are then written to the resulting 16 squares, That each successive square follows the previous one with one side (Fig. 2). If we think of this method, as shown in Fig. 3, the next step, it is easy to see how to assign a single definite point of the square to any given point of the line. It is only necessary to determine the partial stretches of the line to which the given point falls. The squares indicated by the same numbers are necessarily in one another and include a certain point of the surface piece in the boundary. This is the point assigned to the given point. The image thus found is unambiguous and continuous, and vice versa, each point of the square corresponds to one, two, or four points of the line. Moreover, it appears remarkable that, by a suitable modification of the partial lines in the squares, a clear and continuous representation can easily be found, the reversal of which is nowhere more than three-fold.
- David Hilbert, "Über die stetige Abbildung einer Linie auf ein Flächenstück", Mathematische Annalen Vol 38

Thanks to Google Translate for an incredible job.

Constructing a Hilbert Curve

To construct a Hilbert curve, you just have to follow the recipe. It doesn't matter what your square contains so far, or how many levels in you are, whether it's the first curve or the five hundredth:

  1. First, take yer square.

  2. Second, quadruple yer square. That means, make four copies, that all make a square.

  3. Now rotate the bottom left and bottom right via diagonal reflection.

  4. Fourth step is, you're done - that's you're new square!

Performing a Hilbert Sort

We will omit a proof of the statement, but given a set of unique (x,y) points, we can always construct a minimal-size Hilbert Curve that visits each point only once.

Points can be sorted, then, according to when they would be visited by said Hilbert Curve. And this ordering provides better preservation of spatial locality and structure of points when aligning them in memory, because these space-filling curves are recursive and preserve spatial locality in a top-down fashion.

For example, if we have two points in our square, one in the lower left and one in the lower right, and we are sorting them via a Hilbert Sort, we definitely know that a Hilbert curve constructed to visit both of these points will definitely visit the lower left point (the quadrant where the Hilbert curve starts) before it visits the lower right point (in the quadrant where the Hilbert curve stops).

This requires thinking about \((x,y)\) points in the box in terms of quadrants, and the order in which the Hilbert curve will visit each quadrant or region, rather than thinking in terms of the explicit Hilbert curve that will visit each particular \((x,y)\) point:

Box of Particles, Divided Into Quadrants

This is a subtle shift in the thinking about this problem, but it is crucial to a successful implementation of a Hilbert sort. The problem that will follow, which asks to implement the Hilbert sort, has many distracting details, including the Hilbert curve itself.

Remember, in Hilbert sort, the end goal is not the curve itself, but the sort order.

Here is how the quadrant-by-quadrant partitioning to sort elements ends up looking when applied repeatedly:

Repeated Applications

It is important to note that the two diagonal reflections happening in the corners are the trickiest part of this problem. We will cover this operation in greater detail in the solution blog post.

Problem Statement

Following is a paraphrased problem statement from the original ACM ICPC Regional Programming Competition problem statement that this problem and its solution was based on.

"If points \((x,y)\) are sorted primarily by x, breaking ties by y, then points that are adjacent in memory will have similar x coordinates but not necessarily similar y, potentially placing them far apart on the grid. To better preserve distances, we may sort the data along a continuous space-filling curve.

"We consider one such space-filling curve called the Hilbert curve...

"Given some locations of interest, you are asked to sort them according to when the Hilbert curve visits them. Note that while the curve intersects itself at infinitely many places, e.g., at \((\frac{S}{2}, \frac{S}{2})\), making S odd guarantees all integer points are visited just once."

Here is an example input file, giving a set of points on an \(M \times N\) grid:

    14 25
    5 5 Honolulu
    5 10 PugetSound
    5 20 Victoria
    10 5 Berkeley
    10 10 Portland
    10 15 Seattle
    10 20 Vancouver
    15 5 LasVegas
    15 10 Sacramento
    15 15 Kelowna
    15 20 PrinceGeorge
    20 5 Phoenix
    20 10 SaltLakeCity
    20 20 Calgary

The corresponding output can be verified intuitively, assuming the coordinates given above are accurate! Here is the output. Indeed, the order in which each city is visited is what we would expect if we drew a space-filling Hilbert curve over a map of the western United States and Canada.

    Honolulu
    Berkeley
    Portland
    PugetSound
    Victoria
    Vancouver
    Seattle
    Kelowna
    PrinceGeorge
    Calgary
    SaltLakeCity
    Sacramento
    LasVegas
    Phoenix

Now that we've used up all of our space here describing the problem, in a follow-up post we will go into greater detail about how to solve the problem conceptually, and come up with some pseudocode for a recursive method (since this is a recursive task). Then, a third post will go into greater detail about the final Java code to perform this task.

References

  1. "ACM Pacific Region Programming Competition." Association of Computing Machinery. Accessed 19 June 2017. <http://acmicpc-pacnw.org/>

  2. "Sur une courbe, qui remplit toute une aire plane." G. Peano. Mathematische Annalen 36 (1890), 157–160. (pdf)

  3. "Über die stetige Abbildung einer Linie auf ein Flächenstück." D. Hilbert. Mathematische Annalen 38 (1891), 459–460. (pdf)

  4. "Hilbert Curve." Wikipedia: The Free Encyclopedia. Wikimedia Foundation. Edited 29 April 2017. Accessed 23 June 2017. <https://en.wikipedia.org/wiki/Hilbert_curve>

  5. "Peano Curve." Wikipedia: The Free Encyclopedia. Wikimedia Foundation. Edited 16 October 2016. Accessed 23 June 2017. <https://en.wikipedia.org/wiki/Peano_curve>

March 2022

How to Read Ulysses

July 2020

Applied Gitflow

September 2019

Mocking AWS in Unit Tests

May 2018

Current Projects

November 2017

A Hard(y) Math Problem