How to resolve the algorithm Hilbert curve step by step in the Python programming language

Published on 12 May 2024 09:40 PM

How to resolve the algorithm Hilbert curve step by step in the Python programming language

Table of Contents

Problem Statement

Produce a graphical or ASCII-art representation of a Hilbert curve of at least order 3.

Let's start with the solution:

Step by Step solution about How to resolve the algorithm Hilbert curve step by step in the Python programming language

The given source code implements a function hilbertCurve that generates an SVG string representing a Hilbert curve of a specified degree n. A Hilbert curve is a space-filling curve, which means it fills a two-dimensional space completely, without crossing itself.

Here's a breakdown of how the code works:

  1. Function hilbertCurve: This function takes an integer n as input and returns an SVG string representing the Hilbert curve of degree n. It does so by composing several other functions:

    • hilbertPoints: Converts a tree representation of the Hilbert curve to a list of points bounded by a square of side w.
    • svgFromPoints: Converts a list of points to an SVG string that can be rendered as a visual representation of the curve.
  2. Function hilbertTree: This function generates a tree representation of the Hilbert curve. It applies a set of rules to a "seed" tree, which is a single node with the root value 'a' and an empty list of children.

    • The rules are defined in a dictionary called rule, which maps characters ('a', 'b', 'c', 'd') to sequences of characters. Each character in the sequence represents a child node.
    • The function go is used to recursively apply the rules to each node in the tree.
    • The result of hilbertTree(n) is a tree that represents the Hilbert curve of degree n.
  3. Function hilbertPoints: This function converts a tree representation of the Hilbert curve to a list of points. It uses a dictionary called vectors to define the vectors corresponding to each character in the tree.

    • For each node in the tree, the function generates a list of points by applying the corresponding vectors to the node's center point.
    • The result is a list of points that represent the Hilbert curve.
  4. Function svgFromPoints: This function converts a list of points to an SVG string. It creates an SVG string containing an SVG path element that connects all the points in the list.

    • The SVG string can then be rendered in a web browser or image viewer to visualize the Hilbert curve.

The code also includes a main function for testing the hilbertCurve function and generating an SVG file for a Hilbert curve of degree 6. The output is an SVG string that can be saved to a file and viewed as a visual representation of the curve.

In addition to the primary functions, the code also defines some generic functions:

  • Node: Constructor for a tree node. It connects a value of some kind to a list of zero or more child trees.
  • flip: Reverses the arguments of a given function.
  • iterate: Generates an infinite list of repeated applications of a function to a given value.

The "TEST" section at the end demonstrates how to use the hilbertCurve function to generate and visualize a Hilbert curve of various orders using both matplotlib.pyplot and turtle graphics.

Source code in the python programming language

'''Hilbert curve'''

from itertools import (chain, islice)


# hilbertCurve :: Int -> SVG String
def hilbertCurve(n):
    '''An SVG string representing a
       Hilbert curve of degree n.
    '''
    w = 1024
    return svgFromPoints(w)(
        hilbertPoints(w)(
            hilbertTree(n)
        )
    )


# hilbertTree :: Int -> Tree Char
def hilbertTree(n):
    '''Nth application of a rule to a seedling tree.'''

    # rule :: Dict Char [Char]
    rule = {
        'a': ['d', 'a', 'a', 'b'],
        'b': ['c', 'b', 'b', 'a'],
        'c': ['b', 'c', 'c', 'd'],
        'd': ['a', 'd', 'd', 'c']
    }

    # go :: Tree Char -> Tree Char
    def go(tree):
        c = tree['root']
        xs = tree['nest']
        return Node(c)(
            map(go, xs) if xs else map(
                flip(Node)([]),
                rule[c]
            )
        )
    seed = Node('a')([])
    return list(islice(
        iterate(go)(seed), n
    ))[-1] if 0 < n else seed


# hilbertPoints :: Int -> Tree Char -> [(Int, Int)]
def hilbertPoints(w):
    '''Serialization of a tree to a list of points
       bounded by a square of side w.
    '''

    # vectors :: Dict Char [(Int, Int)]
    vectors = {
        'a': [(-1, 1), (-1, -1), (1, -1), (1, 1)],
        'b': [(1, -1), (-1, -1), (-1, 1), (1, 1)],
        'c': [(1, -1), (1, 1), (-1, 1), (-1, -1)],
        'd': [(-1, 1), (1, 1), (1, -1), (-1, -1)]
    }

    # points :: Int -> ((Int, Int), Tree Char) -> [(Int, Int)]
    def points(d):
        '''Size -> Centre of a Hilbert subtree -> All subtree points
        '''
        def go(xy, tree):
            r = d // 2

            def deltas(v):
                return (
                    xy[0] + (r * v[0]),
                    xy[1] + (r * v[1])
                )
            centres = map(deltas, vectors[tree['root']])
            return chain.from_iterable(
                map(points(r), centres, tree['nest'])
            ) if tree['nest'] else centres
        return go

    d = w // 2
    return lambda tree: list(points(d)((d, d), tree))


# svgFromPoints :: Int -> [(Int, Int)] -> SVG String
def svgFromPoints(w):
    '''Width of square canvas -> Point list -> SVG string'''

    def go(xys):
        def points(xy):
            return str(xy[0]) + ' ' + str(xy[1])
        xs = ' '.join(map(points, xys))
        return '\n'.join(
            ['<svg xmlns="http://www.w3.org/2000/svg"',
             f'width="512" height="512" viewBox="5 5 {w} {w}">',
             f'<path d="M{xs}" ',
             'stroke-width="2" stroke="red" fill="transparent"/>',
             '</svg>'
             ]
        )
    return go


# ------------------------- TEST --------------------------
def main():
    '''Testing generation of the SVG for a Hilbert curve'''
    print(
        hilbertCurve(6)
    )


# ------------------- GENERIC FUNCTIONS -------------------

# Node :: a -> [Tree a] -> Tree a
def Node(v):
    '''Contructor for a Tree node which connects a
       value of some kind to a list of zero or
       more child trees.'''
    return lambda xs: {'type': 'Node', 'root': v, 'nest': xs}


# flip :: (a -> b -> c) -> b -> a -> c
def flip(f):
    '''The (curried or uncurried) function f with its
       arguments reversed.
    '''
    return lambda a: lambda b: f(b)(a)


# iterate :: (a -> a) -> a -> Gen [a]
def iterate(f):
    '''An infinite list of repeated
       applications of f to x.
    '''
    def go(x):
        v = x
        while True:
            yield v
            v = f(v)
    return go


#  TEST ---------------------------------------------------
if __name__ == '__main__':
    main()


import matplotlib.pyplot as plt
import numpy as np
import turtle as tt

# dictionary containing the first order hilbert curves
base_shape = {'u': [np.array([0, 1]), np.array([1, 0]), np.array([0, -1])],
              'd': [np.array([0, -1]), np.array([-1, 0]), np.array([0, 1])],
              'r': [np.array([1, 0]), np.array([0, 1]), np.array([-1, 0])],
              'l': [np.array([-1, 0]), np.array([0, -1]), np.array([1, 0])]}


def hilbert_curve(order, orientation):
    """
    Recursively creates the structure for a hilbert curve of given order
    """
    if order > 1:
        if orientation == 'u':
            return hilbert_curve(order - 1, 'r') + [np.array([0, 1])] + \
                   hilbert_curve(order - 1, 'u') + [np.array([1, 0])] + \
                   hilbert_curve(order - 1, 'u') + [np.array([0, -1])] + \
                   hilbert_curve(order - 1, 'l')
        elif orientation == 'd':
            return hilbert_curve(order - 1, 'l') + [np.array([0, -1])] + \
                   hilbert_curve(order - 1, 'd') + [np.array([-1, 0])] + \
                   hilbert_curve(order - 1, 'd') + [np.array([0, 1])] + \
                   hilbert_curve(order - 1, 'r')
        elif orientation == 'r':
            return hilbert_curve(order - 1, 'u') + [np.array([1, 0])] + \
                   hilbert_curve(order - 1, 'r') + [np.array([0, 1])] + \
                   hilbert_curve(order - 1, 'r') + [np.array([-1, 0])] + \
                   hilbert_curve(order - 1, 'd')
        else:
            return hilbert_curve(order - 1, 'd') + [np.array([-1, 0])] + \
                   hilbert_curve(order - 1, 'l') + [np.array([0, -1])] + \
                   hilbert_curve(order - 1, 'l') + [np.array([1, 0])] + \
                   hilbert_curve(order - 1, 'u')
    else:
        return base_shape[orientation]


# test the functions
if __name__ == '__main__':
    order = 8
    curve = hilbert_curve(order, 'u')
    curve = np.array(curve) * 4
    cumulative_curve = np.array([np.sum(curve[:i], 0) for i in range(len(curve)+1)])
    # plot curve using plt
    plt.plot(cumulative_curve[:, 0], cumulative_curve[:, 1])
    # draw curve using turtle graphics
    tt.setup(1920, 1000)
    tt.pu()
    tt.goto(-950, -490)
    tt.pd()
    tt.speed(0)
    for item in curve:
        tt.goto(tt.pos()[0] + item[0], tt.pos()[1] + item[1])
    tt.done()


  

You may also check:How to resolve the algorithm Queue/Definition step by step in the Lambdatalk programming language
You may also check:How to resolve the algorithm Address of a variable step by step in the Go programming language
You may also check:How to resolve the algorithm Non-decimal radices/Output step by step in the Arturo programming language
You may also check:How to resolve the algorithm Search a list step by step in the Forth programming language
You may also check:How to resolve the algorithm Shell one-liner step by step in the REXX programming language