18
\$\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\$
    – noodle man
    Apr 6 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\$
    – Neil
    Apr 6 at 12:40
  • \$\begingroup\$ [e=g(n)*6+1,e*4-2,e+g(n-2)*6+1] saves a few bytes \$\endgroup\$
    – emanresu A
    Apr 6 at 20:38
  • \$\begingroup\$ Also doing the initial call to h with s=-3 saves a few more bytes \$\endgroup\$
    – emanresu A
    Apr 6 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\$
    – emanresu A
    Apr 6 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\$ Apr 6 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\$
    – z..
    Apr 6 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\$ Apr 6 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\$
    – z..
    Apr 6 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\$ Apr 7 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\$
    – Blue
    Apr 9 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\$
    – matteo_c
    Apr 9 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\$
    – matteo_c
    Apr 9 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\$
    – Blue
    Apr 9 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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.