はてなキーワード: constとは
const NGWORDS = [
'dorawii',
'エーブイ',// イーブイ
'れめくん', '/boushi_inst',// 電気通信大学れめくん(頻出)
'/asapgoeson', '/gmhtcyznf_abc', '/fft_dareka', '/h2twi', '/kiha2228', '/kqlex1500', '/oreizmmiporin', '/pm95uq', '/reme_kun',// 電気通信大学れめくん
'自己放尿',
];
みんなも各々対策してるのかな?
utf8の文字コードで構成されたバイナリーファイルがある。このファイルの改行の数を数えよ。改行はCRのみとする。このファイルにはCR が必ず含まれており、CRで分割すればマルチスレッドで処理できる。
size_t count_cr_in_file(const std::string& path) {
std::ifstream fs(path, std::ios::binary);
size_t size = fs.tellg();
fs.seekg(0);
const size_t thread_count = std::thread::hardware_concurrency();
const size_t chunk_size = size / thread_count;
std::vector<std::future<size_t>> futures;
for (size_t t = 0; t < thread_count; t++) {
size_t start = t * chunk_size;
size_t end = (t == thread_count - 1 ? size : (t + 1) * chunk_size);
futures.push_back(std::async(std::launch::async, [start, end, &path]() {
std::ifstream fs(path, std::ios::binary);
fs.seekg(start);
size_t count = 0;
for (size_t i = start; i < end; i++) {
char c;
if (c == '\r') count++;
}
return count;
}));
}
size_t total = 0;
for (auto& f : futures) total += f.get();
return total;
}
import mmap
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
return mm.count(b'\r')
c#で書けと指示したところ、
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
{
public static long CountCRInFile(string path)
{
using var mmf = MemoryMappedFile.CreateFromFile(path, FileMode.Open, null);
using var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
long fileSize = accessor.Capacity;
int processorCount = Environment.ProcessorCount;
long chunkSize = fileSize / processorCount;
long total = 0;
object lockObj = new object();
Parallel.For(0, processorCount, i =>
{
long start = i * chunkSize;
long end = (i == processorCount - 1) ? fileSize : (i + 1) * chunkSize;
long localCount = 0;
byte b;
for (long pos = start; pos < end; pos++)
{
if (b == 0x0D) // CR
localCount++;
}
lock (lockObj)
{
total += localCount;
}
});
return total;
}
static void Main(string[] args)
{
long count = CountCRInFile(path);
Console.WriteLine($"CR count = {count}");
}
}
というコードが出てきた。
実際はcrlf,lfもあり得るんだが、そこは考慮しなくてもよく、そう考えるとaiはすごいとしか言いようがない。
https://b.hatena.ne.jp/site/anond.hatelabo.jp/?sort=eid
const NGWORDS = [ 'dorawii', 'あおやまちゃん', 'ボスマン', 'エーブイ',// イーブイ 'れめくん', '/boushi_inst',// 電気通信大学れめくん(頻出) '/asapgoeson', '/gmhtcyznf_abc', '/fft_dareka', '/h2twi', '/kiha2228', '/kqlex1500', '/oreizmmiporin', '/pm95uq', '/reme_kun',// 電気通信大学れめくん '自己放尿', ];
みんなも各々対策してるのかな?
// ==UserScript==
// @namespace http://tampermonkey.net/
// @version 0.2
// @description 本文に「dorawii」または「megalodon」が含まれる投稿を非表示にする
// @match https://anond.hatelabo.jp/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const POST_SELECTOR = '.body .section';
const KEYWORDS = ['dorawii', 'megalodon'];
const posts = document.querySelectorAll(POST_SELECTOR);
posts.forEach(post => {
const textContent = post.textContent || post.innerText;
if (KEYWORDS.some(keyword => textContent.includes(keyword))) {
post.style.display = 'none';
}
});
})();
これはdorawiiもしくはmegalodonを含む投稿を非表示にするけど、
const KEYWORDS = ['dorawii', 'megalodon'];の部分を変えたり追加すれば好きな言葉に変えられるよ
dorawiiがタイトルに「dorawii」と入れなくなったので、本文にdorawiiがある投稿も非表示にする必要が出てきました。
ただAIに聞いたんだけど、CSSでは無理でJavaScriptならできると言われました。
そのJavaScriptを下に載せます。
// ==UserScript==
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 本文に「dorawii」が含まれる投稿を非表示にする
// @match https://anond.hatelabo.jp/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// はてな匿名ダイアリーの一般的な投稿要素のクラスを仮定しています
const POST_SELECTOR = '.body .section'; // 例: .bodyクラスの子孫の.section要素
// すべての投稿要素を取得
const posts = document.querySelectorAll(POST_SELECTOR);
posts.forEach(post => {
// 投稿内の本文が含まれる要素(ここでは投稿全体を本文と見なす)のテキストを取得
const textContent = post.textContent || post.innerText;
if (textContent.includes(KEYWORD)) {
post.style.display = 'none';
}
});
})();
async function collectAllUrls(startUrl) {
const urls = [];
let nextUrl = startUrl;
while (nextUrl) {
const res = await fetch(nextUrl);
const html = await res.text();
const doc = new DOMParser().parseFromString(html, "text/html");
const links = doc.querySelectorAll("div.section > h3 > a:first-child");
urls.push(...[...links].map(link => link.href));
const nextLink = [...doc.querySelectorAll("a")].find(a => a.textContent.includes("次の25件>"));
nextUrl = nextLink ? nextLink.href : null;
console.log(nextUrl)
}
return urls;
}
(async () => {
const allUrls = await collectAllUrls(window.location.href);
console.log("総件数:", allUrls.length);
await Promise.all(allUrls.map(url =>{console.log(url);
fetch('https://b.hatena.ne.jp/dorawii_bukuma/add.edit.json', {
method: 'POST',
headers: {
},
body: new URLSearchParams({
'url': url,
'private': '0',
'comment': '[dorawii]わしが書いた',
'post_twitter': '0',
'with_status_op': '1',
'from': 'web-confirm'
})
});
} ));
console.log("全送信完了");
})();
https://b.hatena.ne.jp/site/anond.hatelabo.jp/?sort=eid
途中でブクマ数増えなくなったんだよね。待機処理つけるべきだったか。
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 https://anond.hatelabo.jp/20250823194237# -----BEGIN PGP SIGNATURE----- iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaKmbHwAKCRBwMdsubs4+ SJC0AP0Q7RDjUSe8p2aNNKV0KLhlbhnTY+kD7uuWCS8yLJILDgEA2Sm4b1496jjy C0ue64hovLwS3C4dcF5r5TBMyfRifw8= =zIYi -----END PGP SIGNATURE-----
https://profile.hatena.ne.jp/dorawii_bukuma/
はてなのサイト側で読み込まれているはずのrksトークンを生成する関数を直接叩く方法がどうしても分からず結局request処理を自分で書く方法ではなく自動でUI側の保存ボタンをクリックするという無難な方向に落ち着いた。
最初から後者の方法をとっていればもっと全然早く作れたのにというは所詮言い訳か。
とにかくスクリプトを公開しておく。
@echo off
cd /d "C:\Users\user\Documents\jsscript"
:: Nodeサーバーを別ウィンドウで起動
start /min "" node run-batch-server.js
:: Pythonサーバーを別ウィンドウで起動(hatenaserver配下)
start cmd /k "" python hatenaserver\server.py
{
"username": "",
"password": ""
}from flask import Flask, request, jsonify
import json
import os
from hatena_client import HatenaClient
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
config_path = os.path.join(os.path.dirname(__file__), 'config.json')
with open(config_path, encoding='utf-8') as f:
config = json.load(f)
@app.route('/bookmark', methods=['POST'])
def handle_bookmark():
data = request.json
url = data.get("url")
if not url:
return jsonify({"error": "Missing URL"}), 400
client = HatenaClient(config["username"], config["password"])
client.start_browser()
if not client.login():
client.quit()
return jsonify({"error": "Login failed"}), 403
success = client.add_bookmark(url)
client.quit()
return jsonify({"status": "ok" if success else "fail"})
if __name__ == "__main__":
app.run(port=12347)
// ==UserScript==
// @name 自動セルクマ送信
// @namespace tampermonkey.net/
// @version 2025-08-07
// @description try to take over the world!
// @author You
// @match anond.hatelabo.jp/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const url = location.href;
if (!/^https:\/\/anond\.hatelabo\.jp\/\d+$/.test(url)) return;
const editLink = document.querySelector('a.edit');
if (!editLink) {
// 既に編集ページなので処理をスキップ
console.log('編集リンクが存在するため、スクリプトを終了します。');
return;
}
fetch('localhost:12347/bookmark', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ url: url })
}).then(r => console.log("通知成功")).catch(e => console.error("通知失敗", e));
})();
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 https://anond.hatelabo.jp/20250821192753# -----BEGIN PGP SIGNATURE----- iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaKb0qwAKCRBwMdsubs4+ SHfiAQDcXmTHBaZ5Zzr1KI/OxZ0xl69oevOdy1FXJYwYvsmo5AD/ZPtZiO1JgTDj m+27iymlkdzIXOIGWfC82UTr1mJ7EwU= =YoV+ -----END PGP SIGNATURE-----
今時点の使えそうな Sonnet4 を使ってコード生成とか業務でやる時に Typescript は案外うまくいかないことが多い。
UIとかシンプルなものであれば結構うまくいくけど、graphql, prisma みたいなところになると、token数すごくなるし結局完成しない。
この辺りが、なんとも小骨がひっかかるから。Typescriptの型ってやっぱりあれなのかと思って調べてもらったんだ。
↓↓↓↓↓↓↓
## ソフトウェア工学から見たTypeScriptの3つの根本課題
Web上の専門的な議論や論文では、TypeScriptの課題は主に以下の3点に集約されます。これらはすべて、JavaScriptという土台との不適合性に起因するものです。
1. 不健全な型システム (Unsound Type System)
ソフトウェア工学において、型システムの**「健全性(Soundness)」**とは、「コンパイル時に型エラーがなかったプログラムは、実行時に型エラーを起こさない」という保証を指します。
TypeScriptは、この健全性を意図的に放棄しています。
設計目標の不在: TypeScriptの公式ドキュメントには「健全であること」は設計目標ではないと明記されています。これは、JavaScriptとの互換性や開発者の利便性を優先した、根本的なトレードオフです。
具体的な不健全さ:
配列の扱い: string 型の変数に (string | number) 型の配列を代入できてしまうなど、配列の扱いに不健全な部分があります。これが原因で、実行時に数値を取り出してしまい、string型のメソッドを呼び出してエラーになる、といったことが起こり得ます。
any型と型アサーション: any型の存在や、開発者がコンパイラに「これはこの型で間違いない」と強制する型アサーション(as構文)は、健全性を根本から破壊する「抜け道」です。
関数の引数(Bivariance): 関数の引数の型チェックが、他の多くの言語(反変)とは異なり、より緩いルール(双変)になっています。これも実用性を優先した結果、理論的な正しさを犠牲にしている例です。
学術的な観点では、この「不健全さ」はTypeScriptの型システムが持つ最大の弱点と見なされています。
2. 構造的部分型(Structural Typing)の罠
TypeScriptは、クラス名などによらず「構造が同じなら同じ型」とみなす構造的部分型を採用しています。これはダックタイピングが主流のJavaScript文化に合わせた賢い選択ですが、ソフトウェア工学的にはいくつかの罠があります。
意図しない互換性: 全く異なる目的で作られた二つのオブジェクトが、偶然同じ構造を持っていたために、型チェックをパスしてしまうことがあります。これにより、ドメイン(業務領域)の意図とは無関係にコードが結合してしまい、予期せぬバグを生む原因となります。
過剰なプロパティの許容: const config: { port: number } = { port: 8080, host: 'localhost' }; のように、変数経由で代入すると、型に定義されていない余分なプロパティ(host)がエラーになりません。これは仕様ですが、開発者の意図しないデータが紛れ込むことを許容してしまい、セキュリティやバグのリスクに繋がります。
これは「TypeScriptの型は、データの『形状』を記述するが、その『意味』や『文脈』を保証しない」という根本的な限界を示しています。
3. 「漏れのある抽象化(Leaky Abstraction)」としての本質
Joel Spolskyが提唱した「漏れのある抽象化の法則」に倣えば、TypeScriptはまさにその典型例です。
TypeScriptは「静的型付け」という抽象化レイヤーを提供しますが、開発者は常にその下にあるJavaScriptの泥臭い現実(undefined, null, thisの挙動など)を意識し続けなければなりません。
抽象化の不徹底: 型を書いているときも、最終的にそれがundefinedになりうることや、thisが何を指すかを常に考えなければなりません。抽象化レイヤーが、下位レイヤーの詳細を隠蔽しきれていないのです。
摩擦コスト: この「漏れ」が、これまで議論してきた「Union地獄」や「境界での型変換の手間」といった、開発上の継続的な摩擦コストを生み出しています。
## 結論
ソフトウェア工学的な観点から見ると、Web上の専門家の議論は我々の対話の結論を強く裏付けています。
TypeScriptの課題は、個別の機能の優劣ではなく、「健全性を犠牲にしてでも、JavaScriptという動的な土台との互換性を最優先した」という、その成り立ちそのものにあります。その結果として生じる「不健全さ」「構造的部分型の罠」「漏れのある抽象化」といった問題は、この言語を使い続ける限り、アーキテクトが向き合い続けなければならない、本質的なトレードオフなのです。
東京23区と周辺のいくつかの市で、東京選挙区での参政党の得票率がどんな要因と関係しているのか、軽い気持ちで回帰分析してみた。
変数の多重共線性とか処理はガバガバなので軽い気持ちで見てほしいんだが、ざっくりまとめると、「大学院卒業者の割合」が高い地域では得票率が低く、「役員の割合」が高い地域では逆に得票率が高い、という傾向がありそう。
使ったデータはNHKが出している投票所別の得票率。手入力なので誤りがあるかもしれない。
それに、東京都の人口統計と国勢調査(令和2年の)などから市区町村ごとの属性データをくっつけて、変数を一律で標準化したうえで回帰分析を行った。
都内の市区町村のうち、データが揃ってる27地域を対象にした(23区+町田・八王子・調布・西東京)。
20万人以上の市しか一部のデータが見つけられなくて、そこはごめんって感じ。
まず、説明変数を11個使って線形回帰分析をしたところ、決定係数は0.83(調整済み決定係数は0.71)だった。何を使ったかは後で。
そこから影響が特に大きそうな4変数(平均年齢、大学院卒業者割合、役員の割合、情報通信業の割合)に絞って分析し直すと、決定係数は0.73(調整済み決定係数は0.68)になった。
詳しくはこれ
国勢調査は5年に1回しかなくて、最新の結果が令和2年のだった。
4つの変数の関係を見てみると、平均年齢は他の3つの変数(大学院卒、役員、情報通信業)と負の相関を持っていた(相関係数 < -0.69)。一方、大学院卒業者の割合・役員の割合・情報通信業の割合は互いに中程度以上の正の相関(相関係数 > 0.5)を持っており、特に大学院卒と役員の間の相関係数は0.75と大きかった(いずれもピアソン相関)。
ただし、回帰係数を見ると、興味深い違いがある。大学院卒業者の割合、平均年齢、情報通信業の割合はいずれも負の係数を持っていて、これらが高いと参政党の得票率は下がる傾向がある。一方で、役員の割合は正の係数を持っていた。
| 市区町村 | 参政党得票率(NHK) | 予測値_参政党得票率 | 平均年齢(令和7年1月) | 大学院卒業者割合(令和2年国勢調査) | 役員の割合(令和2年国勢調査) | 情報通信業の割合(令和2年国勢調査) |
| 千代田区 | 9.4 | 9.6 | 42.69 | 0.088 | 0.162 | 0.115 |
| 中央区 | 9.8 | 9.3 | 42.17 | 0.075 | 0.126 | 0.135 |
| 港区 | 10.1 | 10.4 | 43.48 | 0.065 | 0.171 | 0.131 |
| 新宿区 | 9.4 | 9.5 | 44.08 | 0.052 | 0.097 | 0.129 |
| 文京区 | 7.4 | 7.6 | 43.35 | 0.097 | 0.098 | 0.118 |
| 台東区 | 10 | 10.1 | 45.59 | 0.041 | 0.109 | 0.112 |
| 墨田区 | 10.1 | 9.8 | 44.88 | 0.035 | 0.073 | 0.115 |
| 江東区 | 9 | 9.4 | 44.82 | 0.041 | 0.069 | 0.12 |
| 品川区 | 9 | 8.6 | 44.34 | 0.056 | 0.077 | 0.143 |
| 目黒区 | 9 | 9.4 | 44.88 | 0.05 | 0.109 | 0.137 |
| 大田区 | 9.9 | 9.5 | 45.67 | 0.039 | 0.069 | 0.105 |
| 世田谷区 | 9.9 | 9.4 | 45.19 | 0.047 | 0.097 | 0.128 |
| 渋谷区 | 10 | 9.7 | 44.8 | 0.054 | 0.142 | 0.152 |
| 中野区 | 9.5 | 9.3 | 44.57 | 0.038 | 0.072 | 0.141 |
| 杉並区 | 8.5 | 8.9 | 45.23 | 0.047 | 0.076 | 0.136 |
| 豊島区 | 9.6 | 9.5 | 44.05 | 0.044 | 0.081 | 0.132 |
| 北区 | 9.2 | 9.4 | 45.74 | 0.036 | 0.058 | 0.107 |
| 荒川区 | 9.4 | 9.9 | 46.23 | 0.032 | 0.071 | 0.096 |
| 板橋区 | 9.9 | 10.0 | 45.73 | 0.027 | 0.059 | 0.099 |
| 練馬区 | 10.3 | 9.6 | 45.5 | 0.034 | 0.068 | 0.113 |
| 足立区 | 10.5 | 10.7 | 46.74 | 0.017 | 0.063 | 0.073 |
| 葛飾区 | 10 | 10.4 | 46.52 | 0.02 | 0.061 | 0.083 |
| 江戸川区 | 11 | 10.7 | 45.09 | 0.021 | 0.062 | 0.085 |
| 八王子市 | 10.1 | 9.7 | 48.31 | 0.029 | 0.054 | 0.054 |
| 町田市 | 10 | 9.5 | 48.16 | 0.031 | 0.058 | 0.068 |
| 調布市 | 8.6 | 9.4 | 45.66 | 0.035 | 0.06 | 0.113 |
| 西東京市 | 9.1 | 9.5 | 46.9 | 0.028 | 0.055 | 0.102 |
雑なモデルなので話半分でね。
OLS Regression Results
==============================================================================
Dep. Variable: y R-squared: 0.730
Model: OLS Adj. R-squared: 0.680
Method: Least Squares F-statistic: 14.84
Date: Mon, 21 Jul 2025 Prob (F-statistic): 5.09e-06
Time: 07:21:02 Log-Likelihood: -20.653
No. Observations: 27 AIC: 51.31
Df Residuals: 22 BIC: 57.78
Df Model: 4
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 1.277e-15 0.111 1.15e-14 1.000 -0.230 0.230
x1 -0.5743 0.230 -2.493 0.021 -1.052 -0.096
x2 -1.3278 0.204 -6.512 0.000 -1.751 -0.905
x3 0.8670 0.174 4.973 0.000 0.505 1.229
x4 -0.5382 0.169 -3.184 0.004 -0.889 -0.188
==============================================================================
Omnibus: 2.233 Durbin-Watson: 2.170
Prob(Omnibus): 0.327 Jarque-Bera (JB): 1.169
Skew: -0.035 Prob(JB): 0.557
Kurtosis: 1.983 Cond. No. 4.48
==============================================================================
| 変数 | 回帰係数 |
| 平均年齢(令和7年1月) | -0.78 |
| 1世帯あたり人口 | -0.31 |
| 男性率(令和7年1月) | 0.07 |
| 外国人比率(令和7年1月) | -0.07 |
| 5年間外国人割合変化 | 0.27 |
| 犯罪認知割合 | -0.05 |
| 大学院卒業者/全卒業者(令和2年国勢調査) | -1.77 |
| 不詳者/全卒業者(令和2年国勢調査) | -0.51 |
| 従業上の地位:役員の割合 | 1.39 |
| 従業上の地位:自営業主の割合 | 0.09 |
| 産業区分:情報通信業の割合 | -0.53 |
| 地域 | 参政党得票率(NHK) | 予測値_参政党得票率 | 平均年齢(令和7年1月) | 1世帯あたり人口 | 男性率(令和7年1月) | 外国人比率(令和7年1月) | 5年間外国人割合変化(令和2年から7年) | 犯罪認知割合(令和6年件数/令和7年人口) | 大学院卒業者/全卒業者(令和2年国勢調査) | 不詳者/全卒業者(令和2年国勢調査) | 従業上の地位:役員の割合(令和2年国勢調査) | 従業上の地位:自営業主の割合(令和2年国勢調査) | 産業区分:情報通信業の割合(令和2年国勢調査) |
| 千代田区 | 9.4 | 9.5 | 42.69 | 1.75 | 0.50 | 0.06 | 1.22 | 0.04 | 0.09 | 0.36 | 0.16 | 0.09 | 0.12 |
| 中央区 | 9.8 | 9.8 | 42.17 | 1.76 | 0.48 | 0.07 | 1.33 | 0.01 | 0.08 | 0.28 | 0.13 | 0.08 | 0.14 |
| 港区 | 10.1 | 10.0 | 43.48 | 1.74 | 0.47 | 0.08 | 1.08 | 0.01 | 0.07 | 0.42 | 0.17 | 0.10 | 0.13 |
| 新宿区 | 9.4 | 9.0 | 44.08 | 1.52 | 0.50 | 0.14 | 1.12 | 0.02 | 0.05 | 0.39 | 0.10 | 0.09 | 0.13 |
| 文京区 | 7.4 | 7.5 | 43.35 | 1.80 | 0.48 | 0.07 | 1.32 | 0.01 | 0.10 | 0.25 | 0.10 | 0.08 | 0.12 |
| 台東区 | 10.0 | 10.3 | 45.59 | 1.58 | 0.51 | 0.09 | 1.21 | 0.01 | 0.04 | 0.36 | 0.11 | 0.09 | 0.11 |
| 墨田区 | 10.1 | 10.1 | 44.88 | 1.69 | 0.49 | 0.06 | 1.25 | 0.01 | 0.04 | 0.28 | 0.07 | 0.07 | 0.12 |
| 江東区 | 9.0 | 9.2 | 44.82 | 1.84 | 0.49 | 0.07 | 1.23 | 0.01 | 0.04 | 0.27 | 0.07 | 0.06 | 0.12 |
| 品川区 | 9.0 | 8.6 | 44.34 | 1.73 | 0.49 | 0.04 | 1.19 | 0.01 | 0.06 | 0.24 | 0.08 | 0.07 | 0.14 |
| 目黒区 | 9.0 | 9.3 | 44.88 | 1.74 | 0.47 | 0.04 | 1.19 | 0.01 | 0.05 | 0.35 | 0.11 | 0.10 | 0.14 |
| 大田区 | 9.9 | 9.7 | 45.67 | 1.77 | 0.50 | 0.04 | 1.26 | 0.01 | 0.04 | 0.23 | 0.07 | 0.07 | 0.11 |
| 世田谷区 | 9.9 | 9.3 | 45.19 | 1.84 | 0.47 | 0.03 | 1.22 | 0.01 | 0.05 | 0.30 | 0.10 | 0.10 | 0.13 |
| 渋谷区 | 10.0 | 9.9 | 44.80 | 1.61 | 0.48 | 0.06 | 1.12 | 0.02 | 0.05 | 0.34 | 0.14 | 0.12 | 0.15 |
| 中野区 | 9.5 | 9.5 | 44.57 | 1.57 | 0.51 | 0.07 | 1.20 | 0.01 | 0.04 | 0.33 | 0.07 | 0.09 | 0.14 |
| 杉並区 | 8.5 | 8.9 | 45.23 | 1.73 | 0.48 | 0.04 | 1.19 | 0.00 | 0.05 | 0.26 | 0.08 | 0.09 | 0.14 |
| 豊島区 | 9.6 | 9.5 | 44.05 | 1.57 | 0.50 | 0.12 | 1.21 | 0.01 | 0.04 | 0.34 | 0.08 | 0.09 | 0.13 |
| 北区 | 9.2 | 9.2 | 45.74 | 1.71 | 0.50 | 0.09 | 1.31 | 0.01 | 0.04 | 0.31 | 0.06 | 0.07 | 0.11 |
| 荒川区 | 9.4 | 9.6 | 46.23 | 1.77 | 0.50 | 0.11 | 1.19 | 0.01 | 0.03 | 0.29 | 0.07 | 0.08 | 0.10 |
| 板橋区 | 9.9 | 10.0 | 45.73 | 1.73 | 0.49 | 0.07 | 1.29 | 0.01 | 0.03 | 0.30 | 0.06 | 0.07 | 0.10 |
| 練馬区 | 10.3 | 9.6 | 45.50 | 1.89 | 0.48 | 0.04 | 1.22 | 0.01 | 0.03 | 0.25 | 0.07 | 0.08 | 0.11 |
| 足立区 | 10.5 | 10.6 | 46.74 | 1.84 | 0.50 | 0.06 | 1.28 | 0.01 | 0.02 | 0.31 | 0.06 | 0.08 | 0.07 |
| 葛飾区 | 10.0 | 10.5 | 46.52 | 1.86 | 0.50 | 0.06 | 1.27 | 0.01 | 0.02 | 0.27 | 0.06 | 0.08 | 0.08 |
| 江戸川区 | 11.0 | 10.8 | 45.09 | 1.93 | 0.50 | 0.07 | 1.27 | 0.01 | 0.02 | 0.26 | 0.06 | 0.07 | 0.09 |
| 八王子市 | 10.1 | 9.7 | 48.31 | 1.96 | 0.50 | 0.03 | 1.28 | 0.01 | 0.03 | 0.21 | 0.05 | 0.07 | 0.05 |
| 町田市 | 10.0 | 10.0 | 48.16 | 2.06 | 0.49 | 0.02 | 1.44 | 0.01 | 0.03 | 0.17 | 0.06 | 0.08 | 0.07 |
| 調布市 | 8.6 | 9.1 | 45.66 | 1.92 | 0.49 | 0.02 | 1.14 | 0.01 | 0.04 | 0.23 | 0.06 | 0.08 | 0.11 |
| 西東京市 | 9.1 | 9.2 | 46.90 | 2.00 | 0.49 | 0.03 | 1.15 | 0.01 | 0.03 | 0.20 | 0.06 | 0.08 | 0.10 |
OLS Regression Results
==============================================================================
Dep. Variable: y R-squared: 0.833
Model: OLS Adj. R-squared: 0.711
Method: Least Squares F-statistic: 6.803
Date: Mon, 21 Jul 2025 Prob (F-statistic): 0.000472
Time: 06:53:14 Log-Likelihood: -14.148
No. Observations: 27 AIC: 52.30
Df Residuals: 15 BIC: 67.85
Df Model: 11
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const -5.405e-15 0.106 -5.12e-14 1.000 -0.225 0.225
x1 -0.7820 0.361 -2.165 0.047 -1.552 -0.012
x2 -0.3056 0.355 -0.860 0.403 -1.063 0.452
x3 0.0671 0.270 0.248 0.807 -0.509 0.643
x4 -0.0737 0.213 -0.346 0.734 -0.527 0.379
x5 0.2652 0.168 1.579 0.135 -0.093 0.623
x6 -0.0534 0.246 -0.217 0.831 -0.578 0.472
x7 -1.7650 0.293 -6.018 0.000 -2.390 -1.140
x8 -0.5147 0.379 -1.358 0.195 -1.322 0.293
x9 1.3916 0.348 3.994 0.001 0.649
<html lang="ja"> <head> <meta charset="UTF-8"> <title>GrokのPONGゲーム</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a1a; } canvas { border: 2px solid #00ff00; background: #000; } #score { color: #00ff00; font-family: 'Courier New', monospace; font-size: 24px; position: absolute; top: 20px; width: 100%; text-align: center; } </style> </head> <body>0 : 0<canvas id="gameCanvas" width="800" height="400"></canvas> <script> const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const scoreDisplay = document.getElementById('score'); // ゲームオブジェクト const ball = { x: canvas.width / 2, y: canvas.height / 2, radius: 10, speedX: 5, speedY: 5 }; const paddleLeft = { x: 10, y: canvas.height / 2 - 50, width: 10, height: 100, speed: 8 }; const paddleRight = { x: canvas.width - 20, y: canvas.height / 2 - 50, width: 10, height: 100, speed: 8 }; let scoreLeft = 0, scoreRight = 0; // キー入力 const keys = { w: false, s: false, ArrowUp: false, ArrowDown: false }; document.addEventListener('keydown', e => { if (keys.hasOwnProperty(e.key)) keys[e.key] = true; }); document.addEventListener('keyup', e => { if (keys.hasOwnProperty(e.key)) keys[e.key] = false; }); // ゲームループ function gameLoop() { // 移動 if (keys.w && paddleLeft.y > 0) paddleLeft.y -= paddleLeft.speed; if (keys.s && paddleLeft.y < canvas.height - paddleLeft.height) paddleLeft.y += paddleLeft.speed; if (keys.ArrowUp && paddleRight.y > 0) paddleRight.y -= paddleRight.speed; if (keys.ArrowDown && paddleRight.y < canvas.height - paddleRight.height) paddleRight.y += paddleRight.speed; // ボール移動 ball.x += ball.speedX; ball.y += ball.speedY; // 壁衝突 if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) ball.speedY = -ball.speedY; // パドル衝突 if ( (ball.x - ball.radius < paddleLeft.x + paddleLeft.width && ball.y > paddleLeft.y && ball.y < paddleLeft.y + paddleLeft.height) || (ball.x + ball.radius > paddleRight.x && ball.y > paddleRight.y && ball.y < paddleRight.y + paddleRight.height) ) { ball.speedX = -ball.speedX * 1.05; // 少し加速 } // 得点 if (ball.x < 0) { scoreRight++; ballReset(); } if (ball.x > canvas.width) { scoreLeft++; ballReset(); } // 描画 ctx.fillStyle = '#000'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#00ff00'; ctx.fillRect(paddleLeft.x, paddleLeft.y, paddleLeft.width, paddleLeft.height); ctx.fillRect(paddleRight.x, paddleRight.y, paddleRight.width, paddleRight.height); ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2); ctx.fill(); scoreDisplay.textContent = `${scoreLeft} : ${scoreRight}`; requestAnimationFrame(gameLoop); } function ballReset() { ball.x = canvas.width / 2; ball.y = canvas.height / 2; ball.speedX = (Math.random() > 0.5 ? 5 : -5) * (Math.random() * 0.5 + 0.75); ball.speedY = (Math.random() * 4 - 2); } gameLoop(); </script> </body> </html>
<html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>PONG Game</title> <style> body { margin: 0; padding: 0; background-color: #000; display: flex; justify-content: center; align-items: center; min-height: 100vh; font-family: 'Courier New', monospace; color: white; } ``` .game-container { text-align: center; } canvas { border: 2px solid white; background-color: #000; } .score { font-size: 24px; margin: 20px 0; letter-spacing: 2px; } .controls { margin-top: 20px; font-size: 14px; opacity: 0.8; } .start-button { background-color: #333; color: white; border: 2px solid white; padding: 10px 20px; font-size: 16px; cursor: pointer; font-family: 'Courier New', monospace; margin: 10px; } .start-button:hover { background-color: white; color: black; } </style> ``` </head> <body> ``` <script> // Canvas要素とコンテキストの取得 const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); // ゲームの状態管理 let gameRunning = false; let animationId; // スコア要素の取得 const playerScoreElement = document.getElementById('playerScore'); const computerScoreElement = document.getElementById('computerScore'); // ゲームオブジェクトの定義 const game = { // プレイヤーのパドル(左側) playerPaddle: { x: 10, y: canvas.height / 2 - 50, width: 10, height: 100, speed: 5, upPressed: false, downPressed: false }, // コンピューターのパドル(右側) computerPaddle: { x: canvas.width - 20, y: canvas.height / 2 - 50, width: 10, height: 100, speed: 3.5, // プレイヤーより少し遅く設定 targetY: canvas.height / 2 - 50 }, // ボールの設定 ball: { x: canvas.width / 2, y: canvas.height / 2, radius: 8, speedX: 4, speedY: 3, maxSpeed: 8 }, // スコアの管理 score: { player: 0, computer: 0 } }; // キーボード入力の処理 const keys = {}; // キーが押されたときの処理 document.addEventListener('keydown', (e) => { keys[e.key.toLowerCase()] = true; // ゲームが停止中にスペースキーでゲーム開始 if (e.key === ' ' && !gameRunning) { startGame(); } }); // キーが離されたときの処理 document.addEventListener('keyup', (e) => { keys[e.key.toLowerCase()] = false; }); // パドルの移動処理 function updatePaddles() { // プレイヤーパドルの移動(W/S キーまたは矢印キー) if (keys['w'] || keys['arrowup']) { game.playerPaddle.y -= game.playerPaddle.speed; } if (keys['s'] || keys['arrowdown']) { game.playerPaddle.y += game.playerPaddle.speed; } // プレイヤーパドルの画面外移動を防ぐ if (game.playerPaddle.y < 0) { game.playerPaddle.y = 0; } if (game.playerPaddle.y > canvas.height - game.playerPaddle.height) { game.playerPaddle.y = canvas.height - game.playerPaddle.height; } // コンピューターパドルのAI処理 // ボールの位置を追跡するが、完璧ではない動きを実装 const ballCenterY = game.ball.y; const paddleCenterY = game.computerPaddle.y + game.computerPaddle.height / 2; // ボールとパドルの中心の差を計算 const difference = ballCenterY - paddleCenterY; // 反応に少し遅れを持たせる(人間らしい動き) if (Math.abs(difference) > 10) { if (difference > 0) { game.computerPaddle.y += game.computerPaddle.speed; } else { game.computerPaddle.y -= game.computerPaddle.speed; } } // コンピューターパドルの画面外移動を防ぐ if (game.computerPaddle.y < 0) { game.computerPaddle.y = 0; } if (game.computerPaddle.y > canvas.height - game.computerPaddle.height) { game.computerPaddle.y = canvas.height - game.computerPaddle.height; } } // ボールの移動と衝突判定 function updateBall() { // ボールの位置を更新 game.ball.x += game.ball.speedX; game.ball.y += game.ball.speedY; // 上下の壁との衝突判定 if (game.ball.y - game.ball.radius < 0 || game.ball.y + game.ball.radius > canvas.height) { game.ball.speedY = -game.ball.speedY; } // プレイヤーパドルとの衝突判定 if (game.ball.x - game.ball.radius < game.playerPaddle.x + game.playerPaddle.width && game.ball.x + game.ball.radius > game.playerPaddle.x && game.ball.y + game.ball.radius > game.playerPaddle.y && game.ball.y - game.ball.radius < game.playerPaddle.y + game.playerPaddle.height) { // ボールがパドルに当たった位置によって跳ね返り角度を調整 const hitPos = (game.ball.y - (game.playerPaddle.y + game.playerPaddle.height / 2)) / (game.playerPaddle.height / 2); game.ball.speedX = Math.abs(game.ball.speedX); game.ball.speedY = hitPos * 4; // ボールの速度を少し上げる(ゲームをエキサイティングに) if (Math.abs(game.ball.speedX) < game.ball.maxSpeed) { game.ball.speedX *= 1.02; } } // コンピューターパドルとの衝突判定 if (game.ball.x + game.ball.radius > game.computerPaddle.x && game.ball.x - game.ball.radius < game.computerPaddle.x + game.computerPaddle.width && game.ball.y + game.ball.radius > game.computerPaddle.y && game.ball.y - game.ball.radius < game.computerPaddle.y + game.computerPaddle.height) { // ボールがパドルに当たった位置によって跳ね返り角度を調整 const hitPos = (game.ball.y - (game.computerPaddle.y + game.computerPaddle.height / 2)) / (game.computerPaddle.height / 2); game.ball.speedX = -Math.abs(game.ball.speedX); game.ball.speedY = hitPos * 4; // ボールの速度を少し上げる if (Math.abs(game.ball.speedX) < game.ball.maxSpeed) { game.ball.speedX *= 1.02; } } // ボールが左右の壁を越えた場合(得点処理) if (game.ball.x < 0) { // コンピューターの得点 game.score.computer++; updateScore(); resetBall(); } else if (game.ball.x > canvas.width) { // プレイヤーの得点 game.score.player++; updateScore(); resetBall(); } } // ボールをリセット(得点後の処理) function resetBall() { game.ball.x = canvas.width / 2; game.ball.y = canvas.height / 2; // ランダムな方向でボールを発射 game.ball.speedX = (Math.random() > 0.5 ? 4 : -4); game.ball.speedY = (Math.random() - 0.5) * 6; } // スコア表示の更新 function updateScore() { playerScoreElement.textContent = game.score.player; computerScoreElement.textContent = game.score.computer; } // 描画処理 function draw() { // 画面をクリア ctx.fillStyle = '#000'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 中央の点線を描画 ctx.setLineDash([5, 5]); ctx.beginPath(); ctx.moveTo(canvas.width / 2, 0); ctx.lineTo(canvas.width / 2, canvas.height); ctx.strokeStyle = '#fff'; ctx.stroke(); ctx.setLineDash([]); // プレイヤーパドルを描画 ctx.fillStyle = '#fff'; ctx.fillRect(game.playerPaddle.x, game.playerPaddle.y, game.playerPaddle.width, game.playerPaddle.height); // コンピューターパドルを描画 ctx.fillRect(game.computerPaddle.x, game.computerPaddle.y, game.computerPaddle.width, game.computerPaddle.height); // ボールを描画 ctx.beginPath(); ctx.arc(game.ball.x, game.ball.y, game.ball.radius, 0, Math.PI * 2); ctx.fillStyle = '#fff'; ctx.fill(); // ゲームが停止中の場合、メッセージを表示 if (!gameRunning) { ctx.fillStyle = '#fff'; ctx.font = '20px Courier New'; ctx.textAlign = 'center'; ctx.fillText('ゲーム開始ボタンを押してください', canvas.width / 2, canvas.height / 2 + 60); } } // ゲームのメインループ function gameLoop() { if (!gameRunning) return; updatePaddles(); updateBall(); draw(); animationId = requestAnimationFrame(gameLoop); } // ゲーム開始 function startGame() { gameRunning = true; gameLoop(); } // ゲームリセット function resetGame() { gameRunning = false; if (animationId) { cancelAnimationFrame(animationId); } // スコアをリセット game.score.player = 0; game.score.computer = 0; updateScore(); // ボールとパドルの位置をリセット game.ball.x = canvas.width / 2; game.ball.y = canvas.height / 2; game.ball.speedX = 4; game.ball.speedY = 3; game.playerPaddle.y = canvas.height / 2 - 50; game.computerPaddle.y = canvas.height / 2 - 50; draw(); } // 初期描画 draw(); </script> ``` </body> </html>
ここで言う「プログラミング初級者」とはプログラミングの記述が上から下へ向かって順番に処理されること、条件分岐やループという概念があることを理解しており、RPGゲームが作れる「RPGツクール(現RPG Maker)」や学童向けプログラミング環境「Scratch」、「ナビつき! つくってわかる はじめてゲームプログラミング(ナビつく)」、ADVゲームが作れる「吉里吉里(もしくは吉里吉里2)」、過去にBASICやC、HSP、Javascriptあたりでプログラミングへ挑戦し挫折したなどなど、ある程度の「プログラマブルなロジック」構築の経験がある者を指します。
ある時、筆者はふと思いました。「生成AIはなんだかんだで膨大なテキスト情報を処理している事がキモだよなぁ」とありきたりなことを。
そして、同時にプログラミング初級者の弱点として「現在記述されているコードの管理においてテキストと実際の処理フローが脳内で一致しない」「プログラミング言語ごとに定められているルールや関数予約語の把握が困難」なのが問題とも考えました。
前述したプログラミング初級者の弱点の考え自体は車輪の再発明であり、「Scratch」や、より高度な「UML」が既に存在しており、特筆すべきことは何もありません。
しかし、「Scratch」や「UML」、なんなら「RPGツクール」や「吉里吉里」などに無い点として、現代では自然言語処理が大幅に向上した生成AIが実用の域にまで到達しつつあるのが従来とは異なる点でした。
つまり、自然言語を混ぜ込みやすいテキストベースの言語、かつ、処理を記述するとフローが視覚的に理解しやすい言語、可能であれば情報量が多くて一部の界隈で広く使われている言語があればプログラミング初級者も気軽にプログラミングできるのではないか?と発想しました。
コンピュータ(コンパイラやインタプリタなどソフトウェアを含む)が解することができる言語にはプログラミング言語以外にも様々あり、今回取り上げるのは「データ記述言語」と呼ばれるものです。
データ記述言語の中でもグラフ作成へ特化しており、特にフローチャート作成で真価を発揮する「DOT言語」というものがあります。
早速ですが、実際に手を動かしてみましょう。ちなみにDOT言語はGraphviz OnlineというWebツールがあるため別途に何かしらをインストールして環境構築する必要はありません。便利な世の中ですね。
上記のGraphviz Onlineを開くと、既に左側のDOT言語で記述された内容が、右側で作図されています。DOT言語はこのような図を作図するためのデータ記述言語です。
一旦、左側の記述をCtrl+Aで全選択をしDeleteなどで全削除し、下記の内容をコピペしてみましょう。
digraph graphname {
A -> B;
}
DOT言語の詳細な使い方は様々なWebサイトやブログ記事、Qiitaなどへ譲るとして、A - > Bの見た目から発想の転換をしてみると処理Aから処理Bという流れに見えませんか?
DOT言語は生成AIを利用する上で有利なテキストベースでありながらグラフを作成できるのがキモであり、例えばこのA -> BがA「Webページを開いたら」 → B「Hello, Worldと表示する」という風にできるのであれば処理のフローが可視化されており本当に素晴らしいことです。
ここでプログラミングの有識者は「DOT言語をUMLなどに見立てて処理を記述するのは良いが、プログラミング初心者は求めた結果を出力するロジックやアルゴリズムを発想する知見や経験値が圧倒的に足りていないのが問題ではないか?」と至極真っ当かつ反論の余地がない問題点の指摘をすると思いますが、そこで活きるのが生成AIです。
生成AIは初級者プログラマ個人のロジックやアルゴリズムの作成能力へ無関係にロジックやアルゴリズムを生成できる(可能性が少なくともある)ので、処理のフローをDOT言語で記述することにより処理フローから初級者プログラマが求めるプログラムを生成AIへ推論させ生成させるというのが狙いです。実際に試してみましょう。
digraph graphname {
A[
label = "Webページを表示する"
];
B[
label = "Webページを表示したらHello, worldとテキストを表示する"
];
A -> B;
}
プログラミングの有識者はこの時点で「なるほど、自然言語の記述をそのままDOT言語に入れ込んでしまうのか」と気付いたのではないかと思います。
このDOT言語をGoogle Gemini 2.5 Flashへ「下記のデータ記述言語のDOT言語で記述された処理フローをJavascript言語へ変換をしWebページ上へ「Hello,world」を表示したいので変換をお願いします。」と命令すると以下のコードが生成されました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World Page</title>
</head>
<body>
<div id="output"></div>
<script>
// Webページが表示されたら(DOMが完全に読み込まれたら)実行される
document.addEventListener('DOMContentLoaded', function() {
// "Webページを表示する" ステップは、このHTMLファイル自体が表示されることで達成されます。
// "Webページを表示したらHello, worldとテキストを表示する" ステップ
const outputDiv = document.getElementById('output');
outputDiv.textContent = 'Hello, world';
});
</script>
</body>
</html>
フローを記述する利点は、ロジックやアルゴリズムを発想する知見や経験値が足りなくとも、フローのステップが明確に分かれているので生成AIが処理を切り分けて推論してくれることであり、そしてプログラミング初心者自身がフローチャートを視覚で確認できるので「Aを処理したらBを処理する」と切り分けて考えやすいことです。
また、求めている結果ではなく誤った結果が生成されても、A - > B - > Cとフローを細分化していくことで生成AIの推論精度を高めていくことができるのも利点です。
より生成AIへ精度の高い推論をしてもらうために補足情報を付加するのも有用です。
digraph graphname {
A[
label = "Webページを表示する"
];
B[
label = "Webページを表示したらHello, worldとテキストを表示する",
comment = "Webページが完全に読み込まれるまで待機"
];
A -> B;
}
labelの記述内容もcommentの記述内容も生成AIが推論のための情報として利用するので誤った結果が生成されてもA - > B - > Cとフローを細分化しなくとも良い場合があります。
DOT言語を知るプログラミング有識者が「DOT言語の仕様を考えれば確かにそうだが、その発想はなかった」と言っていただけるであろうDOT言語コード例だとこういう記述方法もアリです。
digraph 増田コード {
最初の処理[
label = "Webページを表示する"
];
次の処理[
label = "Webページを表示したらHello, worldとテキストを表示する",
comment = "Webページが完全に読み込まれるまで待機"
];
最初の処理 -> 次の処理;
}
ノードの名称へ自然言語を採用することにより、例えばゲームプログラミング時に「キャラクターがジャンプする」という読んだそのままな処理のためのノード、というか一般的に言うオブジェクトを作成することが可能で、後は->で繋げて処理をさせられます。
ちなみに別のノードを作成する際に「"キャラクターがジャンプする"から継承する」の様なことをcommentなどへ記述しておくと生成AIが推論して継承します。なんならcommentなどへ「キャラクター画像にimage.gifを使用」などと記述しておくとファイルの読み込みもします。
更にDOT言語にはカスタム要素という仕様が存在しており、DOT言語の仕様で定められた予約語以外も使用が可能です。
digraph 増田コード {
最初の処理[
label = "Webページを表示する"
];
次の処理[
label = "Webページを表示したらHello, worldとテキストを表示する",
comment = "Webページが完全に読み込まれるまで待機",
font_style = "フォントを太字のボールド体、色を赤(#FF0000)とする"
];
最初の処理 -> 次の処理;
}
生成AIはカスタム要素の名称からも推論を発揮し、上記の場合であればフォントスタイルを指定していると推論をするので生成AIの推論精度を高める補足情報として機能します。
つまりこれはカスタム要素の名称として"Action"などの名称を採用すると"動作"として推論をし、"decision"ならば"条件分岐"ですし、"input"ならば"入力"ですし、"loop"ならば"繰り返し"ですし、"Type"ならば"種別"です。
より詳細に process[type="Action"] などのノードを作成してどんどん生成AIの推論精度を高めていくことが可能であり、そろそろ察してきているかと思いますが 処理[種別="動作"] と自然言語で記述しても機能します。
プログラミング有識者は更に「プログラム言語自体の予約語、例えばJavascriptを生成する事を前提にlengthを名称にすると配列を使おうとするのか?」と疑問に感じるでしょうがお察しの通りで生成AIは配列を使おうとするので、敢えて使いたいプログラム言語の機能や外部ライブラリなどがある場合は補足情報として機能する形で記述しておくと生成AIは推論へ利用します(まぁそこまで知識ある方なら該当のプログラム言語使ったほうが手っ取り早いと思いますが)。
以上をもって「生成AIを利用したプログラミング初級者向けの温故知新な提案」を終えたいと思います。
色々とツッコミどころには筆者自身が気付いていて。例えば「結局はDOT言語の仕様を覚えないといけないのでは?」とか「プログラミング初級者に任せると生成前のソースであるDOT言語コードがスパゲッティになりそうだよな」とか「面倒くせぇから普通にプログラミング覚えろや」とか理解してますし至極真っ当かつ反論の余地がないと思ってます。
今回の提案のプログラミング有識者向けの本質は「生成AIへ向いた中間言語の発掘」であり、「DOT言語ならそこそこ普及してるしプログラミング初級者でも扱えるんじゃね?」と業務中に発想したものを書き留め公開いたしました。
Chrome系ブラウザには増田を快適に閲覧するための コンパクトな増田 という古い拡張機能があったが、Chromeの更新に対応し切れておらず、既にChromeには新規インストールできなくなってしまっている。Edgeにはまだインストール可能だが、いずれ対応しなくなる可能性が高い。
そこで、「増田のトップページで、言及エントリ(返信・トラバ)を一覧から除外することで、新規エントリだけを一覧できる」という機能に絞ってコンパクトな増田を再現、ついでにいくつかのおまけ機能を付与したスタイルシート(CSS)を今年の4月に公開していたのだが、今回改めて英文スパム対策を追加したので公開する。
これを導入するには Stylus という拡張が必要で、少し気軽さには欠けるが、増田以外にも活用できるので、この機会にぜひ導入してみてほしい。拡張をインストールしたあとは、下記のコードをコピペして新規スタイルとして導入する方法もあるが、スタイルシートを公開できる userstyles.world の増田CSSページ(※毎朝9:00直後はアクセスできない) から [Install] ボタンでインストールするほうが、自動更新にも対応するので便利かもしれない。
/* トップページで言及エントリを除外 */ /* via: 最近ファーストブクマカが静か https://anond.hatelabo.jp/20250326171302 */ h1/*はてな匿名ダイアリー*/ + #intro/*名前を隠して楽しく日記。*/ + #body div.section:has(h3 > a/*■*/ + a:not(.keyword, .edit)/*anond:YYYYMMDDhhmmss*/){ display: none; } /* うっかりクリックしがちなキーワードリンクを無効に */ a.keyword{ pointer-events: none; } /* 執筆時のテキストエリアを広く */ textarea#text-body{ min-height: 50vh !important; } /* 執筆時に特殊記号のヒント(疑似要素なので選択してコピペできないのがもどかしいけど) */ p.post-submit > span.explain::after{ margin-left: 1em; padding-left: 1em; content: "特殊記号: &[&] <[<] >[>]"; background: url(/images/common/outsite-wh.gif) 0 3px no-repeat; } /* スパム対策部分は下記URLの [Install] ボタンで事前確認できます(随時更新中) */ /* https://userstyles.world/style/23028/ */
なお、このCSSを適用すると、NGワードを含むこの増田自体も、増田トップページからは消えてしまう(この増田単体の個別ページなら閲覧できる)。
念のため、PC・スマホにCSSを適用する方法の解説にもリンクしておく。
PC: 【Stylus】ウェブサイトにCSSを適用できる拡張機能。自由にカスタマイズ! | ナポリタン寿司のPC日記
https://www.naporitansushi.com/stylus/
※ StylusはFirefox版もある https://addons.mozilla.org/ja/firefox/addon/styl-us/
iPhone: MaKeoverアプリでiPhone SafariのCSSをカスタマイズ!万博パビリオン予約結果一覧を見やすくする使い方
https://gintachan.com/makeover-app-css-change-safari-how-to/
Android: スマートフォン Android版FirefoxのCSSカスタマイズ Stylus の使い方・初期設定方法
(7/21追記) また、スパムが特に多い時は、1ページまるごとスパムということもあるので、PCなら uAutoPagerize (Chrome) や weAutoPagerize (Firefox) などの拡張を使うと、自動でページが継ぎ足されて快適に読み進められる。ただし、継ぎ足ししまくるとメモリ不足などでブラウザが重くなることがあるので、そうなったら page: 20 などのページ番号をクリックしてから続きを読もう。
また、スパム対策の簡易NGワードは、下記のスクリプトを使って抽出した「直近の増田の頻出キーワードリンク上位20件」から、誤判定しそうな line と user を除いた18件を用いた。10件だと生き残る英文スパムがあったので20件にしたが、それでもわずかに洩れはある。しかし日本語による真っ当な(?)増田の直近の誤判定はなかった。はてなキーワードのリンクだけを対象にしているので、URLにはこれらのキーワードが入っていても大丈夫だ。ただし、スパムのトレンドが変われば話は変わってくるかもしれないし、過去や未来の増田の誤判定は当然あるだろう。気になる人は前掲のCSSを行単位で編集してほしい。
// AutoPagerizeでまとまった数のページを読み込ませた後に実行するとよい。 (function(){ const keywords = []; // はてなキーワードの集計 document.querySelectorAll('a.keyword').forEach(a => { // 4文字未満は誤検出の可能性が高まるので除外 if(a.textContent.length < 4) return; let index = keywords.findIndex(k => k.keyword === a.textContent); if(index >= 0) keywords[index].count += 1; else keywords.push({keyword: a.textContent, count: 1}); }); keywords.sort((a, b) => a.count < b.count); // ランキング配列の出力 console.log(keywords); // CSS埋め込み用に上位キーワードのみをURIエンコードして出力 console.log(keywords.slice(0, 20).map(k => encodeURIComponent(k.keyword)).join('\n')); })();
anond:20250326171302 ←元はこの増田がきっかけでした。
anond:20250701194328 ←キーワード判定に踏み切る後押しとなりました。
一度投稿したうえで別タブを開いてプログラム的(fetch)に送信してその別タブが閉じられる仕組み。
// ==UserScript==
// @name PGP未署名検出と別タブ自動編集
// @namespace http://tampermonkey.net/
// @version 1.0
// @description PGP署名がない投稿を自動編集ページへ誘導
// @match https://anond.hatelabo.jp/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM.openInTab
// ==/UserScript==
(function () {
'use strict';
const body = document.getElementById('entry-page');
if (!body) return;
const titleText = document.title;
if (!titleText.includes('dorawii')) return;
const pgpRegex = /BEGIN.*PGP(?: SIGNED MESSAGE| SIGNATURE)?/;
const preElements = document.querySelectorAll('div.body pre');
let hasPgpSignature = false;
for (const pre of preElements) {
if (pgpRegex.test(pre.textContent)) {
hasPgpSignature = true;
break;
}
}
if (hasPgpSignature) return;
const editLink = document.querySelector('a.edit');
const childTab = GM.openInTab(editLink.href, { active: false, insert: true, setParent: true });
})();
// ==UserScript==
// @name 編集ページ処理と自動送信・閉じ
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 編集ページで署名処理と送信、タブ自動閉じ
// @match https://anond.hatelabo.jp/dorawii_31/edit?id=*
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant GM_notification
// @connect localhost
// ==/UserScript==
(async function () {
'use strict';
const shouldRun = await GM_getValue('open-tab-for-edit', '0');
const textareaId = 'text-body';
const textarea = document.getElementById(textareaId);
if (!textarea) return;
const content = textarea.value;
const pgpSignatureRegex = /-----BEGIN PGP SIGNED MESSAGE-----[\s\S]+?-----BEGIN PGP SIGNATURE-----[\s\S]+?-----END PGP SIGNATURE-----/;
if (pgpSignatureRegex.test(content)) {
console.log('[PGPスクリプト] 署名が検出されたためそのまま送信します');
return;
}
const httpRequest = (url, data) => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: url,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: `value=${encodeURIComponent(data)}`,
onload: function (response) {
resolve(response.responseText);
},
onerror: function (error) {
reject(error);
}
});
});
};
// textarea の値を取得
// 1. 現在のページのURLからURLオブジェクトを作成
const currentUrl = new URL(window.location.href);
// 2. ベースとなる部分 (例: "https://anond.hatelabo.jp") を取得
const origin = currentUrl.origin;
// 3. 'id' パラメータの値 (例: "20250610184705") を取得
const idValue = currentUrl.searchParams.get('id');
// 4. ベース部分とIDを結合して、目的のURL文字列を生成
// idValueが取得できた場合のみ実行する
let newUrl = null;
if (idValue) {
newUrl = `${origin}/${idValue}`;
}
// 5. 生成されたURLを変数に代入し、コンソールに出力して確認
console.log(newUrl);
const valueToSend = newUrl;
try {
const signatureText = await httpRequest('http://localhost:12345/run-batch', valueToSend);
console.log('バッチ応答:', signatureText);
if (!signatureText.includes('BEGIN PGP SIGNED MESSAGE')) {
alert('PGP署名がクリップボードに見つかりませんでした。');
return;
}
const newText = content.replace(/\s*$/, '') + '\n' + signatureText + '\n';
textarea.value = newText;
console.log('[PGPスクリプト] 署名を貼り付けました。送信を再開します。');
const form = document.forms.edit;
const newForm = form.cloneNode(true);
form.replaceWith(newForm);
newForm.addEventListener('submit', async (e) => {
e.preventDefault(); // HTML標準のsubmitをキャンセル
const bodyText = textarea?.value || '';
// reCAPTCHA トークンの取得
const recaptchaToken = await new Promise((resolve) => {
grecaptcha.enterprise.ready(() => {
grecaptcha.enterprise.execute('hoge', { action: 'EDIT' })
.then(resolve);
});
});
// POSTするデータの構築
const formData = new FormData(newForm);
formData.set('body', bodyText);
formData.set('recaptcha_token', recaptchaToken);
formData.set('edit', '1');
try {
const response = await fetch(newForm.action, {
method: 'POST',
body: formData,
credentials: 'same-origin'
});
if (response.ok) {
console.log('送信成功');
window.close();
} else {
console.error('送信失敗', response.status);
}
} catch (err) {
console.error('送信中にエラーが発生', err);
}
});
// プログラム的に送信トリガー
newForm.dispatchEvent(new Event('submit', { bubbles: true }));
} catch (e) {
console.error('バッチ呼び出し失敗:', e);
}
})();
const http = require('http'); const { exec } = require('child_process'); const querystring = require('querystring'); const server = http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/ping') { res.writeHead(200); res.end('pong'); } else if (req.method === 'POST' && req.url === '/run-batch') { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { const parsed = querystring.parse(body); const value = parsed.value || 'default'; // 値を引数としてバッチに渡す exec(`C:\\Users\\hoge\\Desktop\\makesign.bat "${value}"`, { encoding: 'utf8' }, (err, stdout, stderr) => { if (err) { res.writeHead(500); res.end('Error executing batch: ' + stderr); } else { res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end(stdout.trim()); } }); }); } else { res.writeHead(404); res.end('Not found'); } }); server.listen(12345, () => { console.log('Batch server running at http://localhost:12345/'); });
@echo off setlocal enabledelayedexpansion :: 署名するファイル名 set "infile=%~1" set outfile=%TEMP%\pgp_output.asc :: 以前の出力があれば削除 if exist "%outfile%" del "%outfile%" :signloop :: AutoHotkeyでパスフレーズ入力(gpgがパスワード要求するダイアログが出た場合に備える) start "" /b "C:\Users\hoge\Documents\AutoHotkey\autopass.ahk" :: PGPクリア署名を作成 echo %infile% | gpg --yes --clearsign --output "%outfile%" :: 署名が成功していればループを抜ける if exist "%outfile%" ( goto postprocess ) else ( timeout /t 1 > nul goto signloop ) :postprocess powershell -nologo -command ^ "$header = '>|'; $footer = '|<'; $body = Get-Content '%outfile%' -Raw; Write-Output ($header + \"`r`n\" + $body + $footer)" powershell -nologo -command ^ "$header = '>|'; $footer = '|<'; $body = Get-Content 'signed.asc' -Raw; Set-Clipboard -Value ($header + \"`r`n\" + $body + $footer)" endlocal exit /b
#Persistent #SingleInstance ignore SetTitleMatchMode, 2 WinWaitActive, pinentry SendInput password Sleep 100 SendInput {Enter} ExitApp
動けばいいという考えで作っているので余分なコードも含んでいるかもしれない。
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 https://anond.hatelabo.jp/20250613185036 -----BEGIN PGP SIGNATURE----- iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaEv1FQAKCRBwMdsubs4+ SHHkAQDUOLgBcdji2T6MJ7h/vlMdFfGlWAzNdXijjE1gIuEPywEAiMNMZqhrMmtl c7UqRuggNJ/UTa5xTIcKp622+7jJQQg= =Lgkl -----END PGP SIGNATURE-----
ようやく(ほぼ)すべてが自動化された。
あとはローカルサーバーの起動をスタートアップに設定する(方法をAIに聞いて指示に従う)だけの消化試合。
署名時要求してくるパスワードを自動入力するahkファイルはドキュメントのAutoHotkey配下に置いた。
バッチファイル(make.sign.bat)はデスクトップに置いた。
#Persistent #SingleInstance ignore SetTitleMatchMode, 2 WinWaitActive, pinentry SendInput お前のパスワード Sleep 100 SendInput {Enter} ExitApp
// run-batch-server.js const http = require('http'); const { exec } = require('child_process'); const server = http.createServer((req, res) => { if (req.url === '/ping') { res.writeHead(200); res.end('pong'); } else if (req.url === '/run-batch') { exec('C:\\Users\\you\\Desktop\\makesign.bat', (err) => { res.writeHead(200); res.end(err ? 'Error' : 'OK'); }) ; } else { res.writeHead(404); res.end('Not found'); } }); server.listen(12345, () => { console.log('Batch server running at http://localhost:12345/'); });
@echo off setlocal enabledelayedexpansion :: ミリ秒単位のUTC時刻を取得 for /f %%a in ('powershell -nologo -command "[int64]::Parse((Get-Date).ToUniversalTime().ToString('yyyyMMddHHmmssfff'))"') do set timestamp=%%a :: 署名するファイル名 set infile=%TEMP%\pgp_input.txt set outfile=%TEMP%\pgp_output.asc :: 以前の出力があれば削除 if exist "%outfile%" del "%outfile%" :: タイムスタンプを原文として保存 echo %timestamp% > "%infile%" :signloop :: AutoHotkeyでパスフレーズ入力(gpgがパスワード要求するダイアログが出た場合に備える) start "" /b "C:\Users\infini\Documents\AutoHotkey\autopass.ahk" :: PGPクリア署名を作成 gpg --yes --clearsign --output "%outfile%" "%infile%" :: 署名が成功していればループを抜ける if exist "%outfile%" ( echo [INFO] 署名成功 goto postprocess ) else ( echo [WARN] 署名失敗、再試行します… timeout /t 1 > nul goto signloop ) :postprocess :: PowerShellで余計な改行なしに |< をつけてクリップボードにコピー powershell -nologo -command ^ "$header = '>|'; $footer = '|<'; $body = Get-Content '%outfile%' -Raw; Set-Clipboard -Value ($header + \"`r`n\" + $body + $footer)" echo Done. signed.asc created and clipboard updated (no extra blank line). endlocal exit /b
// ==UserScript== // @name PGP署名自動付加スクリプト(GM_xmlhttpRequest版) // @namespace http://tampermonkey.net/ // @version 1.0 // @description 投稿前にPGP署名を付けてから送信(fetch未使用) // @match https://anond.hatelabo.jp/dorawii_31/edit* // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @grant GM_notification // / @connect localhost // ==/UserScript== (function () { 'use strict'; const submitId = 'submit-button'; const textareaId = 'text-body'; const localServer = 'http://localhost:12345/run-batch'; const pgpSignatureRegex = /-----BEGIN PGP SIGNED MESSAGE-----[\s\S]+?-----BEGIN PGP SIGNATURE-----[\s\S]+?-----END PGP SIGNATURE-----/; const httpRequest = (url) => { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, onload: function (response) { resolve(response.responseText); }, onerror: function (error) { reject(error); } }); }); }; const interceptClick = () => { const btn = document.getElementById(submitId); if (!btn || btn.dataset.pgpIntercepted === 'true') return; btn.dataset.pgpIntercepted = 'true'; btn.addEventListener('click', async function (e) { const textarea = document.getElementById(textareaId); if (!textarea) return; const content = textarea.value; if (pgpSignatureRegex.test(content)) { console.log('[PGPスクリプト] 署名が検出されたためそのまま送信します'); return; } e.preventDefault(); e.stopImmediatePropagation(); console.log('[PGPスクリプト] 署名が見つからないため処理を停止し、署名を取得します'); try { await httpRequest(localServer); // バッチ実行 const signatureText = await navigator.clipboard.readText(); if (!signatureText.includes('BEGIN PGP SIGNED MESSAGE')) { alert('PGP署名がクリップボードに見つかりませんでした。'); return; } const newText = content.replace(/\s*$/, '') + '\n' + signatureText + '\n'; textarea.value = newText; console.log('[PGPスクリプト] 署名を貼り付けました。送信を再開します。'); btn.click(); // イベント再発火 } catch (err) { alert('PGP署名の取得または貼り付けに失敗しました。\n' + err); } }, true); }; window.addEventListener('load', () => { setTimeout(interceptClick, 1000); }); })();
プロミスメソッドとか全然まだ理解してなくてそのなかに関数代入したその関数にオブジェクトのプロパティにresponseを?いやまあそのあたりのコードが示すデータの流れが全然理解できないような人間でもここまでできちゃった。
AIすごいなと思うよ。そして思うのは今後重要になってくるのは文法とか自体に詳しいことじゃなくて、そのプログラムの処理内容を指示できるシステムエンジニア的な言語化能力のほうじゃないかなと思った。
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 20250609111559680 -----BEGIN PGP SIGNATURE----- iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaEbCbwAKCRBwMdsubs4+ SLueAPwOv7PBk4voAe5qlcCEvs/PJhmKc5QAb/1R43JMQFuDZgD/UTPEKsL/PhK9 jFGv2HDXK1dVjLNwvosgX9uYJh5xxwY= =qiOE -----END PGP SIGNATURE-----
ちなみに
JSで0インデックスがヘダーのストリングのアレイのアレイからなるテーブルのカラムを別アレイのカラムインフォオブジェクトのディスプレイナンバーに合わせて並び替えて名前をnameからdisplayNameの値に入れ替えるコード
だと
想定される入力
['id', 'name', 'age'], // ヘッダー
['1', 'Alice', '30'],
['2', 'Bob', '25'],
];
const columnInfo = [
{ name: 'name', displayName: '名前', displayNumber: 0 },
{ name: 'age', displayName: '年齢', displayNumber: 1 },
{ name: 'id', displayName: 'ID', displayNumber: 2 }
];
[
['Alice', '30', '1'],
['Bob', '25', '2']
]
function reorderAndRenameTable(table, columnInfo) {
const nameToIndex = header.reduce((acc, name, idx) => {
return acc;
}, {});
const sortedColumns = [...columnInfo].sort((a, b) => a.displayNumber - b.displayNumber);
// 新しいヘッダーを displayName に置き換え
const newHeader = sortedColumns.map(col => col.displayName);
// 各行の値も同じ順で並び替える
const newBody = body.map(row => {
return sortedColumns.map(col => row[nameToIndex[col.name]]);
});
return [newHeader, ...newBody];
}
ここまで数秒なのでまあこれくらいだと書くよりは早い
id を手動でコピーしてくるのが面倒だったから、削除ボタンが画面に出るようにした
押すと即削除
const rkm = "(トークン的なもの)" const user = "(ユーザ名)" const delmasda = (id) => fetch(`https://anond.hatelabo.jp/${user}/edit`, { "headers": { "content-type": "application/x-www-form-urlencoded", }, "referrer": "https://anond.hatelabo.jp/", "referrerPolicy": "origin", "body": new URLSearchParams({ "rkm": rkm, "mode": "confirm", "id": id, "delete": "削除する" }).toString(), "method": "POST", }) for (const sec of document.querySelectorAll(".section")) { const id = sec.querySelector("a").href.match(/92;/(92;d{14})/)[1] const delbtn = document.createElement("button") delbtn.textContent = "削除" delbtn.onclick = async () => { await delmasda(id) sec.remove() } sec.querySelector(".edit").after(delbtn) }
増田の削除をひとつひとつ詳細画面開いてするのが面倒だからまとめて消すスクリプトやで
const rkm = "(トークン的なもの)" const ids = ["(削除する増田ID)"] const user = "(ユーザー名)" for (const id of ids) fetch(`https://anond.hatelabo.jp/${user}/edit`, { "headers": { "content-type": "application/x-www-form-urlencoded", }, "referrer": "https://anond.hatelabo.jp/", "referrerPolicy": "origin", "body": new URLSearchParams({ "rkm": rkm, "mode": "confirm", "id": id, "delete": "削除する" }).toString(), "method": "POST", })
増田で 3 分以上投稿されない期間があるのか気になったから調べた
直近の 1 日だとこれだけあった
2025-03-22 00:14 -- 2025-03-22 00:18 2025-03-22 00:10 -- 2025-03-22 00:14 2025-03-21 07:56 -- 2025-03-21 08:00 2025-03-21 07:50 -- 2025-03-21 07:56 2025-03-21 07:44 -- 2025-03-21 07:48 2025-03-21 07:28 -- 2025-03-21 07:32 2025-03-21 06:58 -- 2025-03-21 07:03 2025-03-21 06:45 -- 2025-03-21 06:54 2025-03-21 06:32 -- 2025-03-21 06:37 2025-03-21 05:56 -- 2025-03-21 06:04 2025-03-21 05:51 -- 2025-03-21 05:56 2025-03-21 05:34 -- 2025-03-21 05:38 2025-03-21 05:30 -- 2025-03-21 05:34 2025-03-21 05:00 -- 2025-03-21 05:09 2025-03-21 04:56 -- 2025-03-21 05:00 2025-03-21 04:45 -- 2025-03-21 04:50 2025-03-21 04:09 -- 2025-03-21 04:13 2025-03-21 03:41 -- 2025-03-21 03:45 2025-03-21 03:29 -- 2025-03-21 03:39 2025-03-21 03:03 -- 2025-03-21 03:07 2025-03-21 02:56 -- 2025-03-21 03:02 2025-03-21 02:44 -- 2025-03-21 02:48 2025-03-21 02:33 -- 2025-03-21 02:37 2025-03-21 02:21 -- 2025-03-21 02:27 2025-03-21 02:14 -- 2025-03-21 02:19
秒はみてないから 00:01:01 - 00:03:59 はほぼ 3 分だけど 2 分扱いだし、 00:01:59 - 00:04:00 はほぼ 2 分だけど 3 分扱いになるくらいの誤差はある
日によって違うだろうし、曜日の影響も大きそうだから 1 ヶ月分くらい調査しようかと思ったけど、
増田の量が思いの外多すぎて 1 日分だけでも 100 ページ以上取得しないといけなかった
件数だと 2500 以上
一応取得に使ったコードも載せとく
import { setTimeout } from "node:timers/promises" import { Browser } from "happy-dom" const getTimestamps = async function* () { const browser = new Browser() const page = browser.newPage() try { for (let num = 1; ; num++) { await setTimeout(3000) await page.goto(`https://anond.hatelabo.jp/?page=${num}`) const days = page.mainFrame.document.querySelectorAll(".day") for (const day of days) { const date = day.querySelector("h2 .date").textContent.trim() for (const footer of day.querySelectorAll(".sectionfooter")) { const time = footer.textContent.match(/\d{2}:\d{2}/)[0] yield `${date} ${time}` } } } } finally { await page.close() await browser.close() } } const diff = (a, b) => { return new Date(b + ":00") - new Date(a + ":00") } let prev = null for await (const datetime of getTimestamps()) { if (prev &amp;&amp; diff(datetime, prev) > 1000 * 60 * 3) { console.log(datetime, prev) } prev = datetime }
基本は空いても 5 分程度であり、最大でも 10 分となっている
1Mトークン以上かかったわ
以下chatgptによるremixjsの例
import { useEffect, useState } from "react";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
export const loader = async () => {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return json({ initialData: data });
};
export default function Index() {
const { initialData } = useLoaderData();
const [data, setData] = useState(initialData);
useEffect(() => {
const fetchData = async () => {
const response = await fetch("http://api.com/get");
const newData = await response.json();
setData(newData);
};
fetchData();
}, []);
if (initialData.value === null || data.value === null) {
return (
);
}
return (
<h1>Remix: SSR + クライアント処理</h1>
);
}
javascript:(()=>{const%20loc=document.querySelector('link[rel="canonical"]')||location;const%20urlStr=(loc.href.startsWith('about:reader'))?decodeURIComponent(loc.href.slice(17)):loc.href;const%20url=new%20URL(urlStr);let%20s=url.search;s=(s.startsWith('?page=')||s.startsWith('?P='))?"":s;const%20w=window.open('https://b.hatena.ne.jp/entry?url='+encodeURIComponent(url.origin+url.pathname+s));w.document.close();})();
https://gakuzzzz.github.io/slides/for_loop_to_higher_order_functions/#1
これはオブジェクト指向でも同じでみんな「バグを減らすため」にいろんなパラダイムに挑戦してる
それ以外のHowだとかWhatだとかオブジェクトで世界を表すだとかどうでもいい
for文よりmapとかfilterの方がなぜバグが少ないか、というと「余計な操作が入りにくいから」
特にletで宣言してるような書き換え可能な変数っていうのはバグの温床
例でも挙がってるようなProductのpriceの書き換えでもfor文にするとどこかにletな変数を置かないといけない
そんでletな変数っていうのはうっかり消してしまったりうっかり書き換えてしまっても気付くことができない
だからconstで固めて何かしらのリスト処理をするときはmapなりfilterなりを使ってconstに固め直す(か、そのまま使う)
逆に言うとそういう処理がないならfor文使っても全然構わない
この書き換え不可能な変数を作るっていうのはCだとかC++だとかJavaだとかの頃からずーっと一緒でとにかく固めておきたい変数はfinal宣言して書き換えさせない
そうしないと、めちゃくちゃ分かりづらいバグが混入して無意味に1週間とか過ごすことになる
Product.priceを直接書き換えるのはいいの?っていう疑問があるかもしれないが
「そこまでせんでも致命的にはならん」
っていう感じで型のチェックだけするのがTypeScript
とにかくプログラミングに関する規則でHowだとかWhatだとかそういうフワフワしたこと言い出したら要注意
地球は宇宙空間を動いているのだから、地球の進行方向と垂直方向では光の速さが変わるだろう。そう考えて実験してみたところ、どちらの速さも変わらなかった。つまり、どんな系でも光の速さは一定であるらしい。
これを式にするとこうなる。
光の速さをc, 時刻 t の間に光の進む距離を x として
x/t = c
式変形すると
ここで一旦休憩。座標系を回転させても'棒の長さは一定'という式を考えてみよう
x^2 + y^2 = const
かんたんのため z 方向は考えない
この時座系を回転させる式を行列で書くと
こうなる。(心の目で読んで欲しい)
という式を思い出すと
x'^2 + y'^2 = x^2 + y^2 = const
上の'棒の式'とは符号が逆だね。こんなときはsin cos ではなく sinh cosh を使う。
cosθ = ((exp iθ) + (exp -iθ))/2
sinθ = ((exp iθ) - (exp -iθ))/2
coshθ = ((exp θ) + (exp -θ))/2
sinhθ = ((exp θ) - (exp -θ))/2
計算すると
cosh^2 - sinh^2 = 1 になるのがわかると思う。
cos^2 + sin^2 = 1 とは符号が逆になってるね
光の速さが系を変換しても変わらないという式を行列で書くと
こうなる。 これがローレンツ変換。
棒の長さが一定、つまり空間回転は空間方向 (x,y,z)しか混ぜないけれど、
光のはやさが一定、つまりローレンツ変換は 時間と空間 (t, x ) を混ぜているでしょ?
速さ v で進むロケットを考えてみよう。
v=x/t
だ。
一方、ロケットには美加子さんが乗っていてその携帯電話の表示では地球を発ってから T時間後である。
Tを計算してみよう。
先程のローレンツ変換の式に代入すると
ここで x = ct を使ったよ。最後に cosh で全体を纏めると
になる。
ここまで誤魔化していたけど、cosh はロケットの速さ v で決まるパラメータで
1/cosh = \sqrt{1-(v/c)^2}
なんだ。天下りで申し訳ないけど、増田では式も図も書けないので導出は勘弁して欲しい
とにかくまとめると
T = t \sqrt{1-(v/c)^2}
だね。ロケットの速度 v は光速度以下なので T < t になる。
地上で待つ昇くんが大学生になっても美加子さんが中学生のままなのはこんなワケだね
v が大きくなるほど時間の遅れは大きくなるよ
AIインフルエンサーたちはChatGPTが世に出る前は何を投稿していたのか?(1/1)の続き
このTwitterアカウントは、主にYouTube、SEO、競馬、アニメなどの話題について呟いており、自身の活動や興味関心について発信しています。
このアカウントは、日々の米国株や暗号通貨の市場動向、特にテクノロジー関連株やビットコインの価格変動についてツイートしています。
「ゼロコロナ政策再び。 $AAPL の生産に影響が懸念され大きく下落。更に経済混乱が想定され、株価はSP500全業種で
一方で年末商戦売上堅調な滑り出しで $AMZN
暗号資産レンディングBlockFiが経営破綻、暗号通貨も軒並み下落。
チャート上、なんとか踏み止まるか? 」
このアカウントは、AI、特にプロマネAIや量子AIに関する話題や、仕事効率化、プログラミング、そして最新の技術トレンドについて呟いています。
"プロマネAIの実証実験を開始しました!量子AI×NotionによるプロマネAIの実証実験を開始 https://prtimes.jp/main/html/rd/p/000000003.000082094.html… via @PRTIMES_JP"
このアカウントは、主にOpenAIの最新言語モデル「text-davinci-003」の進化と、そのモデルを用いた英語学習ツールやサービスについて呟いています。特に、英語学習におけるAI活用とその重要性を強調しています。
"朝起きたら世界がまた変わっていた(笑)。@OpenAI が最新モデル「text-davinci-003」を発表。主な特徴は、①より明確で、説得力のある文章が書ける②より複雑な指示にも対応③より長い形式のコンテンツが生成可 1月に"InstructGPT"が出た時も感動したけどそれを遥かに上回る進化、です。"
このアカウントは、主にAI、特に画像生成AIに関する話題を呟いています。特にStable Diffusionのバージョンアップや使い方について多くのツイートをしています。
ここにリストアップした人たち以外もみましたが、分類すると3種類のアカウントがいて
またIDを変更してログがヒットしない人は含まれていません(IDから特定できるけどそこまでやらなかった)
javascript:(function() {
const text = Array.from(document.querySelectorAll('[data-testid="tweetText"]')).map(s => s.textContent.trim()).join('\n');
const textarea = document.createElement('textarea');
document.body.appendChild(textarea);
try {
document.execCommand('copy');
console.log('結果がクリップボードにコピーされました!');
} catch (err) {
console.error('クリップボードへのコピーに失敗しました:', err);
}
document.body.removeChild(textarea);
})();