Movatterモバイル変換


[0]ホーム

URL:


はてラボはてな匿名ダイアリー
ようこそ ゲスト さんログインユーザー登録

「const」を含む日記RSS

はてなキーワード:constとは

次の25件>

2025-10-21

「dorawii」を含む投稿非表示にするJavaScript

dorawiiがタイトルに「dorawii」と入れなくなったので、本文にdorawiiがある投稿非表示にする必要が出てきました。

ただAIに聞いたんだけど、CSSでは無理でJavaScriptならできると言われました。

そのJavaScriptを下に載せます

// ==UserScript==

// @nameはてな匿名ダイアリー特定ワード投稿非表示

// @namespacehttp://tampermonkey.net/

// @version 0.1

// @description 本文に「dorawii」が含まれ投稿非表示にする

// @matchhttps://anond.hatelabo.jp/*

// @grant none

// ==/UserScript==

(function() {

'use strict';

//投稿全体を囲む要素のセレクタに置き換えてください

//はてな匿名ダイアリー一般的投稿要素のクラス仮定しています

const POST_SELECTOR = '.body .section'; // 例: .bodyクラスの子孫の.section要素

//非表示にしたいキーワード

constKEYWORD = 'dorawii';

// すべての投稿要素を取得

const posts = document.querySelectorAll(POST_SELECTOR);

posts.forEach(post => {

//投稿内の本文が含まれる要素(ここでは投稿全体を本文と見なす)のテキストを取得

const textContent = post.textContent || post.innerText;

//キーワードが含まれいるかチェック

if (textContent.includes(KEYWORD)) {

//キーワードが含まれていれば非表示にする

post.style.display = 'none';

}

});

})();

自分iPhoneなので、Makeoverというアプリを入れて、JSの部分に上のを貼り付けてます

なんか複雑だし、もっと良い方法があるなら教えてください!

なんでdorawiiのためにこんなことしなきゃいけないんだよ!迷惑千万

Permalink |記事への反応(1) | 18:30

このエントリーをはてなブックマークに追加ツイートシェア

2025-08-23

dorawii@執筆依頼募集中

やべえ。爆速ブクマされるプログラムになった。

async function collectAllUrls(startUrl) {
consturls = [];
let nextUrl = startUrl;

while (nextUrl) {
const res = await fetch(nextUrl);
consthtml = await res.text();
constdoc = new DOMParser().parseFromString(html, "text/html");

constlinks =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)
}

returnurls;
}

(async () => {
constallUrls = 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: newURLSearchParams({

'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

↑膨大な数のブクマが19:34分前後登録になってるだろ?

途中でブクマ数増えなくなったんだよね。待機処理つけるべきだったか

-----BEGINPGP SIGNEDMESSAGE-----Hash: SHA512https://anond.hatelabo.jp/20250823194237# -----BEGINPGP SIGNATURE-----iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaKmbHwAKCRBwMdsubs4+SJC0AP0Q7RDjUSe8p2aNNKV0KLhlbhnTY+kD7uuWCS8yLJILDgEA2Sm4b1496jjyC0ue64hovLwS3C4dcF5r5TBMyfRifw8==zIYi-----ENDPGP SIGNATURE-----

Permalink |記事への反応(0) | 19:42

このエントリーをはてなブックマークに追加ツイートシェア

2025-08-21

dorawii@執筆依頼募集中

自動ブクマするローカルサーバーとかの構成を作った。

ブクマには↓のサブアカ使用

https://profile.hatena.ne.jp/dorawii_bukuma/

はてなサイト側で読み込まれているはずのrksトークンを生成する関数を直接叩く方法がどうしても分からず結局request処理を自分で書く方法ではなく自動UI側の保存ボタンクリックするという無難な方向に落ち着いた。

最初から後者方法をとっていればもっと全然早く作れたのにというは所詮言い訳か。

とにかくスクリプトを公開しておく。

start-server.bat

@echo off
cd /d "C:\Users\user\Documents\jsscript"

:: Nodeサーバーを別ウィンドウで起動
start /min "" noderun-batch-server.js

::Pythonサーバーを別ウィンドウで起動(hatenaserver配下
start cmd /k ""python hatenaserver\server.py

以降はjsscript直下に配置

config.json

{
"username": "",
"password": ""
}
server.py

from flask import Flask, request,jsonify
importjson
importos
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')
withopen(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 noturl:
returnjsonify({"error": "MissingURL"}), 400

client = HatenaClient(config["username"],config["password"])
client.start_browser()

if notclient.login():
client.quit()
returnjsonify({"error": "Login failed"}),403

success =client.add_bookmark(url)
client.quit()

returnjsonify({"status": "ok" if success else "fail"})

if __name__ == "__main__":
app.run(port=12347)

あとはグリモンユーザスクリプトとして書くやつ

// ==UserScript==
// @name自動セルクマ送信
// @namespace tampermonkey.net/
// @version 2025-08-07
// @descriptiontry totakeoverthe world!
// @authorYou
// @matchanond.hatelabo.jp/*
// @grant none
// ==/UserScript==

(function () {
'use strict';

consturl = 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));
})();
-----BEGINPGP SIGNEDMESSAGE-----Hash: SHA512https://anond.hatelabo.jp/20250821192753# -----BEGINPGP SIGNATURE-----iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaKb0qwAKCRBwMdsubs4+SHfiAQDcXmTHBaZ5Zzr1KI/OxZ0xl69oevOdy1FXJYwYvsmo5AD/ZPtZiO1JgTDjm+27iymlkdzIXOIGWfC82UTr1mJ7EwU==YoV+-----ENDPGP SIGNATURE-----

Permalink |記事への反応(1) | 19:27

このエントリーをはてなブックマークに追加ツイートシェア

2025-07-24

LLMとTypescriptって実は相性悪いよねって話

今時点の使えそうな 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文化に合わせた賢い選択ですが、ソフトウェア工学的にはいくつかの罠があります

意図しない互換性: 全く異なる目的で作られた二つのオブジェクトが、偶然同じ構造を持っていたために、型チェックをパスしてしまうことがあります。これにより、ドメイン業務領域)の意図とは無関係コードが結合してしまい、予期せぬバグを生む原因となります

過剰なプロパティの許容:constconfig: { 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という動的な土台との互換性を最優先した」という、その成り立ちそのものにあります。その結果として生じる「不健全さ」「構造的部分型の罠」「漏れのある抽象化」といった問題は、この言語を使い続ける限り、アーキテクトが向き合い続けなければならない、本質的なトレードオフなのです。

Permalink |記事への反応(0) | 23:52

このエントリーをはてなブックマークに追加ツイートシェア

2025-07-21

軽い気持ち東京選挙区参政党得票率を線形回帰分析してみた

東京23区と周辺のいくつかの市で、東京選挙区での参政党の得票率がどんな要因と関係しているのか、軽い気持ち回帰分析してみた。

変数の多重共線性とか処理はガバガバなので軽い気持ちで見てほしいんだが、ざっくりまとめると、「大学院卒業者の割合」が高い地域では得票率が低く、「役員割合」が高い地域では逆に得票率が高い、という傾向がありそう。

使ったデータNHKが出している投票所別の得票率。手入力なので誤りがあるかもしれない。

それに、東京都人口統計国勢調査(令和2年の)などから市区町村ごとの属性データをくっつけて、変数を一律で標準化したうえで回帰分析を行った。

都内市区町村のうち、データが揃ってる27地域対象にした(23区町田八王子調布西東京)。

20万人以上の市しか一部のデータが見つけられなくて、そこはごめんって感じ。

ざっくり結果

まず、説明変数11個使って線形回帰分析をしたところ、決定係数は0.83(調整済み決定係数は0.71)だった。何を使ったかは後で。

そこから影響が特に大きそうな4変数(平均年齢、大学院卒業割合役員割合情報通信業割合)に絞って分析し直すと、決定係数は0.73(調整済み決定係数は0.68)になった。

詳しくはこれ

国勢調査は5年に1回しかなくて、最新の結果が令和2年のだった。

でこの4変数回帰係数の絶対値が大きい順に並べる。

  1. 大学院卒業者の割合(-1.30)
  2. 役員割合(+0.87)
  3. 平均年齢(-0.57)
  4. 情報通信業割合(-0.54)

4つの変数関係を見てみると、平均年齢は他の3つの変数大学院卒、役員情報通信業)と負の相関を持っていた(相関係数 < -0.69)。一方、大学院卒業者の割合役員割合情報通信業割合は互いに中程度以上の正の相関(相関係数> 0.5)を持っており、特に大学院卒と役員の間の相関係数は0.75と大きかった(いずれもピアソン相関)。

ただし、回帰係数を見ると、興味深い違いがある。大学院卒業者の割合、平均年齢、情報通信業割合はいずれも負の係数を持っていて、これらが高いと参政党の得票率は下がる傾向がある。一方で、役員割合は正の係数を持っていた。

得票率と予測値の表
市区町村参政党得票率(NHK予測値_参政党得票率 平均年齢(令和7年1月大学院卒業割合(令和2年国勢調査役員割合(令和2年国勢調査情報通信業割合(令和2年国勢調査
千代田区9.49.6 42.69 0.088 0.162 0.115
中央区9.89.3 42.17 0.075 0.126 0.135
港区10.110.4 43.48 0.065 0.171 0.131
新宿区9.49.5 44.08 0.052 0.097 0.129
文京区 7.4 7.6 43.35 0.097 0.098 0.118
台東区1010.1 45.59 0.041 0.109 0.112
墨田区10.19.8 44.88 0.035 0.073 0.115
江東区 99.4 44.82 0.041 0.069 0.12
品川区 9 8.6 44.34 0.056 0.077 0.143
目黒区 99.4 44.88 0.05 0.109 0.137
大田区9.99.5 45.67 0.039 0.069 0.105
世田谷区9.99.4 45.19 0.047 0.097 0.128
渋谷区109.7 44.8 0.054 0.142 0.152
中野区9.59.3 44.57 0.038 0.072 0.141
杉並区 8.5 8.9 45.23 0.047 0.076 0.136
豊島区9.69.5 44.05 0.044 0.081 0.132
北区9.29.4 45.74 0.036 0.058 0.107
荒川区9.49.9 46.23 0.032 0.071 0.096
板橋区9.910.0 45.73 0.027 0.059 0.099
練馬区10.39.6 45.5 0.034 0.068 0.113
足立区10.510.7 46.74 0.017 0.063 0.073
葛飾区1010.4 46.52 0.02 0.061 0.083
江戸川区1110.7 45.09 0.021 0.062 0.085
八王子10.19.7 48.31 0.029 0.054 0.054
町田109.5 48.16 0.031 0.058 0.068
調布 8.69.4 45.66 0.035 0.06 0.113
西東京9.19.5 46.9 0.028 0.055 0.102

感想

雑なモデルなので話半分でね。

データの中身とか、もうちょい詳しく書いとく


出典

分析に使ったデータの出典はこんな感じ。


変数

使用した11個の変数はこんな感じ。


結果についてももうちょい詳しく

statsmodels.api.OLSの結果

OLS Regression Results                            ==============================================================================Dep. Variable:                      y   R-squared:                       0.730Model:OLS   Adj. R-squared:                  0.680Method:                 Least Squares   F-statistic:                     14.84Date:                Mon, 21 Jul2025   Prob (F-statistic):           5.09e-06Time:                        07:21:02Log-Likelihood:                -20.653No. Observations:                  27AIC:                             51.31Df Residuals:                      22BIC:                             57.78DfModel:                           4                                         Covariance Type:            nonrobust                                         ==============================================================================                 coefstd err          tP>|t|      [0.025      0.975]------------------------------------------------------------------------------const       1.277e-15      0.111   1.15e-14      1.000      -0.230       0.230x1            -0.5743      0.230     -2.493      0.021      -1.052      -0.096x2            -1.3278      0.204     -6.512      0.000      -1.751      -0.905x3             0.8670      0.174      4.973      0.000       0.505       1.229x4            -0.5382      0.169     -3.184      0.004      -0.889      -0.188==============================================================================Omnibus:                        2.233   Durbin-Watson:                   2.170Prob(Omnibus):                  0.327   Jarque-Bera (JB):                1.169Skew:                          -0.035   Prob(JB):                        0.557Kurtosis:                       1.983   Cond. No.                         4.48==============================================================================
説明変数11個でのデータと結果

変数回帰係数
平均年齢(令和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.49.5 42.69 1.75 0.50 0.06 1.22 0.04 0.09 0.36 0.16 0.09 0.12
中央区9.89.8 42.17 1.76 0.48 0.07 1.33 0.01 0.08 0.28 0.13 0.08 0.14
港区10.110.0 43.48 1.74 0.47 0.08 1.08 0.01 0.07 0.42 0.17 0.10 0.13
新宿区9.49.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.010.3 45.59 1.58 0.51 0.09 1.21 0.01 0.04 0.36 0.11 0.09 0.11
墨田区10.110.1 44.88 1.69 0.49 0.06 1.25 0.01 0.04 0.28 0.07 0.07 0.12
江東区9.09.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.09.3 44.88 1.74 0.47 0.04 1.19 0.01 0.05 0.35 0.11 0.10 0.14
大田区9.99.7 45.67 1.77 0.50 0.04 1.26 0.01 0.04 0.23 0.07 0.07 0.11
世田谷区9.99.3 45.19 1.84 0.47 0.03 1.22 0.01 0.05 0.30 0.10 0.10 0.13
渋谷区10.09.9 44.80 1.61 0.48 0.06 1.12 0.02 0.05 0.34 0.14 0.12 0.15
中野区9.59.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.69.5 44.05 1.57 0.50 0.12 1.21 0.01 0.04 0.34 0.08 0.09 0.13
北区9.29.2 45.74 1.71 0.50 0.09 1.31 0.01 0.04 0.31 0.06 0.07 0.11
荒川区9.49.6 46.23 1.77 0.50 0.11 1.19 0.01 0.03 0.29 0.07 0.08 0.10
板橋区9.910.0 45.73 1.73 0.49 0.07 1.29 0.01 0.03 0.30 0.06 0.07 0.10
練馬区10.39.6 45.50 1.89 0.48 0.04 1.22 0.01 0.03 0.25 0.07 0.08 0.11
足立区10.510.6 46.74 1.84 0.50 0.06 1.28 0.01 0.02 0.31 0.06 0.08 0.07
葛飾区10.010.5 46.52 1.86 0.50 0.06 1.27 0.01 0.02 0.27 0.06 0.08 0.08
江戸川区11.010.8 45.09 1.93 0.50 0.07 1.27 0.01 0.02 0.26 0.06 0.07 0.09
八王子10.19.7 48.31 1.96 0.50 0.03 1.28 0.01 0.03 0.21 0.05 0.07 0.05
町田10.010.0 48.162.06 0.49 0.02 1.44 0.01 0.03 0.17 0.06 0.08 0.07
調布 8.69.1 45.66 1.92 0.49 0.02 1.14 0.01 0.04 0.23 0.06 0.08 0.11
西東京9.19.2 46.902.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.833Model:OLS   Adj. R-squared:                  0.711Method:                 Least Squares   F-statistic:                     6.803Date:                Mon, 21 Jul2025   Prob (F-statistic):           0.000472Time:                        06:53:14Log-Likelihood:                -14.148No. Observations:                  27AIC:                             52.30Df Residuals:                      15BIC:                             67.85DfModel:11                                      Covariance Type:            nonrobust                                      ==============================================================================                 coefstd err          tP>|t|      [0.025      0.975]------------------------------------------------------------------------------const      -5.405e-15      0.106  -5.12e-14      1.000      -0.225       0.225x1            -0.7820      0.361     -2.165      0.047      -1.552      -0.012x2            -0.3056      0.355     -0.860      0.403      -1.063       0.452x3             0.0671      0.270      0.248      0.807      -0.509       0.643x4            -0.0737      0.213     -0.346      0.734      -0.527       0.379x5             0.2652      0.168      1.579      0.135      -0.093       0.623x6            -0.0534      0.246     -0.217      0.831      -0.578       0.472x7            -1.7650      0.293     -6.018      0.000      -2.390      -1.140x8            -0.5147      0.379     -1.358      0.195      -1.322       0.293x9             1.3916      0.348      3.994      0.001       0.649

Permalink |記事への反応(1) | 19:19

このエントリーをはてなブックマークに追加ツイートシェア

2025-07-06

Grokが作ってくれたやつ

<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
<canvasid="gameCanvas" width="800" height="400"></canvas> <script>constcanvas = document.getElementById('gameCanvas');const ctx =canvas.getContext('2d');constscoreDisplay = 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 }; letscoreLeft = 0,scoreRight = 0; //キー入力constkeys = { 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>

 

 

https://anond.hatelabo.jp/20250706011306#

Permalink |記事への反応(1) | 01:18

このエントリーをはてなブックマークに追加ツイートシェア

Claudeが作ってくれたやつ

<html lang="ja"><head>    <meta charset="UTF-8">    <metaname="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 solidwhite;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 solidwhite;padding:10px20px;        font-size: 16px;        cursor: pointer;        font-family: 'Courier New', monospace;margin:10px;    }        .start-button:hover {background-color:white;color: black;    }</style>```</head><body>
<canvasid="gameCanvas" width="800" height="400"></canvas>
<button class="start-button">ゲーム開始</button> <button class="start-button">リセット</button>

W/Sキーまたは ↑/↓ 矢印キーパドル操作
```<script> //Canvas要素とコンテキストの取得constcanvas = document.getElementById('gameCanvas');const ctx =canvas.getContext('2d'); //ゲーム状態管理 let gameRunning =false; let animationId; //スコア要素の取得const playerScoreElement = document.getElementById('playerScore');constcomputerScoreElement = 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 } }; //キーボード入力の処理constkeys = {}; //キーが押されたときの処理 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; //ボールパドルの中心の差を計算constdifference = 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; } // 描画処理 functiondraw() { // 画面をクリア 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>

 

 

https://anond.hatelabo.jp/20250706011306#

Permalink |記事への反応(0) | 01:14

このエントリーをはてなブックマークに追加ツイートシェア

2025-07-05

生成AIを利用したプログラミング初級者向けの温故知新提案

はじめに

ここで言う「プログラミング初級者」とはプログラミング記述が上から下へ向かって順番に処理されること、条件分岐ループという概念があることを理解しており、RPGゲームが作れる「RPGツクール(現RPG Maker)」や学童向けプログラミング環境Scratch」、「ナビつき! つくってわかる はじめてゲームプログラミング(ナビつく)」、ADVゲームが作れる「吉里吉里(もしくは吉里吉里2)」、過去BASICやC、HSPJavascriptあたりでプログラミングへ挑戦し挫折したなどなど、ある程度の「プログラマブルロジック」構築の経験がある者を指します。

前日談(初級者は読まなくて良いです)

ある時、筆者はふと思いました。「生成AIはなんだかんだで膨大なテキスト情報を処理している事がキモだよなぁ」とありきたりなことを。

そして、同時にプログラミング初級者の弱点として「現在記述されているコード管理においてテキストと実際の処理フロー脳内で一致しない」「プログラミング言語ごとに定められているルール関数予約語の把握が困難」なのが問題とも考えました。

前述したプログラミング初級者の弱点の考え自体車輪の再発明であり、「Scratch」や、より高度な「UML」が既に存在しており、特筆すべきことは何もありません。

しかし、「Scratch」や「UML」、なんなら「RPGツクール」や「吉里吉里」などに無い点として、現代では自然言語処理が大幅に向上した生成AI実用の域にまで到達しつつあるのが従来とは異なる点でした。

まり自然言語を混ぜ込みやすテキストベース言語、かつ、処理を記述するとフロー視覚的に理解やす言語可能であれば情報量が多くて一部の界隈で広く使われている言語があればプログラミング初級者も気軽にプログラミングできるのではないか?と発想しました。

そこで前述の条件を満たす1つの言語へ目を付けました。

本題

コンピュータ(コンパイラインタプリタなどソフトウェアを含む)が解することができる言語にはプログラミング言語以外にも様々あり、今回取り上げるのは「データ記述言語」と呼ばれるものです。

データ記述言語の中でもグラフ作成へ特化しており、特にフローチャート作成で真価を発揮する「DOT言語というものがあります

早速ですが、実際に手を動かしてみましょう。ちなみにDOT言語Graphviz OnlineというWebツールがあるため別途に何かしらをインストールして環境構築する必要はありません。便利な世の中ですね。

上記Graphviz Onlineを開くと、既に左側のDOT言語記述された内容が、右側で作図されています。DOT言語はこのような図を作図するためのデータ記述言語です。

一旦、左側の記述をCtrl+Aで全選択をしDeleteなどで全削除し、下記の内容をコピペしてみましょう。

digraph graphname {

A -> B;

}

一瞬で○に囲まれたAとBが繋がった図が作成されました。

DOT言語の詳細な使い方は様々なWebサイトやブログ記事Qiitaなどへ譲るとして、A - > Bの見た目から発想の転換をしてみると処理Aから処理Bという流れに見えませんか?

DOT言語は生成AIを利用する上で有利なテキストベースでありながらグラフ作成できるのがキモであり、例えばこのA -> BがA「Webページを開いたら」 → B「Hello, Worldと表示する」という風にできるのであれば処理のフロー可視化されており本当に素晴らしいことです。

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.5Flash「下記のデータ記述言語のDOT言語記述された処理フローJavascript言語へ変換をしWebページ上へ「Hello,world」を表示したいので変換をお願いします。」命令すると以下のコードが生成されました。

<!DOCTYPEhtml>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <metaname="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello World Page</title>
</head>
<body>
    <divid="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言語ならそこそこ普及してるしプログラミング初級者でも扱えるんじゃね?」と業務中に発想したものを書き留め公開いたしました。

何かプログラミング有識者の皆さんからより良い発想があれば参考にしたいと考えていますのでよろしくお願いいたします。以上。

Permalink |記事への反応(36) | 19:36

このエントリーをはてなブックマークに追加ツイートシェア

2025-07-01

[増田しぐさ]増田CSSを紹介する記事(英文スパム対策付き)

Chromeブラウザには増田を快適に閲覧するためのコンパクトな増田 という古い拡張機能があったが、Chrome更新対応し切れておらず、既にChromeには新規インストールできなくなってしまっている。Edgeにはまだインストール可能だが、いずれ対応しなくなる可能性が高い。

そこで、「増田トップページで、言及エントリ(返信・トラバ)を一覧から除外することで、新規エントリだけを一覧できる」という機能に絞ってコンパクト増田再現、ついでにいくつかのおまけ機能付与したスタイルシート(CSS)を今年の4月に公開していたのだが、今回改めて英文スパム対策を追加したので公開する。

これを導入するにはStylus という拡張必要で、少し気軽さには欠けるが、増田以外にも活用できるので、この機会にぜひ導入してみてほしい。拡張インストールしたあとは、下記のコードコピペして新規スタイルとして導入する方法もあるが、スタイルシートを公開できるuserstyles.world の増田CSSページ(※毎朝9:00直後はアクセスできない)から [Install]ボタンインストールするほうが、自動更新にも対応するので便利かもしれない。

増田CSS (7/20:増田文字数制限のため、スパム対策部分は省略しました)

/*トップページ言及エントリを除外 *//*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: "特殊記号: &[&#38;] <[&#60;] >[&#62;]";background:url(/images/common/outsite-wh.gif) 0 3px no-repeat;}/*スパム対策部分は下記URLの [Install]ボタンで事前確認できます(随時更新中) *//*https://userstyles.world/style/23028/ */

なお、このCSS適用すると、NGワードを含むこの増田自体も、増田トップページからは消えてしまう(この増田単体の個別ページなら閲覧できる)。

PCスマホ向けの導入方法

念のため、PCスマホCSS適用する方法解説にもリンクしておく。

PC: 【StylusウェブサイトCSS適用できる拡張機能自由カスタマイズ! |ナポリタン寿司PC日記

https://www.naporitansushi.com/stylus/

iPhone: MaKeoverアプリiPhoneSafariCSSカスタマイズ万博パビリオン予約結果一覧を見やすくする使い方

https://gintachan.com/makeover-app-css-change-safari-how-to/

Android:スマートフォンAndroidFirefoxCSSカスタマイズStylus の使い方・初期設定方法

https://skypenguin.net/2025/06/21/post-109209/

(7/21追記) また、スパム特に多い時は、1ページまるごとスパムということもあるので、PCならuAutoPagerize (Chrome)weAutoPagerize (Firefox) などの拡張を使うと、自動でページが継ぎ足されて快適に読み進められる。ただし、継ぎ足ししまくるとメモリ不足などでブラウザが重くなることがあるので、そうなったら page:20 などのページ番号をクリックしてから続きを読もう。

(参考)増田の頻出キーワードリンク上位20抽出JavaScript

また、スパム対策の簡易NGワードは、下記のスクリプトを使って抽出した「直近の増田の頻出キーワードリンク上位20件」から誤判定しそうなlineuser を除いた18件を用いた。10件だと生き残る英文スパムがあったので20件にしたが、それでもわずかに洩れはある。しか日本語による真っ当な(?)増田の直近の誤判定はなかった。はてなキーワードリンクだけを対象にしているので、URLにはこれらのキーワードが入っていても大丈夫だ。ただし、スパムトレンドが変われば話は変わってくるかもしれないし、過去未来増田誤判定は当然あるだろう。気になる人は前掲のCSSを行単位編集してほしい。

//AutoPagerizeでまとまった数のページを読み込ませた後に実行するとよい。(function(){constkeywords = [];  //はてなキーワードの集計  document.querySelectorAll('a.keyword').forEach(a => {    // 4文字未満は誤検出の可能性が高まるので除外    if(a.textContent.length < 4) return;    letindex =keywords.findIndex(k => k.keyword === a.textContent);    if(index >= 0)keywords[index].count += 1;    elsekeywords.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キーワード判定に踏み切る後押しとなりました。

Permalink |記事への反応(12) | 23:56

このエントリーをはてなブックマークに追加ツイートシェア

2025-06-13

我が名はサイボーグdorawii

パーマリンク署名対象にするより堅牢自動化を作れた。

一度投稿したうえで別タブを開いてプログラム的(fetch)に送信してその別タブが閉じられる仕組み。

改めてスクリプト配布しちゃる

最初投稿してエントリページに移動した親タブ側のjsコード
// ==UserScript==      // @namePGP署名検出と別タブ自動編集      // @namespacehttp://tampermonkey.net/      // @version      1.0      // @descriptionPGP署名がない投稿自動編集ページへ誘導      // @matchhttps://anond.hatelabo.jp/*      // @grantGM_setValue      // @grantGM_getValue      // @grantGM.openInTab      // ==/UserScript==      (function () {        'use strict';constbody = document.getElementById('entry-page');        if (!body) return;consttitleText = document.title;        if (!titleText.includes('dorawii')) return;constpgpRegex = /BEGIN.*PGP(?: SIGNEDMESSAGE| 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 });      })();
親タブから開かれる編集ページの子タブのjsコード
 // ==UserScript==      // @name編集ページ処理と自動送信・閉じ      // @namespacehttp://tampermonkey.net/      // @version      1.0      // @description編集ページで署名処理と送信、タブ自動閉じ      // @matchhttps://anond.hatelabo.jp/dorawii_31/edit?id=*      // @grantGM_getValue      // @grantGM_xmlhttpRequest      // @grantGM_setClipboard      // @grantGM_notification      // @connectlocalhost      // ==/UserScript==      (async function () {        'use strict';const shouldRun = awaitGM_getValue('open-tab-for-edit', '0');consttextareaId = 'text-body';consttextarea = document.getElementById(textareaId);        if (!textarea) return;const content =textarea.value;constpgpSignatureRegex = /-----BEGINPGP SIGNEDMESSAGE-----[\s\S]+?-----BEGINPGP SIGNATURE-----[\s\S]+?-----ENDPGP SIGNATURE-----/;        if (pgpSignatureRegex.test(content)) {console.log('[PGPスクリプト]署名が検出されたためそのまま送信します');          return;        }consthttpRequest = (url, data) =&gt; {          return newPromise((resolve,reject) =&gt; {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 = newURL(window.location.href);        // 2.ベースとなる部分 (例: "https://anond.hatelabo.jp") を取得constorigin = currentUrl.origin;        // 3. 'id'パラメータの値 (例: "20250610184705") を取得constidValue = currentUrl.searchParams.get('id');        // 4.ベース部分とIDを結合して、目的URL文字列を生成        //idValueが取得できた場合のみ実行する        let newUrl = null;        if (idValue) {          newUrl = `${origin}/${idValue}`;        }        // 5. 生成されたURL変数に代入し、コンソールに出力して確認console.log(newUrl);constvalueToSend = newUrl;try {const signatureText = awaithttpRequest('http://localhost:12345/run-batch',valueToSend);console.log('バッチ応答:', signatureText);          if (!signatureText.includes('BEGINPGP SIGNEDMESSAGE')) {            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) =&gt; {            e.preventDefault(); //HTML標準のsubmitをキャンセルconstbodyText =textarea?.value || '';            //reCAPTCHAトークンの取得constrecaptchaToken = await newPromise((resolve) =&gt; {              grecaptcha.enterprise.ready(() =&gt; {                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 {constresponse = 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);        }      })();
node.jsで動かすローカルサーバーコード
consthttp =require('http');const { exec } =require('child_process');const querystring =require('querystring');const server =http.createServer((req, res) =&gt; {  if (req.method === 'GET' &amp;&amp; req.url === '/ping') {    res.writeHead(200);    res.end('pong');  } else if (req.method === 'POST' &amp;&amp; req.url === '/run-batch') {    letbody = '';    req.on('data', chunk =&gt; {body += chunk.toString();    });    req.on('end', () =&gt; {constparsed = querystring.parse(body);constvalue =parsed.value || 'default';      // 値を引数としてバッチに渡す      exec(`C:\\Users\\hoge\\Desktop\\makesign.bat "${value}"`, { encoding: 'utf8' }, (err, stdout, stderr) =&gt; {        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, () =&gt; {console.log('Batch serverrunningathttp://localhost:12345/');});
@echo offsetlocal 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&gt; nulgoto signloop):postprocesspowershell -nologo -command ^  "$header = '&gt;|'; $footer = '|&lt;'; $body =Get-Content '%outfile%' -Raw;Write-Output ($header + \"`r`n\" + $body + $footer)"powershell -nologo -command ^  "$header = '&gt;|'; $footer = '|&lt;'; $body =Get-Content 'signed.asc' -Raw;Set-Clipboard -Value ($header + \"`r`n\" + $body + $footer)"endlocalexit /b
AutoHotkey(以前と同じ)
#Persistent#SingleInstance ignoreSetTitleMatchMode, 2WinWaitActive, pinentrySendInputpasswordSleep 100SendInput {Enter}ExitApp

動けばいいという考えで作っているので余分なコードも含んでいるかもしれない。

-----BEGINPGP SIGNEDMESSAGE-----Hash: SHA512https://anond.hatelabo.jp/20250613185036 -----BEGINPGP SIGNATURE-----iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaEv1FQAKCRBwMdsubs4+SHHkAQDUOLgBcdji2T6MJ7h/vlMdFfGlWAzNdXijjE1gIuEPywEAiMNMZqhrMmtlc7UqRuggNJ/UTa5xTIcKp622+7jJQQg==Lgkl-----ENDPGP SIGNATURE-----

Permalink |記事への反応(1) | 18:50

このエントリーをはてなブックマークに追加ツイートシェア

2025-06-09

dorawii

ようやく(ほぼ)すべてが自動化された。

あとはローカルサーバーの起動をスタートアップに設定する(方法AIに聞いて指示に従う)だけの消化試合

ここにほとんどAI頼りのコードを公開しておく。

事前にインストールしておくもの

autohotkey

nodejs

ユーザースクリプトを実行できる拡張機能

パスとかの注意

署名要求してくるパスワードを自動入力するahkファイルドキュメントAutoHotkey配下に置いた。

バッチファイル(make.sign.bat)はデスクトップに置いた。

以下コード

autopass.ahk
#Persistent#SingleInstance ignoreSetTitleMatchMode, 2WinWaitActive, pinentrySendInput お前のパスワードSleep 100SendInput {Enter}ExitApp
run-bacth-server.js
//run-batch-server.jsconsthttp =require('http');const { exec } =require('child_process');const server =http.createServer((req, res) =&gt; {  if (req.url === '/ping') {    res.writeHead(200);    res.end('pong');  } else if (req.url === '/run-batch') {    exec('C:\\Users\\you\\Desktop\\makesign.bat', (err) =&gt; {      res.writeHead(200);      res.end(err ? 'Error' : 'OK');    })    ;  } else {    res.writeHead(404);    res.end('Not found');  }});server.listen(12345, () =&gt; {console.log('Batch serverrunningathttp://localhost:12345/');});
makesign.bat
@echo offsetlocal enabledelayedexpansion::ミリ秒単位UTC時刻を取得for /f %%a in ('powershell -nologo -command "[int64]::Parse((Get-Date).ToUniversalTime().ToString('yyyyMMddHHmmssfff'))"') doset timestamp=%%a::署名するファイルset infile=%TEMP%\pgp_input.txtset outfile=%TEMP%\pgp_output.asc:: 以前の出力があれば削除if exist "%outfile%" del "%outfile%"::タイムスタンプを原文として保存echo %timestamp%&gt; "%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&gt; nulgotosignloop):postprocess::PowerShellで余計な改行なしに |&lt; をつけてクリップボードコピーpowershell -nologo -command ^  "$header = '&gt;|'; $footer = '|&lt;'; $body =Get-Content '%outfile%' -Raw;Set-Clipboard -Value ($header + \"`r`n\" + $body + $footer)"echo Done.signed.asc created and clipboard updated (no extra blankline).endlocalexit /b
tempermonkeyとかに登録するユーザースクリプト
// ==UserScript==// @namePGP署名自動付加スクリプト(GM_xmlhttpRequest版)// @namespacehttp://tampermonkey.net/// @version      1.0// @description投稿前にPGP署名を付けてから送信(fetch未使用)// @matchhttps://anond.hatelabo.jp/dorawii_31/edit*// @grant        GM_xmlhttpRequest// @grant        GM_setClipboard// @grant        GM_notification// / @connectlocalhost// ==/UserScript==(function () {  'use strict';const submitId = 'submit-button';consttextareaId = 'text-body';const localServer = 'http://localhost:12345/run-batch';constpgpSignatureRegex = /-----BEGINPGPSIGNEDMESSAGE-----[\s\S]+?-----BEGINPGPSIGNATURE-----[\s\S]+?-----ENDPGPSIGNATURE-----/;consthttpRequest = (url) =&gt; {    return newPromise((resolve,reject) =&gt; {      GM_xmlhttpRequest({        method: 'GET',url:url,        onload: function (response) {          resolve(response.responseText);        },        onerror: function (error) {reject(error);        }      });    });  };const interceptClick = () =&gt; {constbtn = document.getElementById(submitId);    if (!btn ||btn.dataset.pgpIntercepted === 'true') return;btn.dataset.pgpIntercepted = 'true';btn.addEventListener('click', async function (e) {consttextarea = 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 {        awaithttpRequest(localServer); //バッチ実行constsignatureText = await navigator.clipboard.readText();        if (!signatureText.includes('BEGINPGPSIGNEDMESSAGE')) {          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', () =&gt; {setTimeout(interceptClick, 1000);  });})();

プロミスメソッドとか全然まだ理解してなくてそのなかに関数代入したその関数オブジェクトプロパティresponseを?いやまあそのあたりのコードが示すデータの流れが全然理解できないような人間でもここまでできちゃった。

AIすごいなと思うよ。そして思うのは今後重要になってくるのは文法とか自体に詳しいことじゃなくて、そのプログラムの処理内容を指示できるシステムエンジニア的な言語化能力のほうじゃないかなと思った。

-----BEGINPGPSIGNEDMESSAGE-----Hash: SHA51220250609111559680 -----BEGINPGPSIGNATURE-----iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaEbCbwAKCRBwMdsubs4+SLueAPwOv7PBk4voAe5qlcCEvs/PJhmKc5QAb/1R43JMQFuDZgD/UTPEKsL/PhK9jFGv2HDXK1dVjLNwvosgX9uYJh5xxwY==qiOE-----ENDPGPSIGNATURE-----

Permalink |記事への反応(2) | 20:16

このエントリーをはてなブックマークに追加ツイートシェア

2025-04-07

anond:20250407021332

ちなみに

JSで0インデックスがヘダーのストリングアレイアレイからなるテーブルカラムを別アレイカラムインフォオブジェクトディスプレイナンバーに合わせて並び替えて名前nameからdisplayNameの値に入れ替えるコード

だと

想定される入力

consttable = [

['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 }

];

出力例(上記データ場合

[

['名前', '年齢', 'ID'],

['Alice', '30', '1'],

['Bob', '25', '2']

]

function reorderAndRenameTable(table, columnInfo) {

const header =table[0];

constbody =table.slice(1);

//nameindexマッピングを取得

constnameToIndex = header.reduce((acc,name,idx) =&gt; {

acc[name] =idx;

return acc;

}, {});

// displayNumber順にカラム情報ソート

const sortedColumns = [...columnInfo].sort((a, b) =&gt; a.displayNumber - b.displayNumber);

// 新しいヘッダーを displayName に置き換え

const newHeader = sortedColumns.map(col =&gt; col.displayName);

// 各行の値も同じ順で並び替える

const newBody =body.map(row =&gt; {

return sortedColumns.map(col =&gt; row[nameToIndex[col.name]]);

});

return [newHeader, ...newBody];

}

ここまで数秒なのでまあこれくらいだと書くよりは早い

Permalink |記事への反応(2) | 02:38

このエントリーをはてなブックマークに追加ツイートシェア

2025-03-22

anond:20250322220222

id を手動でコピーしてくるのが面倒だったから、削除ボタンが画面に出るようにした

押すと即削除

const rkm = "(トークン的なもの)"constuser = "(ユーザ名)"const delmasda = (id) =&gt;    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 (constsec of document.querySelectorAll(".section")) {constid =sec.querySelector("a").href.match(/&#92;/(&#92;d{14})/)[1]const delbtn = document.createElement("button")    delbtn.textContent = "削除"    delbtn.onclick = async () =&gt; {        await delmasda(id)sec.remove()    }sec.querySelector(".edit").after(delbtn)}

Permalink |記事への反応(0) | 22:21

このエントリーをはてなブックマークに追加ツイートシェア

増田の削除をひとつひとつ詳細画面開いてするのが面倒だからまとめて消すスクリプトやで

 

const rkm = "(トークン的なもの)"constids = ["(削除する増田ID)"]constuser = "(ユーザー名)"for (constid ofids)    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",    })

Permalink |記事への反応(1) | 22:02

このエントリーをはてなブックマークに追加ツイートシェア

増田3分以上投稿されない時間

増田で 3 分以上投稿されない期間があるのか気になったから調べた

直近の 1 日だとこれだけあった

 

2025-03-22 00:14 -- 2025-03-22 00:182025-03-22 00:10 -- 2025-03-22 00:142025-03-21 07:56 -- 2025-03-21 08:002025-03-21 07:50 -- 2025-03-21 07:562025-03-21 07:44 -- 2025-03-21 07:482025-03-21 07:28 -- 2025-03-21 07:322025-03-21 06:58 -- 2025-03-21 07:032025-03-21 06:45 -- 2025-03-21 06:542025-03-21 06:32 -- 2025-03-21 06:372025-03-21 05:56 -- 2025-03-21 06:042025-03-21 05:51 -- 2025-03-21 05:562025-03-21 05:34 -- 2025-03-21 05:382025-03-21 05:30 -- 2025-03-21 05:342025-03-21 05:00 -- 2025-03-21 05:092025-03-21 04:56 -- 2025-03-21 05:002025-03-21 04:45 -- 2025-03-21 04:502025-03-21 04:09 -- 2025-03-21 04:132025-03-21 03:41 -- 2025-03-21 03:452025-03-21 03:29 -- 2025-03-21 03:392025-03-21 03:03 -- 2025-03-21 03:072025-03-21 02:56 -- 2025-03-21 03:022025-03-21 02:44 -- 2025-03-21 02:482025-03-21 02:33 -- 2025-03-21 02:372025-03-21 02:21 -- 2025-03-21 02:272025-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 =asyncfunction* (){constbrowser =newBrowser()const page =browser.newPage()try{for (let num = 1; ; num++){await setTimeout(3000)await page.goto(`https://anond.hatelabo.jp/?page=${num}`)constdays = page.mainFrame.document.querySelectorAll(".day")for (const day ofdays){constdate = day.querySelector("h2 .date").textContent.trim()for (const footer of day.querySelectorAll(".sectionfooter")){consttime = footer.textContent.match(/\d{2}:\d{2}/)[0]yield`${date}${time}`}}}}finally{await page.close()awaitbrowser.close()}}constdiff = (a, b) =&gt;{returnnewDate(b +":00") -newDate(a +":00")}let prev =nullforawait (constdatetime of getTimestamps()){if (prev &amp;amp;&amp;amp;diff(datetime, prev)&gt;1000 * 60  * 3){console.log(datetime, prev)}prev =datetime}

 

結果をみると昼間はずっと深夜から早朝にかけてときどきある

基本は空いても 5 分程度であり、最大でも10 分となっている

投稿が少ないと感じるときもあるが、賑わってる方だといえる

Permalink |記事への反応(1) | 02:05

このエントリーをはてなブックマークに追加ツイートシェア

2025-03-10

LLMで50個くらいのjavascriptのvarをconst,letに置き換えさせたら

1Mトークン以上かかったわ

Permalink |記事への反応(0) | 10:01

このエントリーをはてなブックマークに追加ツイートシェア

2025-03-06

今のフロントエンドコードを書いてるとCOBOLの頃を思い出す

constを上から順にひたすら羅列していく感じがなんかね

と思うオブジェクト指向おじさんなのだった

以下chatgptによるremixjsの例

import { useEffect, useState } from "react";

import {json } from "@remix-run/node";

import { useLoaderData } from "@remix-run/react";

exportconst loader = async () =&gt; {

constresponse = await fetch("https://api.example.com/data");

const data = awaitresponse.json();

returnjson({ initialData: data });

};

export default functionIndex() {

const { initialData } = useLoaderData();

const [data, setData] = useState(initialData);

useEffect(() =&gt; {

const fetchData = async () =&gt; {

constresponse = await fetch("http://api.com/get");

const newData = awaitresponse.json();

setData(newData);

};

fetchData();

}, []);

if (initialData.value === null || data.value === null) {

return (

データの取得に失敗しました。

);

}

return (

&lt;h1&gt;Remix:SSR +クライアント処理&lt;/h1&gt;

サーバーサイドの初期データ: {initialData.value}

クライアント更新したデータ: {data.value}

);

}

Permalink |記事への反応(2) | 23:47

このエントリーをはてなブックマークに追加ツイートシェア

ブクマエントリーページを開くブックマークレット

ちょっと長いけど、拡張機能より便利かなと。

javascript:(()=&gt;{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();})();

Permalink |記事への反応(0) | 08:48

このエントリーをはてなブックマークに追加ツイートシェア

2025-02-04

for文を使いたくないのはletを使いたくないか

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

まぁTSでもClass作れるから好きにすればいいんだけど

とにかくプログラミングに関する規則でHowだとかWhatだとかそういうフワフワしたこと言い出したら要注意

たぶん大学とかでフワフワしたプログラミングだけしてて碌なプロダクト作ったことない

ちゃんとしたプログラマーは

「これでバグが減ります(そして生産性が上がります)」

と言ってくれるから、そういうやつだけ重宝しろ

Permalink |記事への反応(14) | 22:55

このエントリーをはてなブックマークに追加ツイートシェア

2024-10-08

anond:20241007235856

 

光の速さは一定

マイケルンモーレーという実験がある

地球宇宙空間を動いているのだから地球の進行方向と垂直方向では光の速さが変わるだろう。そう考えて実験してみたところ、どちらの速さも変わらなかった。つまり、どんな系でも光の速さは一定であるらしい。

 

これを式にするとこうなる。

光の速さをc, 時刻 t の間に光の進む距離を x として

x/t = c

式変形すると

(ct)^2 - x^2 =const = 0

おや、なんだか見たことある形になったね

 

空間回転を考えよう

ここで一旦休憩。座標系を回転させても'棒の長さは一定'という式を考えてみよう

x^2 + y^2 =const

かんたんのため z 方向は考えない

この時座系を回転させる式を行列で書くと

 

x' = |cos  -sin | x

y'    |sin  cos | y

こうなる。(心の目で読んで欲しい)

cos^2 +sin^2 = 1

という式を思い出すと

x'^2 + y'^2 = x^2 + y^2 =const

であることが確かめられると思う

 

戻って光の速さが一定の式

(ct)^2 - x^2 =const = 0

上の'棒の式'とは符号が逆だね。こんなときsincos ではなくsinhcosh を使う。

 

cosθ = ((exp iθ) + (exp -iθ))/2

sinθ = ((exp iθ) - (exp -iθ))/2

 

sincos は↑の定義だったのに対して

sinhcosh は↓の定義

 

coshθ = ((exp θ) + (exp -θ))/2

sinhθ = ((exp θ) - (exp -θ))/2

 

計算すると

cosh^2 -sinh^2 = 1 になるのがわかると思う。

cos^2 +sin^2 = 1 とは符号が逆になってるね

 

光の速さが系を変換しても変わらないという式を行列で書くと

ct' = |cosh  -sinh |ct

x'   | -sinh  cosh | x

こうなる。 これがローレンツ変換

 

(ct')^x - x'^2 = (ct)^2 - x^2

であることが確かめられると思う。

 

棒の長さが一定、つまり空間回転は空間方向 (x,y,z)しか混ぜないけれど、

光のはやさが一定、つまりローレンツ変換時間空間 (t, x ) を混ぜているでしょ?

 

時間が遅れる

速さ v で進むロケットを考えてみよう。

地上では昇くんがロケット観測している。

t時間後に到達した距離を x として

v=x/t  

だ。

一方、ロケットには美加子さんが乗っていてその携帯電話の表示では地球を発ってから T時間である

Tを計算してみよう。

 

先程のローレンツ変換の式に代入すると

 

cT =ctcosh - xsinh =ct (cosh - v/csinh)

ここで x =ct を使ったよ。最後cosh で全体を纏める

=ctcosh ( 1 -tanh^2)

=ct (1/cosh)

になる。

ここまで誤魔化していたけど、cosh はロケットの速さ v で決まるパラメータ

1/cosh = \sqrt{1-(v/c)^2}

なんだ。天下り申し訳ないけど、増田では式も図も書けないので導出は勘弁して欲しい

とにかくまとめると

T = t \sqrt{1-(v/c)^2}

だね。ロケットの速度 v は光速度以下なので T < t になる。

地上で待つ昇くんが大学生になっても美加子さんが中学生のままなのはこんなワケだね

v が大きくなるほど時間の遅れは大きくなるよ




 

Permalink |記事への反応(0) | 14:09

このエントリーをはてなブックマークに追加ツイートシェア

2024-09-22

AIインフルエンサーたちはChatGPTが世に出る前は何を投稿していたのか?(2/2)

AIインフルエンサーたちはChatGPTが世に出る前は何を投稿していたのか?(1/1)の続き

吉見拓哉|Takuya Yoshimi

このTwitterアカウントは、主にYouTubeSEO競馬アニメなどの話題について呟いており、自身活動や興味関心について発信しています

YouTubeプレミアムが500円増額。しかし、、、不可逆!!!

shimayuz@AIクリエイター

このアカウントは、日々の米国株暗号通貨市場動向、特にテクノロジー関連株やビットコイン価格変動についてツイートしています

ゼロコロナ政策再び。 $AAPL生産に影響が懸念され大きく下落。更に経済混乱が想定され、株価はSP500全業種で

一方で年末商戦売上堅調な滑り出しで $AMZN

暗号資産レンディングBlockFiが経営破綻暗号通貨も軒並み下落。

チャート上、なんとか踏み止まるか? 」

元木大介ᯅシステム生成AI Babel/Zoltraak & 生成AI塾

このアカウントは、AI特にプロマネAIや量子AIに関する話題や、仕事効率化、プログラミング、そして最新の技術トレンドについて呟いています

"プロマネAI実証実験を開始しました!量子AI×NotionによるプロマネAI実証実験を開始https://prtimes.jp/main/html/rd/p/000000003.000082094.htmlvia @PRTIMES_JP"

sangmin.eth | Dify Ambassador

このアカウントは、主にOpenAIの最新言語モデルtext-davinci-003」の進化と、そのモデルを用いた英語学習ツールサービスについて呟いています特に英語学習におけるAI活用とその重要性を強調しています

"朝起きたら世界がまた変わっていた(笑)。@OpenAI が最新モデルtext-davinci-003」を発表。主な特徴は、①より明確で、説得力のある文章が書ける②より複雑な指示にも対応③より長い形式コンテンツが生成可1月に"InstructGPT"が出た時も感動したけどそれを遥かに上回る進化、です。"

深津 貴之 / THE GUILD

このアカウントは、主にAI特に画像生成AIに関する話題を呟いています特にStable Diffusionのバージョンアップや使い方について多くのツイートをしています

結果

ここにリストアップした人たち以外もみましたが、分類すると3種類のアカウントがいて

1. ChatGPTブーム後にアカウントを始めた人

2. NFTなどの儲かりそうな技術を追いかけていた人

3.日常ツイートをしていたが目覚めてしまった人

共通する特徴としては会社代表の人が多いです。

またIDを変更してログがヒットしない人は含まれていません(IDから特定できるけどそこまでやらなかった)

ツイートを取得するJS

javascript:(function() {

consttext = Array.from(document.querySelectorAll('[data-testid="tweetText"]')).map(s =&gt; s.textContent.trim()).join('\n');

consttextarea = document.createElement('textarea');

textarea.value =text;

document.body.appendChild(textarea);

textarea.select();

try {

document.execCommand('copy');

console.log('結果がクリップボードコピーされました!');

}catch (err) {

console.error('クリップボードへのコピーに失敗しました:', err);

}

document.body.removeChild(textarea);

})();

Permalink |記事への反応(1) | 23:00

このエントリーをはてなブックマークに追加ツイートシェア

2024-09-15

RSS 取得メモ

const axios =require('axios'); //HTTPリクエストを行うためのモジュール

constfs =require('fs'); //ファイル操作モジュール

constxml2js =require('xml2js'); //XMLJSONに変換するためのモジュール

const chardet =require('chardet'); //文字エンコーディングを検出するためのモジュール

const iconv =require('iconv-lite'); //文字エンコーディングを変換するためのモジュール

//RSSフィードURL

constrssUrl = 'https://www.mlit.go.jp/important.rdf'; // 例としてRSSフィードURL指定

//RSSフィードを取得してファイルに保存する関数

async function fetchAndSaveRSS() {

try {

//RSSを取得

constresponse = await axios.get(rssUrl, {responseType: 'arraybuffer' });

constrssData =response.data;

//文字エンコーディングを検出

const detectedEncoding = chardet.detect(rssData);

console.log('検出された文字エンコーディング:', detectedEncoding);

//UTF-8に変換

const utf8Data = iconv.decode(rssData, detectedEncoding);

//XMLJSONパース

xml2js.parseString(utf8Data, (err, result) =&gt; {

if (err) {

console.error('XML解析エラー:', err);

return;

}

//パース結果をファイル書き込み

constjsonData =JSON.stringify(result, null, 2);

fs.writeFileSync('rss_feed.json',jsonData, 'utf8');

console.log('RSSフィードファイルに保存されました。');

});

}catch (error) {

console.error('エラーが発生しました:',error);

}

}

// 実行

fetchAndSaveRSS();

Permalink |記事への反応(0) | 04:45

このエントリーをはてなブックマークに追加ツイートシェア

2024-09-13

function sleep(ms){

const startTime = newDate();

while(newDate() -startTime&lt;ms);&lt;/p&gt;

}

functionclickMore() {

sleep(200);

document.querySelector('[data-cl-params="_cl_vmodule:ans_re;_cl_link:more"]')?.click();

}

new MutationObserver(clickMore).observe(document.getElementById("leftColumn"), {childList:true, subtree:true});

clickMore();

Permalink |記事への反応(0) | 12:52

このエントリーをはてなブックマークに追加ツイートシェア

2024-08-16

anond:20240816092530

増田コードを表示する方法説明しますね。

記法

ふつう投稿と違うのは以下の3点だけです。

特殊文字は、実体参照(&amp; や &lt;)ではうまくいきません。数値参照を使ってください。

出したい文字数値文字参照
&&#38;
<&#60;
>&#62;

このようなコードを表示したい場合

const dinner = (chicken,curry) => {
if (chicken &&curry) {
console.log('チキンかつカレー')
}
}

このように入力してください。

<pre>const dinner = (chicken,curry) =&#62; {<br>    if (chicken &#38;&#38;curry) {<br>console.log('チキンかつカレー')<br>    }<br>}</pre>

Permalink |記事への反応(2) | 15:39

このエントリーをはてなブックマークに追加ツイートシェア

2024-08-15

スターの多い順にブコメを並び替える

FANZAの検索結果から熟女を除外するブックマークレット がうまく動いたので、気を良くしてはてなブックマークコメント欄スター数順にソートするブックマークレット作りました

作った動機は、「注目コメントに入りきれなかったちょっといいコメント」をサクサク探したいから。結果として建設コメント順位付けモデル無効化していますが、あのアルゴリズムには特に不満は特にありません。

ブックマークレット

javascript: (async () => {
const wait =ms => newPromise(resolve => setTimeout(resolve,ms));
document.querySelector('.js-bookmarks-sort-tab[data-sort="recent"]').click();
window.scrollTo(0, document.body.scrollHeight);
await wait(1000);
window.scrollTo(0, 0);
await wait(1000);
const p = document.querySelector('.js-bookmarks-recent');
let l = Array.from(p.querySelectorAll('.entry-comment-contents'));
const g = e => {
let n = e.querySelectorAll('.hatena-star-star').length;
const c = e.querySelector('.hatena-star-inner-count');
return c ? n +Number(c.textContent) : n
};
l = l.filter(e => g(e) > 0);
l.sort((a, b) => g(b) - g(a));
p.replaceChildren(...l);
})();

ミニファイしたものコードに一部誤りがありましたので訂正しました(2024-08-1611:47)

javascript:(async()=>{const wait=ms=>newPromise(resolve=>setTimeout(resolve,ms));document.querySelector('.js-bookmarks-sort-tab[data-sort="recent"]').click();window.scrollTo(0,document.body.scrollHeight);await wait(1000);window.scrollTo(0,0);await wait(1000);const p=document.querySelector('.js-bookmarks-recent');let l=Array.from(p.querySelectorAll('.entry-comment-contents'));const g=e=>{let n=e.querySelectorAll('.hatena-star-star').length;const c=e.querySelector('.hatena-star-inner-count');return c?n+Number(c.textContent):n};l=l.filter(e=>g(e)>0);l.sort((a,b)=>g(b)-g(a));p.replaceChildren(...l)})()

使い方

FANZAの検索結果から熟女を除外するブックマークレット 参照

コード解説

コード説明にダサイところがあったら厳しく指摘して下さい。

javascript:

ブックマークレット必要な、URLの種類を示すスキーム名です。

(async () => {
// 処理
})();

ページに元々ある変数たちとバッティングしないように、まず無名関数ラップします。処理の中で await を使いたいので async宣言しています

const wait =ms => newPromise(resolve => setTimeout(resolve,ms));

処理の途中で待ち時間を設けるための関数です。便利。

document.querySelector('.js-bookmarks-sort-tab[data-sort="recent"]').click();
window.scrollTo(0, document.body.scrollHeight);
await wait(1000);

「新着コメント」タブをクリックし、ページの一番下までスクロールダウンしてから少し待つ動作です。新着コメントの後半部分(スクロールきっかけの遅延読み込みになっているところ)の読み込みをうながしています

window.scrollTo(0, 0);
await wait(1000);

ページの先頭に戻ってまた少し待ちます。合計2秒の待ち時間雰囲気で決めていますので、これでなければならない・これで過不足ないという値ではありません。単にコメントの読み込み完了を判定する処理を書くのがめんどうだっただけです。

const p = document.querySelector('.js-bookmarks-recent');

新着ブコメの親要素です。繰り返し呼び出すので名前をつけています

let l = Array.from(p.querySelectorAll('.entry-comment-contents'));

コメントをすべて配列に格納します。

const g = e => {
let n = e.querySelectorAll('.hatena-star-star').length;
const c = e.querySelector('.hatena-star-inner-count');
return c ? n +Number(c.textContent) : n
};

コメントはてなスター数をカウントして返す関数です。たくさんスターがついてる ★256★ みたいなやつの数字も足します。

l = l.filter(e => g(e) > 0);

ソートする前に、無スターコメントを消去しています。してもしなくてもいいことですが。

l.sort((a, b) => g(b) - g(a));

残ったコメントスター数で降順ソートします。.querySelectorAll() で収集した要素を配列に入れ直したのは、この .sort()メソッドを使いたいからです(.querySelectorAll() が返す配列風の NodeListオブジェクトは、配列共通メソッドもいくつかあるものの、大半は使えないのです)。

p.replaceChildren(...l);

親要素の内容を、並び替えの終わったコメントそっくり入れ替えて、処理完了です。画面を見ると新着コメントの中身が「スターのついたコメントのみ・スターの多い順」に並んでいます。元に戻す方法はないので、原状回復にはリロードします。ソート状態を示すフラグを立てておいてスターソート⇔日付ソートをかわりばんこに行うようにすればできそうだなと思ったけど実装しません。連打スターを省く処理を追加してUU数でソートできればもっと厳正なランキングになるなーと今思いつきましたがそれも実装しません。

Permalink |記事への反応(1) | 21:55

このエントリーをはてなブックマークに追加ツイートシェア

次の25件>
ログインユーザー登録
ようこそ ゲスト さん
Copyright (C) 2001-2025 hatena. All Rights Reserved.

[8]ページ先頭

©2009-2025 Movatter.jp