2025年3月24日月曜日

LOMのステータスを総当たり探査した話(技術者向け)

 たまには技術ノートでも書こうかなという話
今回のお題はlomのベストパターン探査です。

VBS:
Function CalcStats(w)
s=array(5,5,5,5,5,5,5)
s(0)=s(0)+0.75*w(0)+1 *w(1)+1 *w(2)+1.25*w(3)+1.25*w(4)+1.75*w(5)+0.75*w(6)+0.5 *w(7)+1.25*w(8)+0.75*w(9)+0.5 *w(10)
s(1)=s(1)+1.25*w(0)+1 *w(1)+0.75*w(2)+1 *w(3)+0.75*w(4)+0.75*w(5)+1.25*w(6)+0.75*w(7)+0.75*w(8)+1.25*w(9)+1.75*w(10)
s(2)=s(2)+0.75*w(0)+1 *w(1)+1 *w(2)+0.75*w(3)+1.25*w(4)+0.5 *w(5)+1 *w(6)+0.75*w(7)+0.75*w(8)+0.5 *w(9)+0.75*w(10)
s(3)=s(3)+0.75*w(0)+0.75*w(1)+0.75*w(2)+0.75*w(3)+0.75*w(4)+0.75*w(5)+0.75*w(6)+1.25*w(7)+0.5 *w(8)+0.75*w(9)+0.75*w(10)
s(4)=s(4)+0.5 *w(0)+0.75*w(1)+0.75*w(2)+0.75*w(3)+0.5 *w(4)+0.75*w(5)+0.5 *w(6)+0.5 *w(7)+1 *w(8)+0.5 *w(9)+0.5 *w(10)
s(5)=s(5)+1 *w(0)+0.75*w(1)+1 *w(2)+0.5 *w(3)+0.75*w(4)+0.75*w(5)+0.75*w(6)+0.75*w(7)+0.75*w(8)+0.75*w(9)+0.75*w(10)
s(6)=s(6)+1 *w(0)+0.75*w(1)+0.5 *w(2)+0.75*w(3)+0.5 *w(4)+0.75*w(5)+1 *w(6)+1.25*w(7)+0.75*w(8)+1.25*w(9)+1 *w(10)
for i=0 to ubound(s) s(i)=fix(s(i) if s(i)>99 then s(i)=99 next CalcStats=s end function function t619(s) t=0 for i=0 to ubound(s):t=t+s(i):next if t>=619 then t619=true else t619=false end function function s79t615(s) f=true t=0 for i=0 to ubound(s) if s(i)<79 then f=false t=t+s(i) next if t<615 then f=false s79t615=f end function msgbox s79t615(CalcStats(array(1,81,1,0,0,0,0,6,9,0,0)))
msgbox t619(CalcStats(array(1,81,1,0,0,0,0,6,9,0,0)))

考えなくちゃいけないのは計算と探査判定
探査方法の考え方としては、武器別にステータス配列を作って配列を合算しても良いんだけど配列同士の合算が面倒くさい。
考え方を変えて関数で各武器の回数だけを貰ってその分だけ能力値を掛ける。
これをCscriptで多重forに放り投げるだけ。

Jscript:

function CalcStats(w) {
  var s = [5, 5, 5, 5, 5, 5, 5];
  s[0]=s[0]+0.75*w[0]+1*w[1]+1*w[2]+1.25*w[3]+1.25*w[4]+1.75*w[5]+0.75*w[6]+0.5*w[7]+1.25*w[8]+0.75*w[9]+0.5*w[10];
  s[1]=s[1]+1.25*w[0]+1*w[1]+0.75*w[2]+1*w[3]+0.75*w[4]+0.75*w[5]+1.25*w[6]+0.75*w[7]+0.75*w[8]+1.25*w[9]+1.75*w[10];
  s[2]=s[2]+0.75*w[0]+1*w[1]+1*w[2]+0.75*w[3]+1.25*w[4]+0.5*w[5]+1*w[6]+0.75*w[7]+0.75*w[8]+0.5*w[9]+0.75*w[10];
  s[3]=s[3]+0.75*w[0]+0.75*w[1]+0.75*w[2]+0.75*w[3]+0.75*w[4]+0.75*w[5]+0.75*w[6]+1.25*w[7]+0.5*w[8]+0.75*w[9]+0.75*w[10];
  s[4]=s[4]+0.5*w[0]+0.75*w[1]+0.75*w[2]+0.75*w[3]+0.5*w[4]+0.75*w[5]+0.5*w[6]+0.5*w[7]+1*w[8]+0.5*w[9]+0.5*w[10];
  s[5]=s[5]+1*w[0]+0.75*w[1]+1*w[2]+0.5*w[3]+0.75*w[4]+0.75*w[5]+0.75*w[6]+0.75*w[7]+0.75*w[8]+0.75*w[9]+0.75*w[10];
  s[6]=s[6]+1*w[0]+0.75*w[1]+0.5*w[2]+0.75*w[3]+0.5*w[4]+0.75*w[5]+1*w[6]+1.25*w[7]+0.75*w[8]+1.25*w[9]+1*w[10];
  for (var i = 0; i < s.length; i++) {
      s[i] = Math.floor(s[i]);
      if (s[i] > 99) s[i] = 99;
  }
  return s;
}

function t619(s) {
  var t = 0;
  for (var i = 0; i < s.length; i++) {t += s[i];}
  return t >= 619;
}
function s79t615(s) {
  var f = true;
  var t = 0;
  for (var i = 0; i < s.length; i++) {
     if (s[i] < 79) f = false;
     t += s[i];
  }
  if (t < 615) f = false;
  return f;
}
function s80t607(s) {
  var f = true;
  var t = 0;
  for (var i = 0; i < s.length; i++) {
      if (s[i] < 80) f = false;
      t += s[i];
  }
  if (t < 607) f = false;
  return f;
}

速度比較用にJscriptにそのまま書き直したものがこれ。同じくCscriptで実行したけど速度的に大きな差は無かった。
別にいらなかったんだけど全ステ80以上パターンも探査に追加この時点では607が最低値で分かっていたのでそれを指定。
ちなみにあとで総当たりさせると合計607は三千パターン以上ある為後で合計範囲を610以上に変更した。
619のif elseはそもそも比較演算すればboolなので修正他はそのまま。

javascript(node.js実行):

function CalcStats(w) {
  let s = [5, 5, 5, 5, 5, 5, 5];
  s[0]=s[0]+0.75*w[0]+1*w[1]+1*w[2]+1.25*w[3]+1.25*w[4]+1.75*w[5]+0.75*w[6]+0.5*w[7]+1.25*w[8]+0.75*w[9]+0.5*w[10];
  s[1]=s[1]+1.25*w[0]+1*w[1]+0.75*w[2]+1*w[3]+0.75*w[4]+0.75*w[5]+1.25*w[6]+0.75*w[7]+0.75*w[8]+1.25*w[9]+1.75*w[10];
  s[2]=s[2]+0.75*w[0]+1*w[1]+1*w[2]+0.75*w[3]+1.25*w[4]+0.5*w[5]+1*w[6]+0.75*w[7]+0.75*w[8]+0.5*w[9]+0.75*w[10];
  s[3]=s[3]+0.75*w[0]+0.75*w[1]+0.75*w[2]+0.75*w[3]+0.75*w[4]+0.75*w[5]+0.75*w[6]+1.25*w[7]+0.5*w[8]+0.75*w[9]+0.75*w[10];
  s[4]=s[4]+0.5*w[0]+0.75*w[1]+0.75*w[2]+0.75*w[3]+0.5*w[4]+0.75*w[5]+0.5*w[6]+0.5*w[7]+1*w[8]+0.5*w[9]+0.5*w[10];
  s[5]=s[5]+1*w[0]+0.75*w[1]+1*w[2]+0.5*w[3]+0.75*w[4]+0.75*w[5]+0.75*w[6]+0.75*w[7]+0.75*w[8]+0.75*w[9]+0.75*w[10];
  s[6]=s[6]+1*w[0]+0.75*w[1]+0.5*w[2]+0.75*w[3]+0.5*w[4]+0.75*w[5]+1*w[6]+1.25*w[7]+0.75*w[8]+1.25*w[9]+1*w[10];
  for (let i = 0; i < s.length; i++) {
  s[i] = Math.floor(s[i]);
  if (s[i] > 99) s[i] = 99;
  }
  return s;
  }
function t619(s) {
  return s.reduce((t, val) => t + val, 0) >= 619;
}
function s79t615(s) {
  const t = s.reduce((sum, val) => sum + val, 0);
  return s.every(val => val >= 79) && t >= 615;
}
  function s80t607(s) {
  const t = s.reduce((sum, val) => sum + val, 0);
  return s.every(val => val >= 80) && t >= 607;
}

前回の記事で初めてnode.jsをインストールしたんだけどjavascriptがコーディング出来てローカル処理をしたいなら絶対インストールした方がいい。
処理速度は100倍以上速くなる。
Jscriptでは変数定義にvarしか使えなかったけどjavascriptなのでletやconstに修正して、判定関数も処理を整理。

この後さらにこれをC#に書き換えて並列処理で高速化するんだけど、現状で片手剣30回前後まで絞っても目新しいパターン結果は出なかったので省略。

発見パターンのうち有益なパターンは「LOMリマスター:レベルアップ能力値シミュレーター」の方を参照してください。
※Lvシステムは変わってないのでリマスターじゃない場合も同じです

結局の所、能力調整的に微妙な…いわゆる最大パターン619は6パターンしかありません。
逆に一番いいパターン、79以上615は27パターン程あります。

(オマケとして探査した80以上の合計上限はどう頑張っても611が最大で、79より合計が下がるので微妙。ちなみにこの611パターンは1つしか見つかってないです。80以上610なら18パターンほど見つかってます。)

■大まかな探査時間
片手剣の回数をベースに杖とグラブは無制限、他は10回という制限で
片手剣の使用回数を62回にした場合はだいたい以下の通り。

個別の時間差
 0% →   9%: 13:07:43 - 12:28:35 = 39分 8秒 = 2,348秒
 9% →  18%: 13:40:55 - 13:07:43 = 33分12秒 = 1,992秒
18% →  27%: 14:09:01 - 13:40:55 = 28分 6秒 = 1,686秒
27% →  36%: 14:34:30 - 14:09:01 = 25分29秒 = 1,529秒
36% →  45%: 14:54:26 - 14:34:30 = 19分56秒 = 1,196秒
45% →  54%: 15:10:44 - 14:54:26 = 16分18秒 =   978秒
54% →  63%: 15:24:24 - 15:10:44 = 13分40秒 =   820秒
63% →  72%: 15:35:43 - 15:24:24 = 11分19秒 =   679秒
72% →  81%: 15:45:03 - 15:35:43 =  9分20秒 =   560秒
81% →  90%: 15:52:29 - 15:45:03 =  7分26秒 =   446秒
90% → 100%: 15:58:26 - 15:52:29 =  5分57秒 =   357秒 

ループの外側の数値が大きくなるほど使える回数は減るので内側に向かってどんどん処理時間が早まっていく。
だいたい50前後くらいなら少し放置しておけばリストアップ出来る。
これより片手剣の回数が減ると一日放置とかになるかも

■C#その後:
・並列でも全てをなげちゃうとCPU90%くらいで数日帰ってこないことになるので片手剣の回数を指定出来るように修正、関数として分離(全体を回したい時はこれにfor)
全体に投げてた並列処理をfor内部を並列化して内部のforを高速化

・条件を変更するたびにコンパイルが面倒になったので設置textから判定条件を読み込めるように修正
File.ReadAllLines(conditionsFile);で読み込んで改行でsplitして
読み込むテキストは改行区切りで「80,80,80,80,80,80,80,610」「81,81,81,81,81,81,81,」「,,,,,,,619」こんな感じ。空部分は条件指定なし

・全部デスクトップに吐いて、読み込みもデスクトップだったのを全部実行exeから相対パスに出力と読み込みするように変更「Environment.GetFolderPath(Environment.SpecialFolder.Desktop)→System.Reflection.Assembly.GetExecutingAssembly().Location」とか

・各ループ上限を柔軟に調整したくなったのでリミットテキストを外部において武器の回数上限を変更出来るように修正

・引数で回数を指定して実行する事で入力待ちするのを省略するように変更
片手剣回数を関数分離して回したいときはforにしようと思ってたけど適当にスクリプトで引数付きで実行出来るようにした

などの修正をしたけど検証終わったらもう使わないよなぁという気持ち。
いやまぁ初見言語のC#の勉強にはなったけども

C#(for抜粋):

for (int w0 = 0; w0 <= w0Limit; w0++)
{
  int w2Limit = Math.Min(remaining - w0, loopLimits[2]);
  Console.WriteLine("\n片手剣 " + actualOneHandSword + "回 - 進捗: " + (w0 * 100 / w0Max) + "% (" + w0 + "/" + w0Max + ") - " + DateTime.Now);
  for (int w2 = 0; w2 <= w2Limit; w2++)
  {
    int w3Limit = Math.Min(remaining - w0 - w2, loopLimits[3]);
    for (int w3 = 0; w3 <= w3Limit; w3++)
    {
      int w4Limit = Math.Min(remaining - w0 - w2 - w3, loopLimits[4]);
      for (int w4 = 0; w4 <= w4Limit; w4++)
      {
        Console.Write(".");
        Console.Out.Flush();
        int w5Limit = Math.Min(remaining - w0 - w2 - w3 - w4, loopLimits[5]);
        for (int w5 = 0; w5 <= w5Limit; w5++)
        {
          int w6Limit = Math.Min(remaining - w0 - w2 - w3 - w4 - w5, loopLimits[6]);
          for (int w6 = 0; w6 <= w6Limit; w6++)
          {
            int w7Limit = Math.Min(remaining - w0 - w2 - w3 - w4 - w5 - w6, loopLimits[7]);
            Parallel.For(0, w7Limit + 1, w7 =>
            {
              int w8Limit = Math.Min(remaining - w0 - w2 - w3 - w4 - w5 - w6 - w7, loopLimits[8]);
              Parallel.For(0, w8Limit + 1, w8 =>
              {
                int w9Limit = Math.Min(remaining - w0 - w2 - w3 - w4 - w5 - w6 - w7 - w8, loopLimits[9]);
                for (int w9 = 0; w9 <= w9Limit; w9++)
                {
                  int w10 = remaining - w0 - w2 - w3 - w4 - w5 - w6 - w7 - w8 - w9;
                  if (w10 >= 0 && w10 <= loopLimits[10])
                  {
                    int[] localW = new int[11];
                    localW[0] = w0; localW[1] = actualOneHandSword; localW[2] = w2; localW[3] = w3; localW[4] = w4;
                    localW[5] = w5; localW[6] = w6; localW[7] = w7; localW[8] = w8; localW[9] = w9; localW[10] = w10;
                    bool localHasMatch = false;
                    StatsWrite(localW, actualOneHandSword, limitPrefix, ref localHasMatch);
                    if (localHasMatch) hasMatch = true;
                  }
                }
              });
            });
          }
        }
      }
    }
  }
}

C#のコードだけ記載しないのもあれだったから。総当たり処理部分はこんな感じ。
ループは最初forではなく再帰にしてたんだけどforの方が早いらしい事が分かったのでforに戻した。1秒でも早くというかミリ秒でも早く動いて欲しかった故に・・・。

判定処理をforに入れるとfor内が厚くるのでStatsWrite側に判定処理を入れています。Parallel.Forで並列化してるのは杖とグラブ部分。

2025年3月21日金曜日

node.jsを使ってみた話。

 そもそもnode.jsって何?

今回自分が知りたかった要点ですが「node.js」とは
・node.js=javascriptのローカル実行環境
・実行環境=インストールが必要
・javascriptの記述でローカル処理できる
・WSH(Jscriptやvbs)より
百倍以上速い!!

まぁ他にもあるんですが、今回押さえたい要点はこの4つです。

「node.js」名前だけ聞いたことあったんですが「jQuery」のようなjavascriptライブラリだと思ってたんですよ。
・・・ですが、全然そうではなくて実行環境だったんですよ。

「javascript」と「Jscript」と「node.js」の関係

■javascript
web上で動的に処理する時に使うスクリプト言語です
ファイルの拡張子は「.js」です

■Jscript
Windows上で(ローカル環境で)処理を自動化する時に使うjavascriptベースのスクリプト言語です。限りなくjavascriptに近い言語ですがjavascriptとは別物です
ファイル拡張子は「.js」つまり拡張子までもjavascriptと同じです。

古いjavascriptがベースで、例えば変数定義はvarしかなくlet等は使えません。
また、今後新規にサポートされる予定もありません。むしろ消えかかっている泥船言語です。Microsoftは今後Windowsの自動化はパワーシェルを推奨してる感じです。

根本的な話をするとWSH(Windows Script Host)というWindows上で動かすスクリプト言語二つの内の一つがJscriptです(もう一つはvbs)。
バッチファイル(.bat)などより柔軟な自動化が出来る言語で、当然ながらWindows上でしか動作しません。
ただ逆に言えばWindowsであれば何かをインストールなど不要で直ぐにスクリプトが使えWindowsのこまごました自動化が出来ます。普通に便利です。

■node.js
「node.js」なんて名前だからてっきりファイルかと思うじゃないですか?
そうじゃないんです。
javascriptをローカルで使えるようにする環境」なんです

そうです、記述する言語は「javascript」です。
当然ファイルの拡張子も「.js」です。
しかも、ローカルファイルにアクセスなども出来るようになるんです。
「じゃあ、Jscript完全上位互換になるって事?」と思うでしょうがJscriptで使えるものが使えません。

例えば:
・WScript.Echo: Windows Scripting Host(WSH)特有のメソッドで、Node.jsには存在しません。
・WScript.CreateObject("WScript.Shell"): WSHでシステムオブジェクトにアクセスするための機能。Node.jsでは代替手段が必要。
・new ActiveXObject("Scripting.FileSystemObject"): WindowsのCOMオブジェクトで、Node.jsではサポートされていません。

あと上には書きませんでしたがnode.jsはマルチプラットフォームです。
Windows以外の環境でもjavascriptが使えるって事になります。

node.jsはjavascriptで記述するのに何でそんなに早いの?

そもそもなんでscriptは遅いのかって話に直結します。
一般的にプログラムとスクリプトには壁があり、その大きな違いはコンパイル(やビルド)をするかしないかが大きな境界になります。
インタプリタとコンパイラという区分です。

・インタプリタではソースコードを一行ずつ変換し命令を処理・実行
・コンパイラは全ての命令をまとめて変換後に一括で処理・実行

処理速度だけで言えば機械が処理しやすいプログラム(コンパイラ)の方が圧倒的に早いです。
じゃあなんでスクリプトなんてものがあるかというと、確かにプログラムよりはかなり遅いですが、直ぐに実行できて直ぐに修正出来るという利点があります

小中規模であればスクリプトの方が対処する内容に対して即効性があるのです。
逆にプログラムはコンパイルを通さないと処理が実行出来ませんが大規模な処理は圧倒的に処理速度が重要になります。

node.jsはjavascriptをコンパイルしている

前段の通りコンパイルすると処理速度が爆発的に早くなります。
実はnode.jsはjavascriptをコンパイルする事で高速化しています。

更に詳しく言えば「Just-In-Time (JIT) コンパイル」をしています。
インタプリタで初期実行した後、ホットコードをマシンコードに変換という形です。
手動でコンパイルする必要がないがないが重たい処理だけ知らないうちにコンパイルしてくれるって事ですね。
また、イベント駆動型の非同期I/Oモデル故に大量処理に向いています。


凄い雑にまとめると「javascript」を超高速化出来るのがnode.jsです。
さて、ここから下は駄文です。


そもそもなんでのnode.js使ってみたの?

LOMのレベルアップのステータスを手動で調整してたけど面倒になったから総当たりで他にパターンが無いかの確認と上限を確定させるため。
まぁ一旦雑に総当たりで試してみたが当然遅い。そこで高速化する手立てを考えている時にnode.jsに行きついたわけです。

どれくらい早くなった?

総当たりをぶつけた訳ですが途中探査にJscriptで3時間かかった部分がnode.jsで実行したら1分で探査されました。
Jscript特有部分を少し直した以外は処理方法などは手を加えずに180倍速で処理できました(はっや・・・)。

ただあくまでも今回のしかも一部分を切り取った時間比較なのであれですが基本的には重い処理を作ったなら100倍くらいは早くなると見積もっていいと思います。


追記:
一日node.jsで回して片手剣56回までの探査で行ったので強制終了

「そういえばWindowsってデフォルト環境でC#のコンパイル出来るようになったんだよなぁ」って事を思い出してC#用にコードを変更
さらにC#なら並列で処理できるからもっと早くなるんじゃないか?
フルでぶん投げるけど元より全探査しようとは思ってない。
24時間回してみてどれくらい結果取れるかなぁって感じ←今ここ

余談:早いスクリプト言語って何?

「javascriptをnode.jsで実行(以下node)、javascript、Jscript、VBS、Python」の処理速度を比較した場合単純処理なら

node>>超えられない壁>>Jscript・VBS>javascript>Python

コンパイルがされる関係上百倍くらいは差がでるのでnodeを越える事は出来ません。
重たい処理をしたい場合、悪い事は言いませんのでjavascriptが出来るならnode.jsをインストールした方が良いです。
Pythonインストールするぐらいならnode.jsインストールしたほうがたぶん平和。

もうプログラムで良くない?

まぁ、そもそもWindowsデフォルト環境だけで何もインストールしたくない、もしくは環境的に新たに何かをインストールできない場合、基本的には「Jscript・VBS・javascript」しか選択肢がありません。

ただし、裏技的解決方法としてはC#で書き直す方法があります。
なんとWindowsはデフォルト環境でC#がコンパイルできます!

もう少し丁寧に言うとWindowsはデフォルトで「.NET Framework 」が入っておりC#のコンパイル環境が整っています。
つまり普通にプログラム組んじゃおうって事ですね。当然ながらめちゃ早いです。しかもC#の場合は「Parallel.For」でマルチプロセスで平行処理できる為、今回のような物量探査問題だけならばnodeより確実に早いです。

※一応注意点としてはC#のデフォルトコンパイル環境は「.NET Framework」のバージョンが古いので最新のC#コードが使えない事は留意してください。
例えば「$とか=>」が使えません。
■ラムダ式形式: =>を使った簡潔なメソッド定義はC# 6.0以降の機能。
■文字列補間($"...")がC# 6.0以降の機能


2025年3月13日木曜日

奇数か(偶数か)を判定する

前置き:

今回のお話は 𝕏のおすすめ欄にたまたま流れてきたポスト「1~100までの整数が奇数かどうかを判定するプログラムを書きました!」に起因します。

一般の人には面白さが伝わるか分からないけど『「もし奇数なtrueそうでなければfalse」つまり1なら奇数2なら偶数3なら偶s...って100個書けば良いんだ!』

HAHAHAそうだけどそうじゃないだろっていう面白さ…やっぱり伝わらないかも。

確かに定義が「1~100固定で奇数ならtrueを返し偶数ならfalse」なら間違いないくこれで答えが得られるけど汎用性が無さすぎる事が1つ、もう一つはそもそも処理本質はそこじゃない。っていう所。

「1なら奇数」...は既に答えを知っている場合の条件分岐で本来定義を考えるべきなのは奇数はどの条件で奇数になるのかって話。

このポストにはさらに元ネタがあって「コーディングのヒント」っていうこのベタif文をエクセル等のオートフィル(処理を繰り返す)によって脳筋コードを生成するってネタポストの再ネタポストって事ですね。

そして「【ゆる募】1~100までの整数が奇数かどうかを判定するプログラム大喜利を開催します☺ 言語不問✨」更にネタにした二番煎じ、三番煎じのネタポスト『無駄に洗練された無駄の無い無駄な動き』を考える会みたいな話です


「プログラムをする」=「ロジカルな思考になる事」

自身はプログラマーではないく精々スクリプターなので思考が雑だけれど基本的には同じで「コーディング」する事よりも「その本質はなんなのか」を考える思考ロジックがたぶん一般人とプログラマーの境界な気がします。

この記事を書くきっかけになったのはネタポストもあるけれど実は大喜利のリプにあった「入力が数値じゃないかもしれない」みたいなポストがあってそうじゃないんよなぁと思ってなんとなく(今はそういう話をしてるんではなくロジック部分のみの話なんよ)。

さておき、そもそも通常はどのようにコードを書くか


n=99;print(bool(n%2))


最初に考えたのはこんな感じです。ポスト元が1をtrueにしてるのでそれに合わせます。

奇数なら余りは1になり、偶数なら余りが0になります。

これをbool値「真(True)偽(False)」に変換します。

まぁでもよくよく考えたら「n%2==1」で直接bool値になりますね。

元のコードの通り関数にするなら


num=99
def is_odd(num:int)->bool:return num%2==1
print(is_odd(n))


こんな感じでしょうか。

この通り本来は奇数をだすならたったこれだけなのです。


大喜利の話

特に記載はないですがこの手の大喜利の要点は

結果が正しい事は前提で、より壮大に無駄にリソースを使い条件を満たそうという話です。

かつ限定条件が1~100なのでこの範囲じゃないと上手く動かないとかの方が面白い。

更に言えば単に意味のない処理を入れる訳ではなく必要な処理で構成されており、処理が抜けると動かない形であった方が良いですね。


まぁなのである意味一番元のベタif文100個並べるが完成されてるかもしれません

(判定を100回行い、なおかつ1行でも抜けると正しく動作しない)


もう一つの要点

それは定義を考えるという事です。

通常の奇数の定義は「数値に対して2で割って余りが1なら奇数」これが普通です。

このような大喜利はこの定義を別の条件に言い換え処理を考える遊びです。


例えば入力は1~100の整数という条件ですので「奇数は偶数と必ず交互に存在する」見たいな事を考えた場合「奇数から始まり、対象の数値まで真と偽を交互に代入」すれば奇数か偶数かを判定できる。

𝕏では花占いと表現してポストしたやつで


n=100;b=false;for(i=0;i<n;i++){if(b){b=false}else{b=true}}


こんな感じです。

Jscriptですがまぁjavascriptでも同じです。


更に別の奇数の定義を考えてみます。

交互に存在するなら「真偽交互に100個分回答を用意して、入力数値番目」にアクセスすれば直接真偽を得られる


n=99;WScript.StdOut.WriteLine(Boolean("01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010".split("")[n]*1))


定義を元に少し変更します。

画像でポストするのが面倒だしコピペ出来ないし面倒。という事でコードを「全角140文字=半角280字で考える」

なので真偽配列を10二進数配列を作ります。

さらに配列は0から始まるけど入力値は1~100なので要素-1のアクセスが面倒なので配列の0番目の要素を追加し101個の配列を用意してやれば入力値で直接0か1が返ります。これをbool変換すれば奇数か判定できる。 コードはJscriptのCscriptです。


他にも大喜利の例題は「1,3,5,....99なら奇数」という50個分の判定ですがそもそも偶数奇数判定は「入力値の末尾1桁が1,3,5,7,9なら奇数」でいいじゃんと考える事も出来ます。

であれば


n=100;print([False,True,False,True,False,True,False,True,False,True][int(list(str(n))[-1])])


こんな感じで真偽100個配列を作るより奇数5つの配列でいい 今度はPythonで組んでみましたが先ほどやってる事は同じです。


あと他には「2で割って余りが1なら奇数」という表現を別の表現で考える方法もあります

「2で割った結果が小数になるならば奇数」みたいな感じです


n=100:msgbox cbool(ubound(split(n/2,".")))


例えば2で割ってそれを小数点で配列化、割り切れてれば配列数は0、割り切れてなければ1。

0・1をbool化すれば真偽判定できます


言語特有の考え方ですが先ほどの100個全ての真偽を配列にする方法を

n=100;print(bool(int(list("01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010")[n])))

Python版で考えて、Pythonは進数変換が容易なのでこの二進数を16進数に圧縮しようかと思ったんですがそもそも


n=100;print(bool(int(list("01"*50+"0")[n])))


の圧縮率が高すぎてもうこれで良くない?みたいな事もあります。

「01」というパターンを50回繰り返すというPython記述便利。


さて、交互に繰り返される真偽・二進数・末尾一桁と来ましたが入力値が0かnot0になれば良いわけです。だとするとそもそも「十進数を二進数に変換して末尾一桁を真偽変換」すれば良いのではとも気づきます。


n=100;print(bin(n)[-1]=="1")


おっと、また短くなってしまった。Python久々に書いたけど便利だなぁ。

もっと無駄な形にしないとダメですね


割り算と余りの概念を脳筋で解決する方法も考えてみます

2個をワンセットとして対消滅させていき、数字君が生き残れれば奇数

分かりやすく言うと「数値を2ずつ引いていき、余れば奇数」


n=99:do while n>1:n=n-2:loop:msgbox n=1

vba・vbsならこんな感じです。これもやっぱり短いですね。
やはりベタifが無駄であり無駄という意味では酷いコードの完成度が高い。
でも𝕏の文字数制限内でそれ書くのは面倒臭い。
じゃあ、いっそ実行可能な形式でそれを生成すればいいよね

a=split("false/true","/"):c="n=数値"&vbcrlf:for i=1 to 100:c=c&"if n="&i&" then b="&a(i mod 2)& vbcrlf:next:c=c&"msgbox b"&vbcrlf
createobject("scripting.filesystemobject").createtextfile("n.vbs", 1).write c


𝕏の文字数制限内に収まりました
「数値」の所を1~100に置き換えればそのまま実行可能です。

if n=1 then b=true
if n=2 then b=false
...

をベタ入力したvbsファイルを生成するvbsです。
コードを生成するコードの時点で二度手間で、コード生成時にi mod 2で既に処理内で余りを出してる所にも二度手間感があります。
「でも何かを抜くと動かなくなる」というコードです。
まぁ一旦こんなところで終わりにしましょう。


意味はないけど意味がある事

今回の「奇数の真偽を出す」は別に意味のある事ではありません
ただ、こう言った処理定義の考え方変換や柔軟な発想、手段の引き出しを増やす事はあるほど良いです。

また、0と1で百個用意して配列要素で値をえる方法などは既に結果が変わらない場合などであれば配列をメモリに突っ込んで引き出した方がforなどを回すより高速に処理できるし、呼び出しが外にあればメインは要素1つ与えるだけで取り出せるメイン処理の簡素化が出来たりもします。

こういったものは別に大喜利である必要はありませんんが内容が簡単で初心者でも上級者でも一緒に頭の体操が出来るのが面白い。
簡単な問題なので「処理すべき事」と「結果がこうなる」が分かっているうえで他の人のコードを見れるから初心者からでも上級者がどんな工程を踏むのかが見れるのも良いです。

他にも「与えられた数値が素数かどうか判定する」「うるう年の判定」など条件を知っているが考え方が試されるものは色々あります。

余談:
上記コードは自分で考えたもので、記述出来るのがwscript、VBA、Pythonなどスクリプト系なのでコードがごった煮でごめんね:;(∩´﹏`∩);:

2025年2月12日水曜日

addEventListener()の使い方

まず、一言説明
Q.「addEventListener」って何?
A.「クリック等のマウス操作や、キーボード操作、ブラウザリサイズ等操作で処理を発火できるようになる」


例えば、何かをクリックしたときに関数を呼ぶ場合
「<div onclick="setaf()">」とすれば対象の関数を発火できます。
1つ2つ程度なら全然これで良いと思います。

ですが…、数が多くなった場合はどうでしょうか100個200個divがあった時に全てonclickを入れたり操作するのは大変。
またはクリック以外でもブラウザ上で何か「こういう操作」が発生したらチェックするために関数を発火したい時に関数を発火できます。

経緯

とあるwebシミュレーターを作る時に、gridマップから何処をクリックしたか知りたいなぁという事がありました。
24x24マスのgrid=項目行列も含め625マスのdiv操作
その盤面から何処がクリックされたのか知りたい

操作領域は24x24。の領域を選択範囲19x19をセットで操作=361ヵ所
実際に操作処理するのが165ヵ所。

・・・面倒臭い。

使い方

対象要素.addEventListener( 種類, 関数, false )

一旦形だけ理解しましょう。
これをグローバルエリアに置きます。

第1引数:
固有の種類があります別途調べてください
(つまり、任意の名称設定できません決まったコマンド的なものです)

第2引数:
関数は自分で作った関数名を指定します

第3引数:
イベント伝搬方式のfalseは一旦そのままでいいです。

記述1

対象要素.addEventListener('click', abcdef, false);
function abcdef(){処理内容}

addEventListenerで指定する関数名には「()」は付けないで下さい
付けるとただの関数呼び出しになります
具体的にはリロードされただけで実行されます。

記述2:アロー関数

対象要素.addEventListener('click', () => {処理内容})

関数を呼ばずにすぐ処理したい場合この様に書きます
更に、記述1では括弧をつけないので引数が渡せませんが渡したい時は単にこの処理内容部分から普通に関数を呼べば良いです。

対象要素.addEventListener('click', () => {abcdef(引数)})
function abcdef(aaa){処理内容}

アロー関数の様な方法として無名関数というのもあって
対象要素.addEventListener('click', function() {処理内容}, false)

こんな感じでも記述できます
閑話休題

何処がクリックされたのか

話しを戻して、今回のやりたい事って何かというとつまるところ
「クリックしたgridにあるdivのどれが押されたか?」

ググってみたら「grid div 要素数える」とか色々検索したけどgrid.jsがとかライブラリjsでindexでとかArray.fromで要素を配列に変換したあとにArray.prototype.indexOf.call("集合の要素", "目的の要素")で数えるとか

・・・いやそんな面倒臭いはずは…だってまずforで要素探して数えてるiさえ関数に渡せればいいだけなのになぁとちょっと迷走

今回の実装方法

const target = document.getElementsByClassName('mybox'); 
for(let i = 0; i < target.length; i++) {
    target[i].addEventListener('click', ()=>{boxclick(i)});
}

クリックイベントを検知してそのカウンタiをただの関数に投げるだけ。
自分が求めていたのはこれだけなのに全然すっきりした答えが出て来なくて無駄な遠回りをした。
今回対象にしたのはClassNameです。
同じClass名を対象に丸ごと持ってそれを対象にクリックされたかを調べ発火
アロー関数先の関数に引数を渡してただ呼び出すだけ

記述位置

記述は操作する対象が存在してなければ実行できません
例えば、ヘッダーにいれたjsに書くと対象がロード前で実行失敗になります

つまりhtmlの一番下に書けばOKって事です
・・・ですが面倒臭いですよね

その場合はロードが完了後発火する所に記述すればいいわけです
一番手っ取り早いのは

window.onload=function(){ページロード後に実行したい関数()}

飛ばした関数の中に記述すればOK


2025年1月11日土曜日

javascript配列の値渡しと参照渡し

 今回は簡単な備忘録


超簡単に説明するとデータが複数の情報を内包するもの「オブジェクト型」のもの、更に言えば「配列」「連想配列」等も含むものは参照渡し。

逆に単一データを保持する変数は値渡し。


厳密な言い回しは他に行ってもらうとして、ここにたどり着く人は配列の挙動がおかしいって人の為の対応について。

ーーーーーーーーーーーーーー変数代入

var a=[1,2,3,4]
var b=a.slice()

ーーーーーーーーーーーーーー
以上、終わり。


ーーーーーーーーーーーーーー関数引数

var a=[1,2,3,4]
var b=aaa(a.slice())
function aaa(a){}

ーーーーーーーーーーーーーー
以上、終わり。


■簡単な説明説明
今回、知りたいだろうことは配列を参照渡しではなくて値渡ししたい。
もっと言えば、参照先じゃなくて

・配列を別の変数にコピーをしたい
・関数に配列のコピーを渡したい

この二つだと思うので、処理後に新規配列で生成されるメソッド系を使えばOK
sliceは文字や配列を切り出す処理なので、引数に何も渡さない場合は配列を変更せずにコピーを渡すだけの処理になります。

変換の仕方がスマートかと言われるとまぁ微妙な所ですが、まあ一番手っ取り早い方法は確実にこれ。


■すっごい雑な補足

javascriptの場合はもうちょっと具体的に言うと「参照の参照渡し」を行っている
とはえ、おそらく他でも参照渡しで「概ねあってます」という形だと思う。

結局の所、代入先で値を変更すると代入元も値がかわるので・・・



もっと楽な方法

ーーーーーーーーーーーーーー変数代入

var a=[1,2,3,4]
var b=[...a]

ーーーーーーーーーーーーーー
以上、終わり。


ーーーーーーーーーーーーーー関数引数

var a=[1,2,3,4]
var b=aaa([...a])
function aaa(a){}

ーーーーーーーーーーーーーー


■すっごい雑な補足
この「...」って何?これはスプレッド構文といいます。
配列やオブジェクトの一番外側の括りを展開します。
データとしては上記aなら「...a」と記述した場合「1,2,3,4」ベタ要素が展開されます。
この展開されたベタ要素を受け取ることが出来るのは今言った通り配列かオブジェクト、あとは関数の引数です(ジェネレーター関数周りで使う事もありますが覚えなくていいです)。

■つまり?
「[...a]」と記述すると「[1,2,3,4]」この様に配列内で要素が展開されて名前のない配列が新しく作成されることで結果的に新しい配列を変数に代入したり関数の引数に渡す事が出来ます。



■あと注意点として、どちらも「浅いコピー」です。ネストされた配列やオブジェクトは結局参照が渡されます。ですので1次配列やオブジェクトの階層が1つしかない場合のみという事は留意してください。

2024年12月9日月曜日

Grid Layout(display: grid)でのグリッドデザイン

 さて、前回「タグの意味とデザインタグ」という記事を書きました。
webデザインを行う時にdivとspanこの二つが何のためのタグなのかと、ちゃんとしたデザインタグというものは存在せずタグとして使用用途として無色であるdivとspanが結果的にデザインのタグに使われている。

・・・という感じの内容でした。
今回は、divに新しく追加された・・・いや2011年ごろからの実装なので全然はるか前なのですがこれまでよりデザインに特化した方法について説明します。

実は家にあるDWはCS4なので対応してなかったからずっと放置していたんですが、ついに手を付け始めました。

概念としてはdivをtableの様に格子状に管理してデザインするというものです。
まぁ実際問題として、いにしえのtableデザインは扱いやすかったという事ですね。

floatで浮かして並べて、clear:both; で浮かすのをやめるみたいな構造は確かに分かりにくい。少なからずDWでtableの格子を繋げたり切ったりする方がデザイン的に直感的に見えてしまう。

display: gridはこの格子デザイン概念の良い所だけを得られます。
しかも、子要素のdivはグリッドの好きな位置へ移動でき、逆順にするなども容易。
レスポンシブなデザインとも親和性が高い。

gridの格子概念を理解する

基本構造は簡単です。親要素のdivを指定すると、中のdivを全てグリッドに並べてくれます。
親のdivはgridの列を指定すれば良いだけです。
行(row)の概念もありますが、まずは列がどのように処理されるか分かれば理解できると思います。

「display:grid;grid-template-columns: 20px 20px 20px;」


<div style="display:grid;grid-template-columns: 20px 20px 20px;background-color:#eee">
<div style="background-color:#FAA">1</div>
<div style="background-color:#AFA">2</div>
<div style="background-color:#AAF">3</div>
<div style="background-color:#AAA">4</div>
</div>

1
2
3
4
この様になります。
親divの幅指定が内部全てに反映されます。
また、幅指定を3つだけしたので4つめからは次の行となります。

今回はpxで指定しましたが今回は新たな単位を覚えましょう。
「fr」です。

今まで固定幅は「px」、割合を指定する時は「%」を使っていました。
今回新たに「%」に変わる割合指定方法です。
frはfraction(分数)の略で、等分したときの割合です。
水溶液とかと一緒です。一般的な例でいればカルピスと水の割合は「1:5」みたいな。
上記の例をpxからfrに置き換えてみます。
「display:grid;grid-template-columns: 1fr 1fr 1fr」

<div style="display:grid;grid-template-columns: 1fr 1fr 1fr;background-color:#eee">
<div style="background-color:#FAA">1</div>
<div style="background-color:#AFA">2</div>
<div style="background-color:#AAF">3</div>
<div style="background-color:#AAA">4</div>
</div>
1
2
3
4
さてするとどうでしょうか幅指定は「1:1:1」等幅なのでこのようになります。
じゃあ今度は末端の幅を2frにしてみます。
<div style="display:grid;grid-template-columns: 1fr 1fr 2fr;background-color:#eee">
<div style="background-color:#FAA">①</div>
<div style="background-color:#AFA">②</div>
<div style="background-color:#AAF">③</div>
<div style="background-color:#AAA">④</div>
</div>
この様になります。
「1:1:2」の割合となるので①と②は同じ幅、③は2倍となります。
今までは3分割するなら「33%」で、「1:1:2」の比率にするなら「25%・25%・50%」みたいな割合を自分で計算してパーセントにしていましたがfr指定の登場で分割割合だけ指定すれば良くなったという事です。

また、1frは使用可能な領域の余りを等分します。
ですので、左を30pxで固定して残りを可変としたい場合は「30px 1fr」と指定します。
<div style="background-color: #eeeeee; display: grid; grid-template-columns:30px 1fr;">
<div style="background-color:#FAA">①</div>
<div style="background-color:#AFA">②</div>
<div style="background-color:#AAF">③</div>
<div style="background-color:#AAA">④</div>
</div>

このように一部分を固定して残りを可変にする事も可能。

2024年11月7日木曜日

乾電池とニッケル乾電池(充電式乾電池)

 ちょっと気になって調べた

電池は電極に使用する金属の組み合わせによって電圧が決まります。

アルカリ乾電池は、基準電圧に対して正極の二酸化マンガンが約0.3ボルト、負極の亜鉛が約マイナス1.2ボルトです。その差の1.5ボルトがアルカリ乾電池の電圧です。

これに対してニッケル水素電池は正極の水酸化ニッケルが約0.5ボルト、負極の水素吸蔵合金が約マイナス0.7ボルトです。その差の1.2ボルトがニッケル水素電池の電圧です。

したがって1.5ボルトのニッケル水素電池は、作ることが出来ないということです。

パナソニックのより引用元:(ニッケル水素電池はなぜ1.2ボルトなのですか? PZ18121)

0.3x1.2=1.5v アルカリ乾電池
0.5x0.7=1.2v ニッケル乾電池(充電式)

つまりこういう事で、なおかつこの電圧は仕組み上変更できない。
「じゃあ、電圧の足りない充電式の電池はどの機器に使えないじゃん」と思うかもしれないがそういう事でもない。

普通の乾電池も1.5vから使用中どんどん下がっていく、これについてもパナソニック(なぜニッケル水素電池は1.2ボルトで機器に使用できるのですか?PZ18089)に詳しく記載がある。
普通の乾電池の方が実は充電式よりも早く電圧が1.2vを切ってしまう。

通常の乾電池の方が電圧の下降速度が速く、ニッケル乾電池は1.2vで安定して長く充電の終わり際で大きく下降する。

つまり、ミニ四駆とか瞬間的に速度(回転率)が欲しいなら新品電池が(電圧前半)良いが、機器の持続動作でいえば実はニッケル電池の方が良い。

特に瞬間的な電圧よりも持続性を優先する機器(電圧による性能差がでない機器)、例えばリモコンや時計、ゲーム機などはむしろ充電式の方が良い。

逆に、運動エネルギー等に変換するならアルカリ乾電池(新品限定)に分がある。
上の例でいえばミニ四駆大会とかなら新品アルカリ乾電池を使うと良くて、テスト走行などを何度もさせる場合はニッケル電池を使うのがベストみたいになる。


なんでこんな事を調べてたかと言うと、単三電池で動く時計が止まってしまったので、電池カバーを開けたら1.5vの記載があって、充電池でいいかなぁとエボルタ(ニッケル電池)を見たら1.2vって記載があるので使って大丈夫かを調べてみた。

というそれだけの話でした。