19
\$\begingroup\$

Title courtesy of Greg Martin

For this challenge, I'll define an arc of size \$k\$ as a single piece of a sine wave with a length of \$k\$ units and an height of \$\frac{k}{4}\$ units:

enter image description here

And I'll define a swoosh of order \$1\$ and \$2\$ as a single arc of size 1, and recursively define a swoosh of order \$k\$ as a swoosh of order \$k-1\$ followed by a flipped swoosh of order \$k-2\$, with an arc the same length as the previous two swooshes laid end-to-end.

enter image description here

For example, here are the swooshes of order 3, 4, 5, and 6:

enter image description here

You might notice that the length of a swoosh of order \$n\$ is the \$n\$th fibonacci number, hence the name.

Reference implementation:

let ctx = c.getContext('2d') const scale = 30; let drawArc = (length, offset = 0, amp = 1) => { ctx.moveTo(offset * scale, c.height / 2) for (i = 0; i <= scale * length; i++) { ctx.lineTo(i + offset * scale, -Math.sin(Math.PI * i / (scale * length)) * amp * length * scale / 4 + c.height / 2) } ctx.stroke() } fib = n => fib[n] ||= n <= 2 ? 1 : fib(n - 1) + fib(n - 2) let swoosh = (order, offset = 0, amp = 1) => { if (order <= 2) { drawArc(1, offset, amp); } else { swoosh(order - 1, offset, amp) swoosh(order - 2, offset + fib(order - 1), -amp) drawArc(fib(order), offset, amp) } } swoosh(8)
<canvas width=1000 height=500 id=c></canvas>

Your challenge is to, given an integer \$n\$, draw a fibonacci swoosh of order \$n\$ as defined above, either outputting to an image file or drawing on the screen. You may use 0-indexing or 1-indexing. It's fine if the size of your answer's output is limited by screen/canvas size, but your answer should be able to handle at least \$n=6\$, and each arc should be at least 10 pixels wide onscreen. Each arc should be recognisable as a sine curve.

This is , shortest wins!

Testcases

Here's a higher-resolution image for n=6:

enter image description here And here's an image for n=10: enter image description here

\$\endgroup\$

5 Answers 5

9
\$\begingroup\$

JavaScript (ES6), 287 286 246 235 233 bytes

f= n=>`<svg viewBox=-1,-${[g(n)*6+1,i*24+2,i*6+g(n-2)*6+2]}><path fill=none stroke=#000 d=M0,0${h(n,-3)}>` g=n=>i=n<3||g(n-1)+g(n-2) h=(n,s)=>'c'+[4,s,8,t=s*2,12,t,4,0,8,-s,12,-t].map(m=>m*g(n))+(n>2?'m'+[-i*24,0]+h(--n,s)+h(--n,-s):'')
<input type=number oninput=o.innerHTML=f(+this.value) min=1 max=20><div id=o>

Outputs an HTML5 compatible SVG image. Doesn't adjust the stroke-width for large n so the swoosh tends to fade out. n limited to 20 because it's too slow otherwise. Edit: Saved 1 byte thanks to @noodleman. Saved a few bytes thanks to @Ausername. Version with stroke-width adjustment:

f= n=>`<svg viewBox=-1,-${[g(n)*24+1,i*96+2,i*24+g(n-2)*24+2]}><path fill=none stroke=#000 stroke-width=${g(n-2)} d=M0,0${h(n,-3)}>` g=n=>i=n<3?1:g(n-1)+g(n-2) h=(n,s)=>'c'+[4,s,8,t=s*2,12,t,4,0,8,-s,12,-t].map(m=>m*4*g(n))+(n>2?'m'+[-i*96,0]+h(--n,s)+h(--n,-s):'')
<input type=number oninput=o.innerHTML=f(+this.value) min=1 max=20><div id=o>

\$\endgroup\$
7
  • \$\begingroup\$ You should be able to save a byte on your fibonacci function g with g=n=>n<3||g(n-1)+g(n-2) \$\endgroup\$ Commented Apr 6, 2024 at 12:07
  • \$\begingroup\$ @noodleman Yeah originally I had a raw g(n) somewhere but I think the only one left is in the stroke-width adjustment version. \$\endgroup\$ Commented Apr 6, 2024 at 12:40
  • \$\begingroup\$ [e=g(n)*6+1,e*4-2,e+g(n-2)*6+1] saves a few bytes \$\endgroup\$ Commented Apr 6, 2024 at 20:38
  • \$\begingroup\$ Also doing the initial call to h with s=-3 saves a few more bytes \$\endgroup\$ Commented Apr 6, 2024 at 20:41
  • \$\begingroup\$ -5 more by replacing g with g=n=>i=n<3?6:g(n-1)+g(n-2), removing all *6s, replacing *24 with *4, and adding a /6 in the map call in h (code) \$\endgroup\$ Commented Apr 6, 2024 at 23:06
8
\$\begingroup\$

Google Sheets, 499 490 bytes

-9 bytes thanks to @doubleunary

=SORT(SPARKLINE(LET(F,LAMBDA(F,n,IF(n<3,1,F(F,n-1)+F(F,n-2))),D,LAMBDA(l,o,a,LET(s,SEQUENCE(30*l+1,1,),{s+o*30,SIN(PI()*s/(30*l))*a*l})),W,LAMBDA(W,k,o,a,IF(k<3,D(1,o,a),{W(W,k-1,o,a);W(W,k-2,o+F(F,k-1),-a);D(F(F,k),o,a)})),z,W(W,A1,,1),y,INDEX(z,,1),x,TOCOL(SCAN(,ABS({0;y}-y)>1,LAMBDA(a,c,a+c)),2),IF(A1>2,REDUCE(FILTER(z,x=0),SEQUENCE(COUNTUNIQUE(x)-1),LAMBDA(a,i,LET(k,{a;SORT(a,SEQUENCE(ROWS(a)),)},{QUERY(k,"limit "&XMATCH(VLOOKUP(i,{x,z},2,),INDEX(k,,1),,-1));FILTER(z,x=i)}))),z)))) 

enter image description here

As pointed out by @doubleunary in the comments, it turns out that wasn't a bug but just how the function is designed. When drawing across the given coordinates, it never "lifts the pen" so if it draws \$x1 \rightarrow x2 \rightarrow x3\$ and it has to go back to \$x1\$, it draws a line from \$x3 \rightarrow x1\$ ruining the figure. I addressed this issue by filling the gaps with the intermediate coordinates so \$x1 \rightarrow x2 \rightarrow x3 \rightarrow x1\$ becomes \$x1 \rightarrow x2 \rightarrow x3 \rightarrow x2 \rightarrow x1\$ but this doubled the size of the formula and it no longer works beyond order=7, unlike the previous formula that worked up to order=18.

Google Sheets, 250 237 bytes

-13 bytes thanks to @doubleunary

=SORT(SPARKLINE(LET(F,LAMBDA(F,n,IF(n<3,1,F(F,n-1)+F(F,n-2))),D,LAMBDA(l,o,a,LET(s,SEQUENCE(30*l+1,1,),{s+o*30,SIN(PI()*s/(30*l))*a*l})),W,LAMBDA(W,k,o,a,IF(k<3,D(1,o,a),{W(W,k-1,o,a);W(W,k-2,o+F(F,k-1),-a);D(F(F,k),o,a)})),W(W,A1,,1)))) 

It's just an implementation of the code OP provided.

enter image description here

The straight line connecting the start and end of the wave seems to be a bug with the SPARKLINE function.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ When sparkline() is used like this, it can only draw one continuous line. You could avoid the straight line by "tracking back" to the start position each time you finish a wave. See what happens if you swap the order of sort() and sparkline() and specify a low value for order. \$\endgroup\$ Commented Apr 6, 2024 at 17:20
  • \$\begingroup\$ ahh I see that makes sense! I've never used SPARKLINE before so I'm not very familiar with it. I'll look into it. \$\endgroup\$ Commented Apr 6, 2024 at 17:25
  • 1
    \$\begingroup\$ -14 bytes: =sort(sparkline(let(f,lambda(f,n,if(n<3,1,f(f,n-1)+f(f,n-2))),d,lambda(l,o,a,let(s,sequence(30*l+1,1),{s+o*30,sin(pi()*s/(30*l))*a*l})),w,lambda(w,k,o,a,if(k<3,d(1,o,a),{w(w,k-1,o,a);w(w,k-2,o+f(f,k-1),-a);d(f(f,k),o,a)})),w(w,A1,,1)))) \$\endgroup\$ Commented Apr 6, 2024 at 17:29
  • 1
    \$\begingroup\$ @doubleunary, thanks! I implemented that suggestion (although we still need the , after the 1, or we get this instead of this) and fixed the line. \$\endgroup\$ Commented Apr 6, 2024 at 22:57
  • 1
    \$\begingroup\$ Very nice! Tried to decrease recursion with a reduce() based Fibonacci but that did not help much. You can snip 9 bytes by replacing the second w(w,a1,,1) with z. \$\endgroup\$ Commented Apr 7, 2024 at 4:54
6
\$\begingroup\$

K (ngn/k) 106 95 bytes

{?|/''i=\:_0.5+w%4%1-{:[x<2;k;o[x-1+b;y+y-b]%2-4*b:_y+y],k:`[email protected]*y}[x]'(i:!1+w)%w:2/1,&3+x} 

It takes one argument, the order of the swoosh, and outputs the bitmap.

How it works?

The function

{:[x<2;k;o[x-1+b;y+y-b]%2-4*b:_y+y],k:`[email protected]*y} 

takes two arguments, the order and normalized x coordinate, then it outputs a list of y coordinates the swoosh of that order passes through at the given x coordinate. It calculates the coordinates recursively.

The rest of the code transforms coordinates into bitmap.

Try it here

Here are the bitmaps for k from 1 to 7 (converted to png).

k=1

k=2

k=3

k=4

k=5

k=6

k=7

\$\endgroup\$
4
\$\begingroup\$

C (GCC), 259 257 bytes

w;a;char*m;z(k,s,f,x){k=k>2?z(k-2,z(k-1,s,f),-f):s+100;float W=k-s,q=asin(.75);for(x=0;x<W;++x)if(m)m[(int)(w/2-f*W*(sin(x/W*(3.14-2*q)+q)-.75)+.5)*w+s+x+a]=1;return k;}main(n){w=z(n,0);a=sprintf(m=calloc(w,w+1),"P5 %d %d 1 ",w,w);z(n,0,1);write(1,m,w*w);} 

Input is given as argc, outputs a PGM image.

Function z draws a swoosh of order k, starting at the x coordinate s, and returns the x coordinate at which it ends; f can be either -1 or 1, and it determines if the swoosh is flipped or not (respectively). If the output buffer is NULL, it performs a dry run.

How it works:

  • first, do a dry run of z to compute the width of the final image; for simplicity, the height is set equal to the width;
  • allocate a buffer of proper size, and write the PGM header;
  • then re-run z;
  • output the buffer.
\$\endgroup\$
4
  • \$\begingroup\$ I think you can get rid of return k in z(k,s,f,x) as it's already the first argument. Reference \$\endgroup\$ Commented Apr 9, 2024 at 9:11
  • \$\begingroup\$ @Blue maybe, apart from the fact that it would be another language (GCC -O0), I've just tried and I can't get it work, and I don't think it's doable without adding less then 9 bytes. \$\endgroup\$ Commented Apr 9, 2024 at 10:29
  • \$\begingroup\$ First, I have to introduce another variable (let's call it e) in the signature, since I need the value of k unaltered for the next calls; the same holds for the other parameters, except for x which is modified before the function terminates. This already adds up to +6 bytes, so... I won't try to get it work, you're free to prove me wrong :) \$\endgroup\$ Commented Apr 9, 2024 at 10:33
  • 1
    \$\begingroup\$ Oh yeah, it took me a second to figure out what is happening, it most certainty won't be worth it. Good job! \$\endgroup\$ Commented Apr 9, 2024 at 13:41
4
\$\begingroup\$

Desmos, 153 bytes

c=180 h(n,x)=f(n,x)[1...c] g(n)=\{n<2:n,g(n-1)+g(n-2)\} k=g(n) f(n,x)=\{n<3:[45\sin x\{0<x<c\}],\join(f(n-1,x),-f(n-2,x-cg(n-1)),45k\sin x/k\{0<x<ck\})\} 

The fibonacci swoosh of order \$n\$ can be displayed by calling h(n,x) (x is a free variable, like in f(x)=x or f(x)=x^2). For example, h(3,x) will display the fibonacci swoosh of order 3.

Note that the graph is set in degrees mode; it will not work in radians mode.

Try It On Desmos!

Try It On Desmos! - Prettified

Second ever Desmos answer using recursion!!! I am still gushing over the fact that Desmos actually added recursion; it's now turing complete and that opens the door for so many challenges that Desmos previously couldn't solve. Just to think that just a couple days ago, this challenge would not be possible to do in Desmos...

Also, this does work up to \$n=6\$ (see screenshots below), but for some reason that I am unaware of, it takes an extremely long time for the graph to display anything. I didn't keep track of exactly how long it took to load the graph, but, for me, it was upwards of 30 minutes. This is why I didn't test \$n>6\$; it was taking super long for the graphs to load anything. Although in theory, it should be able to handle a couple of cases past \$n=6\$. I found that the runtime can be reduced by replacing the c in [1...c] to a smaller number (like 20), but it still takes a while to display the graph.

Also, for some reason that I am also unaware of, simply calling f(n,x) without any modifications will result in the following error: Can't plot a list whose length depends on free variable 'x'.. To fix this, f(n,x) has to be indexed by a range that is dependent on a non-free variable; in this case, it is c, which is defined to be 180. This is precisely what h(n,x) achieves.

Here are the screenshots of the graphs after they finished running.

Prettified:

Golfed:

\$\endgroup\$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.