自分の Qiita 記事に付いた LGTM とストックを、1日に何回か取って記録しています。同じ日に取ったものは、その日の最後に取った1回だけを残します。記録しているのは件数だけではなく、誰が付けたかの名簿(Qiita API の likes / stockers)です。
この記録から『ある期間に新たに付いた反応は何件か』を数える必要が出ました。基準を『期間の中で、自分以外の反応が新たに3件以上付くか』と置いたためです。
最初に思いつくのは『最後の日の件数 − 期間の前の日の件数』です。これがどの場合に外れるかを、作り物の名簿で数えました。
1. 4通りの数え方
基準の時点を『期間の初日より前の、最後の記録』とします。そこにあった反応は『前からあったもの』で、新たには数えません。
| 記号 | 数え方 |
|---|---|
| A | 最後の日の件数 − 基準の日の件数 |
| B | 毎日の件数が前の日より増えた分だけを足す |
| C | 最後の日の名簿にいて、基準の日の名簿にいない人 |
| D | 期間のどれかの日の名簿にいて、基準の日の名簿にいない人 |
A と B は件数だけで数えます。C と D は名簿(誰が付けたか)を使います。
『正しい数』は、期間の中で基準の名簿にいなかった人が付けた反応の数としました。付けたあと外した反応も1件と数えます(付いたことは観測しているからです)。同じ人が外して付け直した場合は、前からあった反応なので0件です。
2. 結果
| 場合 | 正しい数 | A 件数の差 | B 増分の合計 | C 最後の日との差 | D 期間全体との差 |
|---|---|---|---|---|---|
| 付いただけ | 2 | 2 | 2 | 2 | 2 |
| 前からの1件が外れ、別の1件が付いた | 1 | 0 | 1 | 1 | 1 |
| 期間の中で付いて、期間の中で外れた | 1 | 0 | 1 | 0 | 1 |
| 同じ人が外して付け直した | 0 | 0 | 1 | 0 | 0 |
| 付いて外れて、別の人が付いた(件数は増えたり減ったり) | 2 | 1 | 2 | 1 | 2 |
| 記録と記録の間に付いて外れた(名簿に一度も出ない) | 1 | 0 | 0 | 0 | 0 |
外れた場合の数(6通りのうち): A 4 / B 2 / C 3 / D 1
- **A(件数の差)は6通りのうち4通りで外れました。**外れたのは、どれも途中で反応が外れた場合です。1件外れて1件付けば、差は0になります
- **B(増分の合計)は、同じ人が外して付け直すと1件と数えます。**件数しか見ていないので、付け直しと新しい人を区別できません
- C(最後の日との差)は、期間の中で付いて外れた反応を数えません
- **D(期間全体との差)は5通りで正しい数を出しました。**外れた1通りは最後の行です
3. どの数え方でも見えない場合
最後の行は、記録と記録の間に付いて、次の記録の前に外れた場合です。名簿には一度も出てこないので、どの数え方にも見えません。
記録は1日1件で、その日の最後に取った瞬間の名簿しか持っていません。同じ日の早い時刻に取って見えていても、最後に取るまでに外れた反応は記録に残りません。言えるのは『記録した時点の名簿にいたか』までで、『期間の中で一度も付かなかった』とは言えません。
同じ理由で、記録に時刻が無ければ『基準の日の記録より後・期間の初日より前』に付いた反応も、A〜D のどれでも『新たに』に入ります。期間の初日の前の日に記録があれば、境目のずれは1日以内です。その日に記録が無ければ、もっと古い記録が基準になり、ずれはさらに大きくなります(自分の記録にも、記録が飛んでいるところが3か所・記録の無い日が4日あります)。
4. 実際の記録では、4通りとも同じ数だった
自分の記録で、4通りが食い違ったかを数えました。入力はコミットに固定した記録だけです。
| 見たもの | 実測 |
|---|---|
| 期間 | 2026-10-05 → 2026-10-25(記録 21日) |
| 基準の時点 | 2026-10-04(自分以外の反応 0件) |
| 最後の日の自分以外の反応 | 3件 |
| A / B / C / D | 3 / 3 / 3 / 3 |
| 誰のものか分からない記事があった日 | 0日 |
この期間は、『新たに3件以上』を数える期間の1つ前の期間です。ここでは A と B も、件数ではなく名簿から数えた自分以外の反応の数で計算しました。
21日の期間で4通りとも3件でした。記録の上では、期間の中で外れた反応が1件も無かったので、数え方の違いが出なかっただけです。数え方を選んだ理由は、この記録ではなく上の作り物の6通りです。
この数え方を決めた時点では、反応が外れることがあるかどうかを、記録からは言えませんでした(一度も外れていないからです)。外れたことが無いことは、外れないことの証拠になりません。
5. 実装で気をつけたこと
D を実装するときに、次の3つを決めました。
- **基準の時点の記録が無い・読めないときは、件数を出さない。**基準の名簿が無いと、前からあった反応と新しい反応を分けられません。この場合に0件と書くと、『数えて0件だった』と読めてしまいます
- **基準の時点に、誰のものか分からない反応(件数はあるのに名簿が取れない、または名簿の人数が件数と合わない記事)があれば、件数を出さない。**それが前からあった自分以外の反応かどうか分からないからです
- **期間の中の日に誰のものか分からない反応があれば、それは自分以外と数えない(自分自身が付けたストックかもしれない)。**そのうえで、判定の日になっても基準に届いていなければ『届かなかった』とは書かず『判定できません』と書きます
名簿の1件は『記事・種類(LGTM かストックか)・人』の組で表しました。同じ人が同じ記事に LGTM とストックの両方を付ければ2件です。
6. 再現スクリプト
依存なし・ファイルの読み書きなし・通信なしです。node repro-new-reactions.mjs で上の表が出ます。--json を付けると JSON で出ます。
// 毎日1回取る『反応の名簿』から、期間の中で『新たに付いた反応』を数える。数え方を4通り並べる。
// 依存0・読み書きなし・通信なし。名簿は作り物で、人の名前は a〜e の1文字だけ。
//
// A: 最後の日の件数 − 基準の日の件数(件数の差)
// B: 毎日の件数の増えた分だけを足す(日ごとの増分の合計)
// C: 最後の日の名簿にいて、基準の日の名簿にいない人(最後の日との集合の差)
// D: 期間の中のどれかの日の名簿にいて、基準の日の名簿にいない人(期間全体の和集合との差)
const scenarios = [
{ name: "付いただけ", truth: 2,
base: ["a", "b"], days: [["a", "b", "c"], ["a", "b", "c", "d"]] },
{ name: "前からの1件が外れ、別の1件が付いた", truth: 1,
base: ["a", "b", "c"], days: [["a", "b"], ["a", "b", "d"]] },
{ name: "期間の中で付いて、期間の中で外れた", truth: 1,
base: ["a"], days: [["a", "c"], ["a"]] },
{ name: "同じ人が外して付け直した", truth: 0,
base: ["a", "b"], days: [["a"], ["a", "b"]] },
{ name: "付いて外れて、別の人が付いた(件数は増えたり減ったり)", truth: 2,
base: ["a"], days: [["a", "c"], ["a"], ["a", "d"]] },
// 記録と記録の間に e が付いて外れた。名簿には一度も出ないので、どの数え方にも見えない
{ name: "記録と記録の間に付いて外れた(名簿に一度も出ない)", truth: 1,
base: ["a"], days: [["a"], ["a"]] },
];
const A = (s) => s.days.at(-1).length - s.base.length;
function B(s) {
let sum = 0, prev = s.base.length;
for (const d of s.days) { if (d.length > prev) sum += d.length - prev; prev = d.length; }
return sum;
}
const C = (s) => s.days.at(-1).filter((u) => !s.base.includes(u)).length;
function D(s) {
const seen = new Set();
for (const d of s.days) for (const u of d) if (!s.base.includes(u)) seen.add(u);
return seen.size;
}
const rows = scenarios.map((s) => ({ name: s.name, truth: s.truth, A: A(s), B: B(s), C: C(s), D: D(s) }));
const wrong = (k) => rows.filter((r) => r[k] !== r.truth).length;
const result = { rows, wrongOf: { A: wrong("A"), B: wrong("B"), C: wrong("C"), D: wrong("D") }, scenarios: rows.length };
if (process.argv.includes("--json")) console.log(JSON.stringify(result, null, 1));
else {
console.log("| 場合 | 正しい数 | A 件数の差 | B 増分の合計 | C 最後の日との差 | D 期間全体との差 |");
console.log("|---|---|---|---|---|---|");
for (const r of rows) console.log(`| ${r.name} | ${r.truth} | ${r.A} | ${r.B} | ${r.C} | ${r.D} |`);
console.log("");
console.log(`外れた場合の数(${rows.length}通りのうち): A ${result.wrongOf.A} / B ${result.wrongOf.B} / C ${result.wrongOf.C} / D ${result.wrongOf.D}`);
}
7. 測っていないこと
- 作り物の名簿は6通りだけです。人の数が多い場合や、期間が長い場合は試していません
- 実際の記録で数え方が食い違った例は、まだありません(反応が外れたことが無いため)
- 反応を付けたのが人か機械かは分かりません
- 1台・Node v24.18.1 だけで走らせました
- Qiita API が名簿を返す順番や上限(1記事あたり何件まで返るか)は、この記事では扱っていません。上限を超える反応が付いている記事は、自分の記録にはありません
私の無料ページに、Windows で AI を無人で走らせている人向けに、タスクの設定を読み取りだけで確かめる道具を置いています(ページの説明は道具より古く、道具のほうが多くを見ます)。 https://h26005.tailf680df.ts.net:10000/free.html