参加しました
参加しました。
午前
今回は前回の続き、ぷるさんによるJavaScriptの非同期処理についてでした。
例とかを出しながら解説していただきました。JSは各機能の名前ぐらいしか知りませんでしたが、いくつかの単語について分かったので良かったです。ところでニコ生感ありましたね
午後からの会場に移動しないと行けなかったので最後の方は見れなかった…もう一度見直そうと思います。
午後
午後はいつものようにつらい問題を解きました。問題と解答はここです。
とてもつらかったです
時間中はbash/zshのみでの解答をしてましたが、参加記ではPowerShellでの解答にもチャレンジします。
Q1
Shift-JISで書かれたCSVを読み、トマト、ピーマン、バナナについて最後に書かれている日付と個数を出力する問題
$ cat Sh*/vol.45/data.csv|iconv -f sjis|sort -V|tr -d \r|awk -F, '{box[$2]=$1" "$3" "$2}END{for(x in box)print box[x]}' 2019/12/30 4個 トマト 2019/12/21 5個 バナナ 2019/11/21 32個 ピーマン
iconv -f sjis
を使ってShift-JISからUTF8に変換します。次にsort
の-V
を使って各レコードを日付順に並べ替えます。最後にawk
の連想配列に入れて出力しています。
$ (cat -Encoding sjis .\ShellGeiData\vol.45\data.csv) -replace "/(.),","/0`$1,"|sort|% -begin {$box=@{}} {$L="$_".Split(',');$box[$L[1]]="$_"} -end {$box} Name Value ---- ----- バナナ 2019/12/21,バナナ,5個 トマト 2019/12/30,トマト,4個 ピーマン 2019/11/21,ピーマン,32個
PowerShellでも方針は同じにしました。解説しています。
PowerShell で cat
は Get-Content
へエイリアスされています。 Get-Content
の -Encoding
オプションでエンコードを指定できます。sjisを指定して data.csv
を読み出します。
sort
を使って日付を並べ替えたいですが、日付順にソートができなさそうだったので、日付を文字列としてソートしたいです。そのために、0埋めします。 -replace
を使います。replaceの引数は old,new
という感じで置換文字列を指定します。正規表現も扱えて、キャプチャーも可能です。今回だと (.) で1文字を引っ掛けて、後ろの `$1 で参照しています。
置換後の出力を載せておきます
$ (cat -Encoding sjis .\ShellGeiData\vol.45\data.csv) -replace "/(.),","/0`$1," 2019/12/06,トマト,3個 2019/11/23,バナナ,2個 2019/11/08,ピーマン,31個 2019/12/30,トマト,4個 2019/11/02,トマト,1個 2019/12/09,バナナ,4個 2019/12/21,バナナ,5個 2019/11/21,ピーマン,32個 2019/12/01,トマト,7個
これで sort
すると日付順に並ぶので、後は ForEach-Object
で連想配列に入れて出力しています。
Q2
日経平均株価の日時データから毎月の終値の最大値と最小値を求める問題
$ cat nikkei_stock_average_daily_jp.csv|iconv -f sjis|sed 1d|awk -F, 'NF>=2{print $1,$2}'|awk -F"[/ ]" '{print $NF,$1","$2}'|tr -d \"|sort -k2,2 -k2,1|pee "uniq -f1" "tac|uniq -f1"|sort -k2|xargs -n4|awk '{print $2, $1,$3}' 出力略 ...
これもエンコーディングがShift-JISなので変換します。1行目はヘッダーなので、sed
を使って削除します。次に awk
で1カラム目と2カラム目を取り出します。入力がCSVなので -F,
でフィールドセパレーターをカンマにしています。
sort
の -k
でキーを指定して並び替えます。日付で並べ替えた後、値で並べ替えます。 pee
に渡します。pee
では uniq -f1
と tac|uniq -f1
にSTDINから受け取った文字列を渡し、最大値と最小値のレコードを取り出します。そして日付で sort
し直し、xargs
,awk
で整形して終わりです。
PowerShellでもやってみます
$ Import-Csv -Encoding sjis .\nikkei_stock_average_daily_jp.csv|? {$_.終値 -gt 0}|% {@{D=(($_.データ日付) -replace "/..$",""); V=$_.終値}}|% -begin{$box=@{}} {if($box.Contains($_.D)){$box[$_.D].Max=[System.Math]::Max($_.V, $box[$_.D].Max); $box[$_.D].Min=[System.Math]::Min($_.V, $box[$_.D].Min)}else{$box[$_.D]=@{Max=$_.V; Min=$_. V}}} -end {$box.Keys|%{"$_"+",Max="+$box[$_].Max+",Min="+$box[$_].Min}} 出力略 ...
長いな…。解説します。
Import-Csv
を使うと1行目をカラム名としたオブジェクトの配列を作ってくれます。
$ Import-Csv -Encoding sjis .\nikkei_stock_average_daily_jp.csv|head -n15 データ日付 : 第一カラム 終値 : 第二カラム 始値 : 第三カラム 高値 : 第四カラム 安値 : 第五カラム ...
最後の注意書きの部分もパースされちゃうので、そこを削除するために ?
(Where-Object
) を使って除外しました。日付のフォーマットを整えつつ日付と終値のペアを作り、それぞれのMaxとMinを取っていきます。最後に出力して終わりです。Max,Minを取るところがちょっと冗長なのが辛いですね。
Q3
flags_a
と flags_b
には国旗が書いてある。2つのファイルの間で左上から数えて何個目の国旗が違うかを出力する問題。なんだこれは
$ cat Sh*/vol.45/flags_a|grep -o .. | diff - <(grep -o .. Sh*/vol.45/flags_b)|xargs -n3|awk -F"[a-z]" '{print $1}' 39 65
国旗は2文字の組み合わせで表現されるので、2つのファイルを grep -o ..
で2文字ずつに分解し、diff
にかけます。後は出力を整形して終わりです。
PowerShellでもやってみます。
$ diff ([System.Text.Encoding]::UTF8.GetBytes( (cat .\ShellGeiData\vol.45\flags_a))|xargs -n8|nl) ([System.Text.Encoding]::UTF8.GetBytes( (cat .\ShellGeiData\ vol.45\flags_b))|xargs -n8|nl)|sls "=>"|%{$_}|awk 'NF>=1{print $2-1}' 36 65
こっちでは grep -o ..
できれいに分割できなかったので、バイト列に変換し、8バイトずつに切り分けてから diff
しました。ちょっと整形に awk
使ってます…セパレートするの面倒なんですよねPowerShell
Q4
ワタナベエンコードされた文字列をIVSを考慮しながら1文字ずつに分解する問題。母語じゃん
$ cat Sh*/vol.45/nabe|perl -C -lne "print for /\X/g" 部 邊 邊󠄓 邊󠄓 邉 邉󠄊 邉󠄂 邊 邊󠄓 邊󠄓
perl
の \X
を使いました。書記素クラスタを考慮した文字列分解がこれで可能です。 uconv
を使ってユニコード正規化し、grep -o .
で分解できるかな〜と思ったんですけどダメでした。
PowerShellでもやってみます
$ [System.Globalization.StringInfo]::GetTextElementEnumerator((cat .\ShellGeiData\vol.45\nabe)) 部 邊 邊 邊 邉 邉 邉 邊 邊 邊 邉
(うちのターミナルだと文字化けちゃったのでここでは整形してます。)
.NETのDLLが使えるのでこっちでも楽ですね。System.Globalization.StringInfo.GetTextElementEnumerator
を使うと書記素クラスタを意識した文字列分割ができます。比較的シェルフレンドリーですねワタナベエンコード
Q5
文字列の部分文字列から回文を列挙する問題。全問までで疲れ果てていたので時間内に解けてない。
この問題はやり直してみたところ解答とほぼ同じになった(ruby
ではなくxargs
でecho @ $(echo @|rev)
したぐらい)なのでbash/zshは省略で
PowerShellでもやってみます。
$ cat ./ShellGeiData/vol.45/message|%{for($i=0;$i -le $_.Length;$i++){$_.SubString(0,$i)}}|%{for($j=0;$j -le $_.Length;$j++){$_.SubString($j)}}|Sort-Object -Unique|?{$_.Length -ge 2 -and $_ -eq ([string]::Join("",$_[($_.Length-1)..0]))} おかしがすきすきすがしかお かしがすきすきすがしか がすきすきすが きすき きつつき けやぶやけ しがすきすきすがし すきす すきすきす たけやぶやけた つつ とまと やぶや ゆんゆ んゆん
2回の ForEach-Object
で部分文字列を全列挙してから Where-Object
で条件に合う文字列を絞り込んでいます。stringにはReverse()
メソッドが生えてると思ってたんですけどあれはLinq
のだったんですね…。ということで [string]::Join("",$_[($_.Length-1)..0])
でReverseした文字列を作っています。
Q6
Q5の答えから部分文字列を削除する問題。例えば「つつ」は「きつつき」の部分文字列なので削除する。という感じですね。やってみます。
$ cat ./vol.45/message.ans|xargs -I@ grep -o @ ./vol.45/message.ans|sort |uniq -c|awk '$1==1{print $2}' おかしがすきすきすがしかお きつつき たけやぶやけた とまと ゆんゆ んゆん
xargs
で1レコードずつ grep
に渡します。-o
をつけているので、マッチした部分だけ出力されます。こうすると部分文字列は複数マッチし、そうでない文字列は1度しかマッチしません。そこで sort|uniq -c
で個数とともに出力し、awk
で整形して出力しています。
PowerShellでもやってみます。
$ cat ./ShellGeiData/vol.45/message.ans|%{cat ./ShellGeiData/vol.45/message.ans|sls $_|%{$_.Matches.Value}}|group|?{$_.Count -eq 1}|select Name Name ---- おかしがすきすきすがしかお きつつき たけやぶやけた とまと ゆんゆ んゆん
sls
は Select-String
へのエイリアスです。STDINなどの入力からマッチする部分をMatchInfo
型で返します。bash/zsh版でもやったようにマッチした部分だけほしいので MatchInfo.Matches.Value
プロパティを取得します。ここまでの出力を見てみます
$ cat ... $_.Matches.Value}} つつ つつ きすき きすき きすき きすき きすき きすき すきす すきす すきす すきす すきす すきす とまと やぶや やぶや やぶや ゆんゆ んゆん きつつき けやぶやけ けやぶやけ すきすきす すきすきす すきすきす すきすきす すきすきす がすきすきすが がすきすきすが がすきすきすが がすきすきすが たけやぶやけた しがすきすきすがし しがすきすきすがし しがすきすきすがし かしがすきすきすがしか かしがすきすきすがしか おかしがすきすきすがしかお
いい感じですね。ここからレコードの個数を取得して、個数が1であるレコードだけ出力すれば良さそうです。Sort-Object
なんかをこねこねすれば行けそうですが Group-Object
という便利なコマンドレットがあります。Group-Object
は レコードを GroupBy
し、GroupInfo
型を返します。GroupInfo
はグループに含まれるレコード数を表す Count
プロパティがあるので、これが1なグループだけ出力すればOKですね
Q7
循環少数を永遠に出力する問題。言い換えるとシェル上で割り算の筆算をする問題
$ echo 1 7|awk '{print "0."; for(i=0;i<10;i++){$1*=10; print int($1/$2); $1=$1%$2}}'|tr -d \n 0.1428571428
無限とのことだったんですが10桁にしました。無限にするなら while(1)
にすればいいですね。
やっていることはまぁ割り算の筆算を実装しただけですね。うん
PowerShellでもやってみます。
$ echo "1 7"|%{$L=$_ -split ' '|%{[int]$_};$X=$L[0];$Y=$L[1];for($i=0;$i -le 10;$i++){$X*=10;[int]($X/$Y);$X=$X%$Y}}|% -begin{$O="0."} {$O=$O+$_} -end {$O} 0.14396714396
特にややこしいことはしていません。
Q8
循環少数の何桁目から何桁目が循環部分になっているのかを出力する問題。この辺の記憶はない
$ echo 1 7|awk '{while(1){$1*=10; print int($1/$2), $1%$2; $1=$1%$2}}'|awk 'box[$2]==2{exit}{box[$2]++;if(box[$2]==1){printf $1}}'|awk '{print "0."$0, 1, length($0)}' 0.142857 1 6
同じ余りが出現すると循環少数が一周したことになるので循環が見つかるまで出力し続けて最後に長さとともに出力しています。しかしこれ 0.xyzxyz
みたいなパターンしか無理ですね
LT
今回もLTしました。聴いてくださった方々ありがとうございました!
ワタナベエンコーディング1周年ということなのでワタナベシェルを作りました
まとめ
今回もめちゃくちゃ疲れました。翌日まで披露が残るので大変です。設営、企画の皆様いつもありがとうございます。