Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visualization of the Recaman Sequence

I saw a video about the Recaman Sequence by Numberphile. If you don't know the algorithm you can look at this link: https://www.youtube.com/watch?v=FGC5TdIiT9U or this one: https://blogs.mathworks.com/cleve/2018/07/09/the-oeis-and-the-recaman-sequence/

I wrote a little piece of software with Processing and p5.js to visualize the sequence. My algorithm makes steps in which the next hop gets defined and then I try to draw a semicircle from the previous point to the new point. My problem is that the current semicircle disappears when the next is drawn. I want all of the semicircles to remain visible.

Here is the link to CodePen where you can see my Code and the Output: https://codepen.io/stefan_coffee/pen/QBBKgp

let S = [];
let count = 0;
let active_num = 0;

function setup() {

}

function draw() {
   createCanvas(600, 400);
   background(50, 50, 50);

   for(i = 0; i < 20; i++) {
      step();
      drawStep();
   }
}  


function drawStep() {
  var x =  (S[S.indexOf(active_num)-1] + active_num ) /2;
  var y = height / 2;
  var w = active_num - S[S.indexOf(active_num)-1];
  var h = w;

  if (count % 2 == 0) {
    stroke(255);
    noFill();
    arc(x, y, w, h, PI, 0)
  } else {
    stroke(255);
    noFill();
    arc(x, y, w, h, 0, PI);
  }
}

function step() {
  count++;
  S.push(active_num);
  console.log('active_num: ' + active_num +'  count: ' + count + '  ' + S);
  if (S.indexOf(active_num - count) > 0) {
    active_num += count;
  } else {
    if (active_num - count <= 0) {
      active_num += count;
    } else {
      active_num -= count;
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

I want my output to look something like this: Visualization using ellipses

like image 638
Stefan Avatar asked Aug 05 '18 11:08

Stefan


2 Answers

While draw is called continuously in every frame, setup is only called once at startup.

Create the canvas only once and clear the back ground only once at startup. This will cause that semicircles continuously added to the canvas, with out clearing the semicircles from the previous frames:

function setup() {
    createCanvas(600, 400);
    background(50, 50, 50);
}

function draw() {

   for(i = 0; i < 20; i++) {
      step();
      drawStep();
   }
}  

Further there is an issue in your algorithm. When you search for the index of active_num, you may not find active_num in the array S, because it is not added yet, but it will be added in the next loop. See step, where active_num is added to S at the begin of the function, but increment later.

In the function drawStep you want to read the last element of the array, which you can get by:

var prev_num = S[count-1];

The relevant values for the next arc are the last element of the array S and active_num.

To solve the issue, you may change the code like this:

function drawStep() {
   var prev_num = S[count-1];

   var x = (prev_num + active_num) /2;
   var y = height / 2;
   var w = abs(active_num - prev_num);
   var h = w;

   stroke(255);
   noFill();
   arc(x, y, w, h, (count % 2 == 0) ? 0 : PI, (count % 2 == 0) ? PI : TWO_PI);
}

Note, you can completely skip the loop in the function draw, so you can better "see" the "animation":

function draw() {
   step();
   drawStep();
}

and you can manually set the frames per second by frameRate:

function setup() {
    createCanvas(600, 400);
    background(50, 50, 50);
    frameRate(20);
}

If you would scale the size of the semicircles, then the result may look like this:

function drawStep() {
   var scale    = 10;
   var prev_num = scale * S[count-1];
   var num      = scale * active_num;

   var x = (prev_num + num) /2;
   var y = height / 2;
   var w = abs(num - prev_num);
   var h = w;

   stroke(255);
   noFill();
   arc(x, y, w, h, (count % 2 == 0) ? 0 : PI, (count % 2 == 0) ? PI : TWO_PI);
}

preview

let S = [];
let count = 0;
let active_num = 0;

function setup() {
    createCanvas(600, 400);
    background(50, 50, 50);
    frameRate(20);
}

function draw() {
   step();
   drawStep();
}  

function drawStep() {
   var scale    = 10;
   var prev_num = scale * S[count-1];
   var num      = scale * active_num;

   var x = (prev_num + num) /2;
   var y = height / 2;
   var w = abs(num - prev_num);
   var h = w;

   stroke(255);
   noFill();
   arc(x, y, w, h, (count % 2 == 0) ? 0 : PI, (count % 2 == 0) ? PI : TWO_PI);
}

function step() {
   count++;
   S.push(active_num);
   console.log('active_num: ' + active_num +'  count: ' + count + '  ' + S);
   if (S.indexOf(active_num - count) > 0) {
      active_num += count;
   } else {
      if (active_num - count <= 0) {
         active_num += count;
      } else {
         active_num -= count;
      }
   }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.js"></script>
like image 68
Rabbid76 Avatar answered Nov 11 '22 14:11

Rabbid76


Firstly, as others have pointed out, you want to avoid asking p5.js to redraw the canvas from scratch every iteration. While we're at it, we'll drop the frame rate to something slow (1 per second) to prevent the browser going crazy. So, make the following changes to setup():

function setup() {
  // Move these two lines from draw():
  createCanvas(600, 400);
  background(50, 50, 50);
  // Add this line to slow things down
  frameRate(1);
}

Next, let's get rid of the loop where we call draw() and drawStep() repeatedly. p5.js will already be calling draw() repeatedly, there's no need for us to repeatedly call these functions. Let's also make the animation stop after a certain number of steps:

function draw() {
    step();
    drawStep();

    if (count >= 20) {
        noLoop();
    }
}

So, we make these changes to your code, and what happens? We see some semicircles, but not many.

To investigate why, take a look at the first line of your drawStep function:

var x =  (S[S.indexOf(active_num)-1] + active_num ) /2;

I think you are intending S[S.indexOf(active_num)-1] to be the previous number in the sequence, but this expression will not work as you expect:

  • If active_num appears somewhere else within S (this does happen: 42 is the first number to appear twice), then S[S.indexOf(active_num)-1] will return the number that was before the first occurrence of active_num, not the current occurrence.

  • If active_num doesn't occur anywhere else within S, S.indexOf(active_num) will be -1, and hence S[S.indexOf(active_num)-1] evaluates to S[-2], which is undefined.

In the first case you will get arbitrary incorrect semicircles, in the second you will get nothing.

The previous number in the sequence is in fact the last one in S, and you can get that using S[S.length-1]. So, let's replace S.indexOf(active_num) with S.length in both places it occurs in drawStep():

   var x =  (S[S.length-1] + active_num ) /2;
   var y = height / 2;
   var w = active_num - S[S.length-1];
   var h = w;

Finally, I'd suggest replacing the line:

    if (S.indexOf(active_num - count) > 0) {

with

    if (S.indexOf(active_num - count) >= 0) {

The intention of this line is to say 'if active_num - count is already in S then ...', but if active_num - count is zero, S.indexOf(active_num - count) will be zero because 0 is in S at index 0. It happens that the else block correctly handles the case where active_num - count is zero, so you don't have a bug, but it's worth being aware that S.indexOf can return 0 to indicate an element is in S.

Finally, you may want to consider scaling the drawing up to match the image you included in your question. That should just be a case of multiplying the x offset and the width and height of your semi-circular arcs by a scale factor. I'll leave that up to you.

like image 4
Luke Woodward Avatar answered Nov 11 '22 14:11

Luke Woodward