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などスクリプト系なのでコードがごった煮でごめんね:;(∩´﹏`∩);: