- 北の空からみなみへ -
exblog staff

タグ:へっぽこ ( 5 ) タグの人気記事
コマンドプロンプトの1行ベンチマーク
2018年 03月 11日
先の1行FizzBuzzを反復実行して、所要時間から処理速度を判別しようとしたら、老化脳では「どのくらい?」という程度感覚が掴めなかった。
(また、処理出力をNULリダイレクトで隠してしまえばいいが、表示させてしまうと、100行×1000試行=10万行の彼方に開始時刻が流れ去っていて確認に困った)

なので、一行FizzBuzzの反復試行ルーチンのさらに前後に手を入れて、開始時間を表示し→計算反復の実行→終了時間を表示し所要時間を表示する。ように書き加えてみた。
(例によって後で再度使い回す場合のために、備忘録として記録)

説明文を簡便化するために、実行する本体を仮に FIZZBUZZとおこう(代入だね)。

実行土台となる CMD.EXE は、スイッチ /v:on で遅延変数展開ONでコマンドプロンプトを起動しておく。
(現在時刻変数 time の一行内の時間差から、所要時間を算出するのだから、これは必須。)
このところ、砂場遊びでは次の一行をコマンドプロンプト起動直後に入れるのがルーチン化している。(自動でこうなるようにできるが、それは禁じ手=職場PCなので)

color F0&cmd/v:on/k prompt $g ^&title SandBox

FIZZBUZZの1,000回実行コードはこんな感じ。

for /L %z in (1,1,1000) do FIZZBUZZ
これを画面に表示させずNULへ流すには、括弧で括ってリダイレクトアウト。
(for /L %z in (1,1,1000) do FIZZBUZZ)>nul

この前に現在時刻を取得し、深夜0時からのミリ秒数計算のコードをおく。(バッチ内でない限り、set/a で代入するための計算結果はコンソール表示されてしまうので、まとまるように書く)

バッチ出力を最低限の改行で済ませるために、コードは set/p&nul="改行なし表示出力" の形式を多用。
投稿ボックス内で自動改行されると空白などが分かりにくいから横長表示で。
(ついでにマウスオン解説表示も多用)
この上には表示や計算結果や解説が表示されます。
set/p&nul=start=%tilme%[
start= 01:23:45.67[5025670]
set/p&nul=start=%tilme%[
&set stime=%time%
変数 stime(=start time) に時刻を代入(※実のところ1行処理なら無用=後で解説)
set stime=%time%
&set/a start
変数 start にミリ秒変換した開始時刻を代入(表示もされる)
set/a start
=1000*(!stime:~0,2!*3600+(1!stime:~3;2!-100)60+(1!stime:~6,2!-100)
start= 01:23:45.67[5025670] "時刻文字列"を秒数に
!stime:~0,2!*3600+(1!stime:~3;2!-100)60+(1!stime:~6,2!-100)
)+1!stime:~-2!0-1000
start= 01:23:45.67[5025670]
+1!stime:~-2!0-1000
&echo.]
start= 01:23:45.67[5025670]
echo.]
&

後半もほとんど同じコードですね。
この上には表示や計算結果や解説が表示されます。
&set etime=!time!
変数 etime(=end time) に時刻を代入完了「直後」の時刻
set etime=!time!
&set/p&nul=end=!etime![
start= 02:08:45.67[7725670] average=270ms
set/p&nul=end=!etime![
&set/a end
変数 end にミリ秒変換した処理完了時刻を代入(表示もされる)
set/a end
=1000*(!etime:~0,2!*3600+(1!etime:~3;2!-100)60+(1!etime:~6,2!-100)
end= 02:08:45.67[[7725670] average=270ms "時刻文字列"を秒数に
!etime:~0,2!*3600+(1!etime:~3;2!-100)60+(1!etime:~6,2!-100)
)+1!etime:~-2!0-1000
end= 02:08:45.67[7725670] average=270ms
+1!etime:~-2!0-1000
&set/p&nul=] average=
end= 02:08:45.67[7725670] average=270ms
set/p&nul=] average=
&anp;set/a ltime=(!end!-!start!)/1000
end= 02:08:45.67[7725670] average=270ms(計算結果は改行なしで表示される)
set/a ltime=(!end!-!start!)/1000
&echo.ms
end= 02:08:45.67[7725670] average=270ms
echo.ms

あとは、前半コード+FIZZBUZZコード+後半コード のサンドイッチで出来上がり。
ということで職場で私が触れる範囲のPCやWSでベンチマークを実行してみました。(結果はmore欄で)

ですが、この投稿にまとめていて気づいたのです。
遅延変数展開下の環境では、変数参照に !変数!(遅延展開=実行時に動的変数として展開)と %変数%(時即時展開=コマンドライン改行の瞬間に一行内の変数は確定)とが、「どちらも」使えるのでした。

つまり、サンドイッチに挟まなくても、
(処理) & (終了時刻「!time!」・開始時刻「%time%」で所要計算)
みたいにすれば、より簡明な記述ができたのですね。。。。orz...

さらにループ回数を柔軟に設定できるように書き換えると、こうなります。
(実地検証でこけまして修正)
(if not defined $count set/a $count=1000)&(for /L %z in (1,1,!$count!) do FIZZBUZZ)>nul&set etime=!time!&set/p<nul=%time%-!etime!/!$count!Loop Average = &set/a ltime=(1000*(3600*!etime:~0,2!+60*(1!etime:~3,2!-100)+1!etime:~6,2!-100)+1!etime:~-2!0-1000-(1000*(3600*%time:~0,2%+60*(1%time:~3,2%-100)+1%time:~6,2%-100)+1%time:~-2%0-1000))/!$count!&echo ms
1日を跨ぐ場合には、ltime が負数になるので、86400000を加え86400000で剰余を取るなどして正数化したミリ秒数を 試行回数 $countで割る。このように扱う必要もあるけど、そこまで使うケースはおそらくない。
(if not defined $count set/a $count=1000)&(for /L %z in (1,1,!$count!) do cmd/q/v:on/c"for /L %a in (0,1,6) do for %A in (1 2 Fi 4 Bu Fi 7 8 Fi Bu 11 Fi 13 14 FizzBu) do if %a11==6%A (exit) else if %A gtr a (echo.%Azz) else set/a $=15*%a+%A&echo.")&set etime=!time!&set/p<nul=%time%-!etime!/!$count!Loop Average = &set/a ltime=((1000*(3600*!etime:~0,2!+60*(1!etime:~3,2!-100)+1!etime:~6,2!-100)+1!etime:~-2!0-1000-(1000*(3600*%time:~0,2%+60*(1%time:~3,2%-100)+1%time:~6,2%-100)+1%time:~-2%0-1000))+86400000)%86400000/!$count!&echo ms


じっこうけっか と 小さな説
[PR]
by bucmacoto | 2018-03-11 22:09 | &Tips;&code; | Trackback | Comments(1)
砂場遊び(1行FizzBuzz)/後片付け
2018年 03月 08日
砂場遊び(1行FizzBuzz)

bugmacotoとも言うべきお間抜けコードを前投稿(more欄)で晒した。Windows実機でちゃんと動くコードに直したので再掲する。
なお、ちょこっとずつ解説入れておこう。(自分でも後から可読する自信はないらしい)

まずは、最短(なんちゃってなので105までカウントアップが突っ走り)なBizzBuzz方言版

cmd/q/v:on/c "for /L %a in (0,1,6) do for %A in (1 2 i 4 u i 7 8 i u B i D E izzBu) do if %A gtr f (echo.B%Azz) else set/a $=15*%a+0x%A&echo."
142 ↑ byte
    cmdのオプション
  • /q : echo off状態で起動
  • /v:on : 変数の遅延展開を有効にした状態で起動
    バッチで SETLOCAL ENABLEEXTENSIONS 宣言下と同じ状態
  • /c : 残り全部を引用符で囲まれたパラメータ(引数)を続けると、最初と最後の引用符が外されてコマンドライン上で実行される感じになる。
    (マイクロソフトのcmd.exeの文字列を扱う実装は、ちょっとずつ状況に応じて変化する=一貫性はない感じ。
     BASIC時代から、対称性の破れみたいな実装を好んでいる感じがする。)
    遅延展開有効下での例 : 比較文字として キャレットで「|」と引用符「"」を比較
    引用符にキャレットって無効では?(なんと有効でした。むしろ \" なんて書くとエラーします)

    > if ^| gtr ^" echo !time: =0!
    20:00:00.00

    > if ^| gtr ^" echo.!time: =0!
    !time: =0!

    echoの後ろに、空白区切りとピリオド区切りとで、変数の拡張展開されたり、されずにそのまま文字列表示になったり。

次に16進アイデアを温存したままで、方言ではないFizzBuzzへの対応版
無理やりな文字列比較をして動くようになったバージョン
なお、16進コードの A~F は大文字でも小文字でもいいらしい。
さらには、引用符あっても(0x"F" と書くとそれは 15 になる)OKという実装。

cmd/q/v:on/c "for /L %a in (0,1,6) do for %A in (1 2 Fi 4 Bu Fi 7 8 Fi Bu "b" Fi "d" "e" FizzBu) do if %A gtr "f" (echo.%Azz) else set/a $=15*%a+0x%A&echo."
156 ↑ byte
    if での比較は、記号 < 数字 < 文字
    (同じ文字なら大文字が大きいが、大文字の A は小文字の b より小さいという定義 )
  • 例 :
    >if b gtr A echo
    ECHO は <ON> です。
  • set/a での変数へ代入する計算結果はバッチ内では結果は表示されないが、コマンドライン上では表示される。
    なお、改行はされない。
    (echo ではなく set/p<nul= で出力したのと同様な吐き出し方をしてる)
  • set/a $= : $は仮変数(ダミー)
    昨日の寝ぼけた式は間違いで、こちらがちゃんとしたカウントアップ。
  • &echo. : 数値は吐き出されても改行はされていない。なので空改行を入れている
    (echo だけでは 「ECHO は <ON> です。」が出るのはおなじみ)

文字比較式があんまりなので、16進数のアイデアを放棄したらむしろ短くなった。
これでかなりすっきりして、分かりやすくなった。

cmd/q/v:on/c "for /L %a in (0,1,6) do for %A in (1 2 Fi 4 Bu Fi 7 8 Fi Bu 11 Fi 13 14 FizzBu) do if %A gtr a (echo.%Azz) else set/a $=15*%a+%A&echo."
149 ↑ byte
  • >ここでは、gtr で 最小の英文字 a と文字比較している。
    より一般的にするなら、if %A geq a とすることで、変数%Aに代入された全ての文字(記号と数字以外)が処理対象となる。
    結果、else 以降は数字処理に専念となる。
  • %A=Fi : 後ろに zz で Fizz
  • %A=Bu : 後ろに zz で Buzz
  • %A=FizzBu : 後ろに zz で FizzBuzz

最後に仕上げ。

FizzBuzz問題は、1から100 という条件なので、それに対応する IF文を加える。
cmdの /c スイッチに続くのが引用符つきのパラメータの場合は、直後の空白を削っても正しく解釈されるのでそうした。

cmd/q/v:on/c"for /L %a in (0,1,6) do for %A in (1 2 Fi 4 Bu Fi 7 8 Fi Bu 11 Fi 13 14 FizzBu) do if %a11==6%A (exit) else if %A gtr a (echo.%Azz) else set/a $=15*%a+%A&echo."
173 ↑ byte
    遅延変数展開下とはいっても、%変数%の形式はしっかり有効なまま。
    (改行するまでは、展開=反映されない変数として普通の環境同様に扱われる)
  • if %a11==6%A (exit) ← これはOK
  • if %a%A==611 (exit) ← これNG
  • ※ なぜなら %a% という変数展開を優先されてしまうので 下の例では
    「%a%A」という4文字を 611 と比較する式に解釈される。
    そこで、たすき掛けにしてやるとまともに動いた。


(more欄のスクリーンショットは撤廃)→ 反復実行の前後時間から平均所要時間を算定の 1行ベンチマークテスト については次の投稿で。
[PR]
by bucmacoto | 2018-03-08 21:13 | &Tips;&code; | Trackback | Comments(0)
砂場遊び(1行FizzBuzz)
2018年 03月 08日
へっぽこ腕試しに、やってみた。(手元にwindowsマシンがないのでアイデアを練ること1日。コード作業に試行錯誤で数時間)
環境文字指定の自己参照、と、文字切り出し位置を変数で指定するのに for 文で変数を作ればできそうというアイデアで挑戦。
cmd/q/v:on/c "for /L %A in (1,1,100) do (set/a I=%A%3,Z=%A%5>nul&(for %B in (!I!) do set I=!I:~%B,1!)&for %B in (!Z!) do set Z=!Z:~%B,1!)&set $= &(if defined I set $=!$!Fizz)&(if defined Z set $=!$!Buzz)&set $=!$:~1!&(if not defined $ set $=%A)&echo !$!"
254 byte
実行結果
c0062295_19155944.gif

私にはこれが限界だったが、さすがはプロの方は更に100Byteほども小さく仕上げていた。(コードは他人様のですので実行結果だけ表示)
c0062295_19234540.gif

FizzBuzz問題についての、Wikipediaの解説

もう一日の熟考結果
[PR]
by bucmacoto | 2018-03-08 00:47 | &Tips;&code; | Trackback | Comments(0)
cmd.exe 砂場遊び /3.5~番外地後片付け
2018年 03月 07日
先の ワンライナー番外地 の投稿でキャレット使いまくりが恥ずかしいと書いた。

ふと文字列扱いのネタを思い出して引っ張り出してみた。
こちらは1月にXP上で変数の挙動テストした時のスクリーンショット。
c0062295_21053456.gif
なんだか分かりにくいが中ほど過ぎで、for /f "delims=" %c in (" 123 " 456 " 789 ") do echo %~c となっているところを見てほしい。引用符の最初と最後だけが外れるように見えている。

そういえば、cmd/exe へのパラメータ渡しは、引用符であればキャレット外せるのだと気付いた。

cmd/? でヘルプを読むと、次の部分の2番目に該当させればよさげである。
/C または /K が指定されている場合、スイッチの後の残りのコマンド ラインが
コマンド ラインとして処理されます。次のルールが引用符 (") の処理に使われます:
  1. 次のすべての条件に一致する場合、コマンド ラインの引用符が有効になり
    ます:

    - /S スイッチがない
    - 引用符が 1 組ある
    - 引用符の中に特殊文字がない
    (特殊文字は &<>()@^| です)
    - 引用符の中に 1 つ以上のスペースがある
    - 引用符の中の文字列が、実行可能ファイルの名前である

  2. 最初の文字が引用符であるにも関わらず上の条件に一致しない場合は、最初
    の引用符とコマンド ラインの最後の引用符が削除され、最後の引用符の後
    のテキストが有効になります。
そうして、以下のように修正した。(パス数も99を上限に)
cmd/q/v:on/k "prompt $g &for /L %i in (101,1,199) do set i=%i&for /F "delims=;" %p in ("!PATH!") do set $path!i:~1!=%p&set path=!PATH:%p;=!&if not defined PATH set $path&set/a i-=100>&2&exit !i!"
(この実行環境はWindows 7)
c0062295_15264611.gif


これで更に連結すると、パス文字分割ワンライナープログラムが一丁あがり
c0062295_15340265.gif

cmd/q/v:on/k "prompt $g &for /L %i in (101,1,199) do set i=%i&for /F "delims=;" %p in ("!PATH!") do set $path!i:~1!=%p&set path=!PATH:%p;=!&if not defined PATH set $path&set/a i-=100>&2&exit !i!">tmp.txt&(for /F "delims=" %A in (tmp.txt) do set %A)&del tmp.txt

で、やってみて気付いたのだが、ヘルプの中で、「最初と最後の引用符」みたいに書いてあったが、上記の場合にはちゃんとcmd.exe へ渡すパラメータの外で、連結されていた。(ちゃんと動いている)
引用符直後にリダイレクト記号があるからなのかわからないが、しばらく砂場遊びを離れてから後で見直すと、戸惑うかもしれない。

※追記※ 上記を 遅延環境変数展開(CMD.exe /v:on)下で実行してはなりません。(バッチ流用時には要注意です)
 理由 = !PATH! の文字が引用符内で展開されてコマンド(cmd/q/v:on/k)に渡されてしまいます。
(この現象確認は、Windows XP SP3 においてなされました)


xp上で動くchoice コマンドもどきは改善途上だが、少しだけ進化
[PR]
by bucmacoto | 2018-03-07 18:15 | &Tips;&code; | Trackback(1) | Comments(0)
砂場遊び /ワンライナー番外地
2018年 03月 04日
エラーレベル(%errorlevel%)をセットするのに cmd/c exit <数字> とやると、任意の数のエラーレベルを戻り値として返すことができると気づいた。

そこで、パス文字列を 擬似配列変数に分解し、エラーレベルとしてパス文字のフォルダ数を返し、パスの擬似配列リストを標準出力するワンライナーを書いてみた。
(windows 10 と XP で動作確認)

キャレットエスケープ使いまくりなのが恥ずかしい。
cmd/q/v:on/k prompt ^^^> ^&path=%path%;^&path=!path:;;=;!^&for /L %i in (1001,1,1999) do set/a $i=%i^>nul^&set $i=!$i:~1!^&for /F "delims=;" %p in ("!PATH!") do set $PATH!$i!=%p^&set path=!PATH:%p;=!^&if not defined PATH set/a $c=%i-1000^>nul^&set $PATH^&exit !$c!
 ↑ これは1行なのでメモ帳的には下の感じ。
cmd/q/v:on/k prompt ^^^> ^&path=%path%;^&path=!path:;;=;!^&for /L %i in (1001,1,1023) do set/a $i=%i^>nul^&set $i=!$i:~1!^&for /F "delims=;" %p in ("!PATH!") do set $PATH!$i!=%p^&set path=!PATH:%p;=!^&if not defined PATH set/a $c=%i-1000^>nul^&set $PATH^&exit !$c!


more /ちょこっと解説
[PR]
by bucmacoto | 2018-03-04 04:58 | &Tips;&code; | Trackback(7) | Comments(0)