競技プログラミングのためのF#入門
1 2 3 |
|
たまには完全命令型の処理も紹介します. アルゴリズムは公式解説とは少し変えます. まずは各行を左から順に処理して右端に集め, 残った右端は上から処理する形に変えます. 文章でわかりにくい場合は以下の実装を見ればすぐにわかるでしょう.
ちなみに入力をいちいち書き換える形で実装しているため, REPLで実行するたび入力の価を読み込み直すのが面倒でした.
Xa
¶特に深い理由はありませんが, ここではResizeArray<string>
としてはじめから最終的に返す文字列の形で積みます. ついでに文字列生成関数も作ります.
1 2 |
|
入力の値が奇数な場合, そこを-1
して書き換えつつ, 右の価に+1
して書き換えます.
1 2 3 4 5 6 |
|
F#のジェネレーター(?)は0..H
で[|0..H|]
とH
まで作ってくれます. PythonやRustと挙動が違うため注意してください. はじめに書いたように一筆書き形式ではなく各行は右端で処理を止めるため, 列に関して0..W-2
としている点に注意してください.
他の言語では+=
で簡単に+1
できる部分がいちいち全て書かなくてはいけません. ただこれは2022年時点で変更可能な変数が起こしてきた事故を受け, いろいろな言語は不変な変数を導入しています. 特にF#と同じく何も書かなければ標準で不変な仕様にしている言語さえ増えています. やってほしくない処理を面倒にしてそもそも敬遠させる言語設計です. これを極端にしたのがHaskellのモナド機構です.
単純に右端を上から処理します.
1 2 3 4 5 |
|
特に言うべきことはないでしょう. 強いていうならIa.[i].[W-1] <- Ia.[1].[W-1]-1
の更新は不要です.
これも特に言うことはありません.
1 |
|
ここでは先頭の奇数行を左から読み, 偶数行を右から読む形にします. 配列処理上は一行目が配列の零行目になるため偶奇が反転します. もちろん配列の零行目を右から読み始めても構いません.
1 2 3 4 5 |
|
反転させている部分は添字を持っていないと面倒なため, 値だけではなく添字も持たせています. ついでに添字は入力Ia
の添字ではなく, 問題指示の1
-originの添字に変換しています. 一筆書き仕様に変えているため, 最後にArray.concat
を使って二重配列から単なる配列に変換しました.
fold
処理の大枠¶一筆書きのJa
を使ってfold
で処理します. 値の入れ替えは文字列化してリストで記録します.
問題は値の入れ替えともとの配列の値を見た書き換え処理です. もともと偶数であったとしても隣を書き換えた結果, 奇数として処理する必要が出てきます. 入力の配列の価を書き換えずに処理するには前の項の偶奇をfold
に積みます.
これらをまとめるとfold
で取り回す値は([],true,(0,0))
とすればよいでしょう. はじめの値が変更した場所を積むリスト, 次の真偽値は前の値の偶奇判定結果, 最後の値は入力の配列の添字です.
これをもとに大枠は次のように書けます.
1 2 |
|
最終的には最小処理回数も返す必要があり, それはリストの長さであるため, 文字列化して先頭に積みます. 破壊的な処理ではResizeArray
にAdd
で積みましたが, 今回はリストにcons
で積んだため最後にList.rev
が必要です.
fold
の中身¶前の値が偶数か奇数か, 新たな値が偶数か奇数かで四通りの判断が必要です. 言葉よりも実装を見る方が速く正確でしょう.
1 2 3 4 5 6 7 |
|
前の値がtrue
, つまり偶数だったなら変更はなくacc
に値を積みません. ただしfold
で新たに来た値にその真偽を積み, 奇数だった場合は次の処理で変更を積みます.
前の値がfalse
だったときを考えます. もとの値v
が偶数だと変更処理が入って奇数になるため, b
にfalse
を積む必要があります.