Recaman 序列的可视化

Visualization of the Recaman Sequence

我看过 Numberphile 制作的关于 Recaman 序列的视频。如果你不知道算法你可以看看这个 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/

我用 Processing 和 p5.js 编写了一个小软件来可视化序列。我的算法制定了定义下一跳的步骤,然后我尝试从前一点到新点画一个半圆。我的问题是当前半圆在绘制下一个时消失了。我希望所有的半圆都保持可见。

这是 CodePen 的 link,您可以在其中看到我的代码和输出: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>

我希望我的输出看起来像这样:

解决问题的第一步:

function setup() {
  createCanvas(600, 400);
  noLoop();
}

默认情况下 draw 用于绘制动画,它会被重复调用,这就是您看到闪烁形状的原因。 noLoop() 导致 draw 只被调用一次。

即使进行了此更改,我也无法使您的代码正常工作。

虽然draw在每一帧都被连续调用,但setup只在启动时调用一次。

仅在启动时创建一次canvas并仅清除一次背景。这将导致半圆连续添加到 canvas,而不清除前几帧的半圆:

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

function draw() {

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

另外你的算法有问题。当你搜索active_num的索引时,你可能在数组S中找不到active_num,因为它还没有添加,但会在下一个循环中添加。请参阅 step,其中 active_num 在函数开头添加到 S,但稍后递增。

在函数drawStep中你想读取数组的最后一个元素,你可以通过:

var prev_num = S[count-1];

下一个arc的相关值是数组Sactive_num的最后一个元素。

要解决这个问题,您可以像这样更改代码:

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);
}

注意,你可以完全跳过函数draw中的循环,所以你可以更好地"see" "animation":

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

你可以通过frameRate:

手动设置每秒帧数
function setup() {
    createCanvas(600, 400);
    background(50, 50, 50);
    frameRate(20);
}

如果缩放半圆的大小,结果可能如下所示:

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);
}

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>

首先,正如其他人指出的那样,您希望避免要求 p5.js 每次迭代都从头开始重新绘制 canvas。当我们这样做的时候,我们会将帧率降低到一些慢的水平(每秒 1 帧)以防止浏览器变得疯狂。因此,对 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);
}

接下来,让我们去掉重复调用 draw()drawStep() 的循环。 p5.js 已经在重复调用 draw(),我们没有必要重复调用这些函数。让我们也让动画在一定步数后停止:

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

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

那么,我们对您的代码进行了这些更改,结果如何?我们看到了一些半圆,但不是很多。

要调查原因,请查看 drawStep 函数的第一行:

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

我认为您打算 S[S.indexOf(active_num)-1] 成为序列中的前一个数字,但是这个表达式不会像您期望的那样工作:

  • 如果 active_num 出现在 S 中的其他地方(确实会发生这种情况:42 是第一个出现两次的数字),那么 S[S.indexOf(active_num)-1] 将 return 第一次出现 active_num 之前的数字,而不是当前出现的数字。

  • 如果 active_num 没有出现在 S 中的其他任何地方,S.indexOf(active_num) 将是 -1,因此 S[S.indexOf(active_num)-1] 计算到 S[-2],即 undefined.

在第一种情况下你会得到任意不正确的半圆,在第二种情况下你什么也得不到。

序列中的前一个数字实际上是 S 中的最后一个数字,您可以使用 S[S.length-1] 得到它。因此,让我们在 drawStep():

中出现的两个地方用 S.length 替换 S.indexOf(active_num)
   var x =  (S[S.length-1] + active_num ) /2;
   var y = height / 2;
   var w = active_num - S[S.length-1];
   var h = w;

最后,我建议更换行:

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

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

这行的目的是说'if active_num - count is already in S then ...',但是如果active_num - count为零,S.indexOf(active_num - count)将为零,因为0S中在索引 0 处。else 块碰巧正确处理了 active_num - count 为零的情况,因此您没有错误,但值得一提的是 S.indexOf 可以 return 0 表示元素在 S.

最后,您可能需要考虑将绘图放大以匹配您在问题中包含的图像。这应该只是将 x 偏移量和半圆弧的宽度和高度乘以比例因子的情况。我会把它留给你。