Sierpinski pentagon
You are encouraged to solve this task according to the task description, using any language you may know.
Produce a graphical or ASCII-art representation of a Sierpinski pentagon (aka a Pentaflake) of order 5. Your code should also be able to correctly generate representations of lower orders: 1 to 4.
- See also
Haskell
For universal solution see Fractal tree#Haskell
<lang haskell>import Graphics.Gloss
pentaflake :: Int -> Picture pentaflake order = iterate transformation pentagon !! order
where transformation = Scale s s . foldMap copy [0,72..288] copy a = Rotate a . Translate 0 x pentagon = Polygon [ (sin a, cos a) | a <- [0,2*pi/5..2*pi] ] x = 2*cos(pi/5) s = 1/(1+x)
main = display dc white (Color blue $ Scale 300 300 $ pentaflake 5)
where dc = InWindow "Pentaflake" (400, 400) (0, 0)</lang>
Explanation: Since Picture forms a monoid with image overlaying as multiplication, so do functions having type Picture -> Picture:
f,g :: Picture -> Picture f <> g = \p -> f p <> g p
Function copy
for an angle returns transformation, which shifts and rotates given picture, therefore foldMap copy
for a list of angles returns a transformation, which shifts and rotates initial image five times. After that the resulting image is scaled to fit the inital size, so that it is ready for next iteration.
If one wants to get all intermediate pentaflakes transformation
shoud be changed as follows:
<lang haskell>transformation = Scale s s . (Rotate 36 <> foldMap copy [0,72..288])</lang>
See also the implementation using Diagrams
Java
<lang java>import java.awt.*; import java.awt.event.ActionEvent; import java.awt.geom.Path2D; import static java.lang.Math.*; import java.util.Random; import javax.swing.*;
public class SierpinskiPentagon extends JPanel {
// exterior angle final double degrees072 = toRadians(72);
/* After scaling we'll have 2 sides plus a gap occupying the length of a side before scaling. The gap is the base of an isosceles triangle with a base angle of 72 degrees. */ final double scaleFactor = 1 / (2 + cos(degrees072) * 2);
final int margin = 20; int limit = 0; Random r = new Random();
public SierpinskiPentagon() { setPreferredSize(new Dimension(640, 640)); setBackground(Color.white);
new Timer(3000, (ActionEvent e) -> { limit++; if (limit >= 5) limit = 0; repaint(); }).start(); }
void drawPentagon(Graphics2D g, double x, double y, double side, int depth) { double angle = 3 * degrees072; // starting angle
if (depth == 0) {
Path2D p = new Path2D.Double(); p.moveTo(x, y);
// draw from the top for (int i = 0; i < 5; i++) { x = x + cos(angle) * side; y = y - sin(angle) * side; p.lineTo(x, y); angle += degrees072; }
g.setColor(RandomHue.next()); g.fill(p);
} else {
side *= scaleFactor;
/* Starting at the top of the highest pentagon, calculate the top vertices of the other pentagons by taking the length of the scaled side plus the length of the gap. */ double distance = side + side * cos(degrees072) * 2;
/* The top positions form a virtual pentagon of their own, so simply move from one to the other by changing direction. */ for (int i = 0; i < 5; i++) { x = x + cos(angle) * distance; y = y - sin(angle) * distance; drawPentagon(g, x, y, side, depth - 1); angle += degrees072; } } }
@Override public void paintComponent(Graphics gg) { super.paintComponent(gg); Graphics2D g = (Graphics2D) gg; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int w = getWidth(); double radius = w / 2 - 2 * margin; double side = radius * sin(PI / 5) * 2;
drawPentagon(g, w / 2, 3 * margin, side, limit); }
public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setTitle("Sierpinski Pentagon"); f.setResizable(true); f.add(new SierpinskiPentagon(), BorderLayout.CENTER); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); }); }
}
class RandomHue {
/* Try to avoid random color values clumping together */ final static double goldenRatioConjugate = (sqrt(5) - 1) / 2; private static double hue = Math.random();
static Color next() { hue = (hue + goldenRatioConjugate) % 1; return Color.getHSBColor((float) hue, 1, 1); }
}</lang>
Perl 6
Generate an SVG file to STDOUT. Redirect to a file to capture and display it. <lang perl6>constant order = 5; constant $dim = 250; constant $sides = 5; constant scaling-factor = ( 3 - 5**.5 ) / 2; my @orders = ((1 - scaling-factor) * $dim) «*» scaling-factor «**» (^order);
INIT say qq:to/STOP/;
<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg height="{$dim*2}" width="{$dim*2}" style="fill:blue" transform="translate($dim,$dim) rotate(-18)" version="1.1" xmlns="http://www.w3.org/2000/svg"> STOP
END say '</svg>';
my @vertices = map { cis( $_ * τ / $sides ) }, ^$sides;
for 0 ..^ $sides ** order -> $i {
my $vector = [+] @vertices[$i.base($sides).fmt("%{order}d").comb] «*» @orders; say pgon ((@orders[*-1] * (1 - scaling-factor)) «*» @vertices «+» $vector)».reals».fmt("%0.3f");
};
sub pgon (@q) { qq|<polygon points="{@q}"/>| } </lang>
Python
Draws the result on a canvas. Runs pretty slowly. <lang python>from turtle import * import math speed(0) # 0 is the fastest speed. Otherwise, 1 (slow) to 10 (fast) hideturtle() # hide the default turtle
part_ratio = 2 * math.cos(math.radians(72)) side_ratio = 1 / (part_ratio + 2)
hide_turtles = True # show/hide turtles as they draw path_color = "black" # path color fill_color = "black" # fill color
- turtle, size
def pentagon(t, s):
t.color(path_color, fill_color) t.pendown() t.right(36) t.begin_fill() for i in range(5): t.forward(s) t.right(72) t.end_fill()
- iteration, turtle, size
def sierpinski(i, t, s):
t.setheading(0) new_size = s * side_ratio if i > 1: i -= 1 # create four more turtles for j in range(4): t.right(36) short = s * side_ratio / part_ratio dist = [short, s, s, short][j] # spawn a turtle spawn = Turtle() if hide_turtles:spawn.hideturtle() spawn.penup() spawn.setposition(t.position()) spawn.setheading(t.heading()) spawn.forward(dist) # recurse for spawned turtles sierpinski(i, spawn, new_size) # recurse for parent turtle sierpinski(i, t, new_size) else: # draw a pentagon pentagon(t, s) # delete turtle del t
def main():
t = Turtle() t.hideturtle() t.penup() screen = t.getscreen() y = screen.window_height() t.goto(0, y/2-20) i = 5 # depth. i >= 1 size = 300 # side length # so the spawned turtles move only the distance to an inner pentagon size *= part_ratio # begin recursion sierpinski(i, t, size)
main()</lang>
Racket
<lang racket>#lang racket/base (require racket/draw pict racket/math racket/class)
- exterior angle
(define 72-degrees (degrees->radians 72))
- After scaling we'll have 2 sides plus a gap occupying the length
- of a side before scaling. The gap is the base of an isosceles triangle
- with a base angle of 72 degrees.
(define scale-factor (/ (+ 2 (* (cos 72-degrees) 2))))
- Starting at the top of the highest pentagon, calculate
- the top vertices of the other pentagons by taking the
- length of the scaled side plus the length of the gap.
(define dist-factor (+ 1 (* (cos 72-degrees) 2)))
- don't use scale, since it scales brushes too (making lines all tiny)
(define (draw-pentagon x y side depth dc)
(let recur ((x x) (y y) (side side) (depth depth)) (cond [(zero? depth) (define p (new dc-path%)) (send p move-to x y) (for/fold ((x x) (y y) (α (* 3 72-degrees))) ((i 5)) (send p line-to x y) (values (+ x (* side (cos α))) (- y (* side (sin α))) (+ α 72-degrees))) (send p close) (send dc draw-path p)] [else (define side/ (* side scale-factor)) (define dist (* side/ dist-factor)) ;; The top positions form a virtual pentagon of their own, ;; so simply move from one to the other by changing direction. (for/fold ((x x) (y y) (α (* 3 72-degrees))) ((i 5)) (recur x y side/ (sub1 depth)) (values (+ x (* dist (cos α))) (- y (* dist (sin α))) (+ α 72-degrees)))])))
(define (dc-draw-pentagon depth w h #:margin (margin 4))
(dc (lambda (dc dx dy) (define old-brush (send dc get-brush)) (send dc set-brush (make-brush #:style 'transparent)) (draw-pentagon (/ w 2) (* 3 margin) (* (- (/ w 2) (* 2 margin)) (sin (/ pi 5)) 2) depth dc) (send dc set-brush old-brush)) w h))
(dc-draw-pentagon 1 120 120) (dc-draw-pentagon 2 120 120) (dc-draw-pentagon 3 120 120) (dc-draw-pentagon 4 120 120) (dc-draw-pentagon 5 640 640)</lang>
Sidef
Generates a SVG image to STDOUT. Redirect to a file to capture and display it. <lang ruby>define order = 5 define sides = 5 define dim = 500 define scaling_factor = ((3 - 5**0.5) / 2) var orders = order.of {|i| ((1-scaling_factor) * dim) * scaling_factor**(i-1) }
say <<"STOP"; <?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg height="#{dim*2}" width="#{dim*2}"
style="fill:blue" transform="translate(#{dim},#{dim}) rotate(-18)" version="1.1" xmlns="http://www.w3.org/2000/svg">
STOP
var vertices = sides.of {|i| Complex(0, i * Number.tau / sides).exp }
(sides**order).range.each { |i|
var vector = ([vertices["%#{order}d" % i.base(sides) -> chars]] »*« orders «+») var points = (vertices »*» orders[-1]*(1-scaling_factor) »+» vector »reals»() «%« '%0.3f') say ('<polygon points="' + points.join(' ') + '"/>')
}
say '</svg>'</lang>
zkl
<lang zkl>const order=5, sides=5, dim=250, scaleFactor=((3.0 - (5.0).pow(0.5))/2); const tau=(0.0).pi*2; // 2*pi*r orders:=order.pump(List,fcn(n){ (1.0 - scaleFactor)*dim*scaleFactor.pow(n) });
println(
- <<<
0'|<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg height="%d" width="%d" style="fill:blue" transform="translate(%d,%d) rotate(-18)"
version="1.1" xmlns="http://www.w3.org/2000/svg">|
- <<<
.fmt(dim*2,dim*2,dim,dim));
vertices:=sides.pump(List,fcn(s){ (1.0).toRectangular(tau*s/sides) }); // points on unit circle vx:=vertices.apply('wrap([(a,b)]v,x){ return(a*x,b*x) }, // scaled points orders[-1]*(1.0 - scaleFactor)); fmt:="%%0%d.%dB".fmt(sides,order).fmt; //-->%05.5B (leading zeros, 5 places, base 5) sides.pow(order).pump(Console.println,'wrap(i){
vector:=fmt(i).pump(List,vertices.get) // "00012"-->(vertices[0],..,vertices[2]) .zipWith(fcn([(a,b)]v,x){ return(a*x,b*x) },orders) // ((a,b)...)*x -->((ax,bx)...) .reduce(fcn(vsum,v){ vsum[0]+=v[0]; vsum[1]+=v[1]; vsum },L(0.0, 0.0)); //-->(x,y) pgon(vx.apply(fcn([(a,b)]v,c,d){ return(a+c,b+d) },vector.xplode()));
}); println("</svg>"); // 3,131 lines
fcn pgon(vertices){ // eg ( ((250,0),(248.595,1.93317),...), len 5
0'|<polygon points="%s"/>|.fmt( vertices.pump(String,fcn(v){ "%.3f %.3f ".fmt(v.xplode()) }) )
}</lang>
- Output:
See this image. Displays fine in FireFox, in Chrome, it doesn't appear to be transformed so you only see part of the image.
zkl bbb > sierpinskiPentagon.zkl.svg $ wc sierpinskiPentagon.zkl.svg 3131 37519 314183 sierpinskiPentagon.zkl.svg