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
的相关值是数组S
和active_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)
将为零,因为0
在S
中在索引 0 处。else
块碰巧正确处理了 active_num - count
为零的情况,因此您没有错误,但值得一提的是 S.indexOf
可以 return 0
表示元素在 S
.
最后,您可能需要考虑将绘图放大以匹配您在问题中包含的图像。这应该只是将 x
偏移量和半圆弧的宽度和高度乘以比例因子的情况。我会把它留给你。
我看过 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
的相关值是数组S
和active_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)
将为零,因为0
在S
中在索引 0 处。else
块碰巧正确处理了 active_num - count
为零的情况,因此您没有错误,但值得一提的是 S.indexOf
可以 return 0
表示元素在 S
.
最后,您可能需要考虑将绘图放大以匹配您在问题中包含的图像。这应该只是将 x
偏移量和半圆弧的宽度和高度乘以比例因子的情况。我会把它留给你。