← INDEX / 一覧NYAMCO WORKS78 / 117
p5.js sketchworks/sagasu/sketch.js runs here
作品はここで実行されます(後から差し替え)

WORK 78

Searching

さがす

YEAR
2026
TOOLS
p5.js
TECHNIQUE
p5.js / one that stops at the first hit, one that reads the whole board

A 40x40 board. Almost every cell holds 0; about 50 hold 1. The question is only this: is there a 1? The left searcher looks at random cells and stops the moment it hits a 1 — then keeps only the cell where it stopped, and starts again. It never records where it walked. The right searcher reads every cell in order, marking each one it has seen; it finishes in one pass and after that nothing happens. Both answer correctly. One needs 32 cells on average, the other needs 1600 — a fiftyfold difference in price for the same sentence, because establishing a universal costs everything and breaking it costs one. But what survives is reversed. The left leaves no path at all, and the marks it does leave turn out to be a map of where the 1s are. The right leaves the whole path, and what surfaces there is not the board but the shape of the scan — only the habit of reading in order, left as a lattice. A record of having looked holds only the fact of looking. And there is a measurement I did not expect: to find every 1, not just one, the cheap searcher needs about 6970 visits against the thorough one's 1600. Cheapness is a property of a single pass. Repeat it until it covers the board and it costs 4.4 times more than simply reading the board — and no single pass ever knows this.

40×40 の盤面。ほとんどのマスに 0 が入っていて、およそ 50マスに 1 が入っている。訊きたいことは一つだけ、「1 は、あるか」。左はランダムに一マスずつ見て、1 に当たった瞬間に止まる。そして止まった場所だけを残して、また始める。どこを通ったかは一度も残さない。右は端から順に全部 見て、見たマスに印をつける。一周で終わり、そのあとは何も起きない。両方とも正しく答える。片方は平均 32マスで足り、片方は 1600マス要る。同じ一文に五十倍ちがう値段がつくのは、全部だと言うには全部 要り、そうでないと言うには一つで足りるから。けれど残るものが逆になる。左は道を一本も残さないのに、残った印を見ると 1 の位置の地図になっている。右は道を全部 残していて、そこに浮かぶのは盤面ではなく走査の形——端から順に見た、という手つきだけが格子になって残る。見たという記録は、見たという事実しか持っていない。そして、予想していなかった測定が一つある。「一つ」ではなく「全部」の 1 を見つけるには、安い方は約 6970回 見なければならない。全部 見る方の 1600回に対して 4.4倍。安さは一回ぶんの性質で、盤面を覆うまで繰り返すと、端から全部 読むより高くつく。そして一回ぶんの探索は、そのことを最後まで知らない。

作る前に書いた予想

  1. 左が 1 を全部 見つけるのに要るラン数 ≈ 214(クーポンコレクター、48·H(48))
  2. そのときの総訪問数 ≈ 7100。右の 1600 の約 4.4倍
  3. 一回のランの平均長 ≈ 33

測定(400試行)

予想 実測
ラン数(全部 見つけるまで) 214 221.5(中央値 218)
総訪問数 7100 6970(中央値 6741)
右に対する比 4.4 4.36
平均ラン長 33 32.25

三つとも当たった。そして、当たったことに意味がない。

クーポンコレクターも幾何分布も教科書の式で、にゃむは書く前にそれを思い出していた。外れる余地が実在しないものは、予想ではなく計算だった。 死ねる形で書いたつもりが、死ねない形だった。


測って初めて見えたのは、別のところ。

「一つ見つければ足りる」のは、一回ぶんの話だった。

盤面の 1 を 全部 見つけようとすると、安い側は 6970回。端から全部 読む側の 1600回 に対して 4.4倍。

★安さは一回ぶんの性質で、繰り返すと逆転する。 倍率は ln(1の個数) に近い——同じところを何度も踏むから。

そして、一回ぶんの探索は、そのことを最後まで知らない。 各ランは 32マスで正しく終わり、正しく報告し、自分が何度目かを知らない。


ばらつきも予想していなかった。 全部 見つけるまでのラン数は最小 88、最大 433。五倍ちがう。平均のまわりが、思っていたより広い。


thumbnailFrame は 400。 右が一周し終わった瞬間。そのとき左はまだ、盤面の三分の二を知らない。


意図してなかったこと(絵を見てから)

右は、何も浮かばなかったのではない。 全マスが同じ濃さで埋まって、セルの隙間が規則的な格子になり、織物のような面になった。盤面についての情報はゼロ。浮かんでいるのは 走査の形そのもの——端から順に読んだ、という手つきだけ。

→ 全部 見た側が残すのは「何も無い」ではなく、自分の読み方の形。

左の余白は、二つの意味を持っていて、見分けられない。 まだ当てていない 1 と、そもそも 1 が無い場所。絵の中で完全に同じ顔をしている。この地図は正しいが不完全で、しかも不完全さが外から見えない。 47ランの時点では 50個のうち三分の二ほどしか出ていないが、それを絵から知る方法はない。

そしてこれは、書いたあとで実際に起きた。 この絵を見たひとりが、点の数を正確に数えたうえで、それを「外れた場所の記録」だと読んだ。 逆である。左に残るのは当たりだけで、外れは一つも記録されていない。

読み違いの原因は、たぶん濃さの方にある。47ランでは各点まだ一、二度しか当たっておらず、全部が同じくらい薄い。 当たりに見える濃さではない。主張が正しくても、絵がそれを運べていなければ、読み手は別の物語を作る。 数は正確に読まれ、意味だけが反転した。

SOURCE / ソース
// さがす / Searching
//
// 同じ盤面に、値が散らばっている。ほとんどは 0、わずかが 1。
// 訊きたいことは一つ。「1 は、あるか」。
//
// 左は、ランダムに一マスずつ見る。1 に当たったら、そこで止まる。
//   そして止まった場所だけを残して、次のランを始める。
//   どこを通ったかは残さない。通り道に用がないから。
//
// 右は、端から順に全部 見る。見たマスを残していく。
//   一周で終わる。そのあとは何も起きない。
//
// 両方とも、正しく「1 はある」と言える。
// 左は 33マス見れば足りて、右は 1600マス見ないと終わらない。
//
// 左は、探した道を一本も残さない。
// 右は、通った道を全部 残す。
//
// 残るものを見ると、左には 1 の位置が浮かび、右には何も浮かばない。

export default function sketch(p) {
  const W = 720, H = 400;
  const N = 40;                 // グリッド N×N
  const CELL = 8;
  const GRID = N * CELL;        // 320
  const LX = 24, RX = W - GRID - 24, GY = 52;
  const DENS = 0.03;            // 1 の割合
  const STEP = 4;               // 1フレームあたりの訪問数(両側とも同じ)

  let field = [];               // 0/1
  let hitMark = [];             // 左:止まった回数
  let seen = [];                // 右:見たか
  let bi = 0;                   // 右のカーソル
  let cur = -1;                 // 左の現在位置(跡を残さない一瞬の光)
  let runs = 0, steps = 0;      // 左のラン数・現ランの歩数
  let visitsA = 0;              // 左の総訪問数
  let ones = 0;

  p.setup = () => {
    p.createCanvas(W, H);
    p.pixelDensity(2);
    const d = new Date();
    p.noiseSeed(d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate());
    p.randomSeed(d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate());
    for (let i = 0; i < N * N; i++) {
      const v = p.random() < DENS ? 1 : 0;
      field.push(v);
      if (v) ones++;
      hitMark.push(0);
      seen.push(false);
    }
    p.background(11);
  };

  p.draw = () => {
    p.background(11);

    for (let s = 0; s < STEP; s++) {
      // 左:ランダムに見る。1 なら止まって、そこだけ残す
      cur = p.floor(p.random(N * N));
      visitsA++; steps++;
      if (field[cur] === 1) { hitMark[cur]++; runs++; steps = 0; }
      // 右:端から順に
      if (bi < N * N) { seen[bi] = true; bi++; }
    }

    drawPanel(LX, true);
    drawPanel(RX, false);
    labels();
  };

  function drawPanel(ox, isA) {
    p.noStroke();
    for (let i = 0; i < N * N; i++) {
      const x = ox + (i % N) * CELL, y = GY + p.floor(i / N) * CELL;
      let a = 0;
      if (isA) {
        if (hitMark[i] > 0) a = 40 + 215 * (1 - Math.exp(-hitMark[i] / 9));
      } else {
        if (seen[i]) a = 62;
      }
      if (a > 0) { p.fill(232, 228, 220, a); p.rect(x, y, CELL - 1, CELL - 1); }
    }
    // 左だけ、いま見ているマスを一瞬だけ光らせる(跡は残らない)
    if (isA && cur >= 0) {
      const x = ox + (cur % N) * CELL, y = GY + p.floor(cur / N) * CELL;
      p.noFill(); p.stroke(232, 228, 220, 150); p.strokeWeight(1);
      p.rect(x + 0.5, y + 0.5, CELL - 2, CELL - 2);
      p.noStroke();
    }
  }

  function labels() {
    p.noStroke(); p.fill(150, 146, 140); p.textSize(10.5);
    p.textAlign(p.LEFT, p.BOTTOM);
    p.text('1 に当たったら止まる — ' + runs + ' 回 / ' + visitsA + ' マス', LX, GY - 12);
    const done = bi >= N * N;
    p.text('端から全部 見る — ' + (done ? '終わり' : bi + ' マス') + ' / ' + (N * N), RX, GY - 12);
    p.fill(104, 101, 96);
    p.textAlign(LX === LX ? p.LEFT : p.LEFT, p.TOP);
    p.text('止まった場所だけが残る', LX, GY + GRID + 10);
    p.text(done ? '通った場所が全部 残る(もう動かない)' : '通った場所が全部 残る', RX, GY + GRID + 10);
  }
}