jakubcerveny / gilbert

Space-filling curve for rectangular domains or arbitrary size.
BSD 2-Clause "Simplified" License
121 stars 11 forks source link

Make into a generator? #2

Closed clawsoon closed 3 years ago

clawsoon commented 3 years ago

Hi! This is a great little algorithm. I made my version a little friendlier by turning it into a generator, so that I could use it in other functions. Not sure if you'd be interested in doing that. The diff looks like this for the 2d script; same basic idea for the 3d script. I could put in a formal pull request if you're interested in the idea and prefer that way of working.

diff --git a/gilbert2d.py b/gilbert2d.py
index 15035b2..c402d3a 100755
--- a/gilbert2d.py
+++ b/gilbert2d.py
@@ -21,14 +21,14 @@ def gilbert2d(x, y, ax, ay, bx, by):
     if h == 1:
         # trivial row fill
         for i in range(0, w):
-            print(x, y)
+            yield(x, y)
             (x, y) = (x + dax, y + day)
         return

     if w == 1:
         # trivial column fill
         for i in range(0, h):
-            print(x, y)
+            yield(x, y)
             (x, y) = (x + dbx, y + dby)
         return

@@ -44,8 +44,8 @@ def gilbert2d(x, y, ax, ay, bx, by):
             (ax2, ay2) = (ax2 + dax, ay2 + day)

         # long case: split in two parts only
-        gilbert2d(x, y, ax2, ay2, bx, by)
-        gilbert2d(x+ax2, y+ay2, ax-ax2, ay-ay2, bx, by)
+        yield from gilbert2d(x, y, ax2, ay2, bx, by)
+        yield from gilbert2d(x+ax2, y+ay2, ax-ax2, ay-ay2, bx, by)

     else:
         if (h2 % 2) and (h > 2):
@@ -53,9 +53,9 @@ def gilbert2d(x, y, ax, ay, bx, by):
             (bx2, by2) = (bx2 + dbx, by2 + dby)

         # standard case: one step up, one long horizontal, one step down
-        gilbert2d(x, y, bx2, by2, ax2, ay2)
-        gilbert2d(x+bx2, y+by2, ax, ay, bx-bx2, by-by2)
-        gilbert2d(x+(ax-dax)+(bx2-dbx), y+(ay-day)+(by2-dby),
+        yield from gilbert2d(x, y, bx2, by2, ax2, ay2)
+        yield from gilbert2d(x+bx2, y+by2, ax, ay, bx-bx2, by-by2)
+        yield from gilbert2d(x+(ax-dax)+(bx2-dbx), y+(ay-day)+(by2-dby),
                  -bx2, -by2, -(ax-ax2), -(ay-ay2))

@@ -64,9 +64,11 @@ def main():
     height = int(sys.argv[2])

     if width >= height:
-        gilbert2d(0, 0, width, 0, 0, height)
+        for x, y in gilbert2d(0, 0, width, 0, 0, height):
+            print(x, y)
     else:
-        gilbert2d(0, 0, 0, height, width, 0)
+        for x, y in gilbert2d(0, 0, 0, height, width, 0):
+            print(x, y)
clawsoon commented 3 years ago

And to make it even more useful as an import:

def main(width, height):

    if width >= height:
        for x, y in gilbert2d(0, 0, width, 0, 0, height):
            yield(x, y)
    else:
        for x, y in gilbert2d(0, 0, 0, height, width, 0):
            yield(x, y)

if __name__ == "__main__":
    width = int(sys.argv[1])
    height = int(sys.argv[2])
    for x, y in main(width, height):
        print(x, y)

Then I'm able to, for example, write my own script like this to put spaces between the lines, since I just need to give it width and height and it gives me back a nice generator that loops over all the values:

#!/usr/bin/env python3

import sys
import gilbert2d

def main(width, height):

    (old_x, old_y) = (0, 0)

    for x, y in gilbert2d.main(width, height):
        print((x+old_x), (y+old_y))
        print(2*x, 2*y)
        (old_x, old_y) = (x, y)

if __name__ == "__main__":
    width = int(sys.argv[1])
    height = int(sys.argv[2])
    main(width, height)
jakubcerveny commented 3 years ago

Hi @clawsoon, I like the idea of using yield and making the module readily usable, at the same time I would like the code to stay as simple and clean as possible (for me this is basically a simple way to publish the algorithm itself, close to pseudocode).

Please do post a PR. I am no Python expert so I have a few questions:

Thanks, Jakub

clawsoon commented 3 years ago

Hi @jakubcerveny. Those all sound like sensible ideas. I'll see if I can put a pull request together in the next couple of days. As I play around with it I might come up with a few more ideas. :-)

The usual convention for a private function in Python is to prefix the function name with an underscore, so _gilbert2d. It's not really private, of course, since no functions in Python are private. Details here, with some extra stuff about double underscore prefixes that I haven't taken the time to fully understand myself:

https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references

Yep, the last code I posted is just an example. Adding the two coordinates together in the example gives me the in-between step when I double the size and space things out. So if the algorithm gives me (0,0), (0,1), (1,1), I'm converting that to (0,0)[original], (0,1)[inbetween], (0,2)[original], (1,2)[inbetween], (2,2)[original]. That allows me to directly draw the pattern as pixels with spaces between the lines. Not mathematically useful, just visually useful. :-)

clawsoon commented 3 years ago

Pull request submitted.

jakubcerveny commented 3 years ago

3 merged.