Code Aquarium

minazoko's blog -*- 水底のブログ -*-

(Clojure) 標準出力 - オンライン判定コード提出用テンプレその2

前回投稿では標準出力周りが微妙な感じで終わってしまいました。折角なのでどうすれば出力も楽になるか考えてみました。こんなテンプレになりました。

(def in-seq #(line-seq (java.io.BufferedReader. *in*)))
(def s->long #(Long/parseLong %))
(defprotocol Out (output [x]))
(extend-protocol Out
  clojure.lang.Sequential
  (output [x] (map println x))
  Object
  (output [x] (println x)))

(defn solve [lines]
  )

;;(dorun (-> (in-seq) solve output))

sequentialオブジェクトならば改行しながら要素を出力。それ以外はprintlnで単純出力をします。
問題によっては出力の型が常に一致しているとは限らないことがあります。
例えば「解があるときは列挙して、無いときは"NG"と出力」のようなケースです。
そいういう場合でもsolveはoutputのI/Fを気にすることなく解を返せるようになりました。

注意点としては「Javaのネイティブ配列は sequentialではない」ということです。解を配列で作成した場合にはsolveから返す前にseq関数適用する必要があります。 その辺りはClojureの少し分かりにくい話題になりますが、こちらなどが参考になります。
* 翻訳:"Inside Clojure's Collection Model" - 草の根Clojure日記

(Clojure) 標準入力 - オンライン判定コード提出用テンプレ

最近PaizaのオンラインハッカソンやCodeIQの出題コードを問いたりして遊んでいます。 この手のサイトでは標準入出力を使います。 出力はともかく標準入力読み取りを毎回考えて書くのは無駄なのでコピペできるテンプレを用意するのが常套手段のようです。

ところで、このページのClojureコード例を見てくれないか?
* 各言語の標準入出力サンプル|CodeIQ│CodeIQ
なんともムズムズするサンプルですね。

なぜコードが真っ赤なのか、は置いとくとして、ClojureはLazySequenceがお手軽に扱えるのが売りですから標準入力もシーケンスとして取得しておいたがよいでしょう。 それからuseは今となっては非推奨なオペレータなのでrequireにしましょう。

というわけで、自分ならこう書きます。お題は「すべての入力をupperして出力」です。

(def in-seq #(->> (repeatedly read-line)
                  (take-while identity)))

(require '[clojure.string :refer :all])

(dorun (->> (in-seq)
            (map #(upper-case %))
            (map println)))

repeatedly だけだと無限にreadしてしまうので、読み取り継続判定(終了判定)を付け加えています。 read-lineはEOFでnilを返すのでidentityで判定すれば「偽」で止まります。

通常clojureのコードでは一行目にネームスペース宣言があり、その後にモジュールrequire等が並びますが、今回の用途ではネームスペースは不要なので省略しました。 in-seq よりもrequireが下にあるのは書き換え頻度が低いものを上方にまとめるという方針。

サンプルの書き換えとしてはこんなものかと思いますが、実際に活用するとなるともう少し手を入れたくなります。 現時点では次のようなテンプレを使いまわしています。

(def in-seq #(line-seq (java.io.BufferedReader. *in*)))
(def s->long #(Long/parseLong %))

(defn solve [lines]
  )

;;(dorun (->> (in-seq) solve (map println)))

s->longを追加してあります。よく使う関数はこのように上の方にまとめていきます。
最後の行も定型文です。コメントアウトしてあるのは、emacsで間違ってバッファを評価してコードが実行され無限ループでフリーズみたいな状況を避けるため。提出時にはコメントを外します。

line-seqはjava.io.BufferedRederを受け取り行毎のlazySeqを返します。EOFに到達するとそこで読み取り停止してくれるのでこちらの方が少し楽です。

またはこちらのようにclojure.java.io/readerを使ってもよいですね。Javaクラスを剥き出しで使いたくなければどうぞ。
* 各プログラミング言語の標準入出力サンプル(ややマイナーなもの用) - Qiita

あとは、問題に応じてsolve関数を完成させるだけです。

追記

コメントアウトの最終行は出力が単一行だと面倒なことになりますね。 出力が一行の場合は。(map println) を println に書き換えるなりしないとダメでした。
実際どうしているのかというと、コメントアウトは2行あって出力によって使う方を選択していました。テンプレ化せずに出力はsolveの中に書いてしまってもよいかもしれないですね。

(PowerShell) 簡易 timeit

Windowsでコマンドの処理時間計測したい場合みなさんどうしてるんでしょうかね。
PowerShell使えばそういうコマンドがあるだろうと思ったのですが、いやあるにはあるんです Measure-Command が。

PS C:\wk> measure-command {ping localhost}

でもこのコマンド計測結果を返すだけで、対象コマンドの標準出力を隠してしまうんです。
Windowsコマンドの方でログを吐かせて、別のTerminalで cat -tail すれば良いかな、とも思ったのですがそれも手間。
結局、悩んだりググったりよりはコマンドを作ってしまった方が早いという結論。
こんなワンライナー

PS C:\wk> function timeit($cmd){$t0=(get-date); &$cmd; (get-date)-$t0}

こう使います。

PS C:\wk> timeit {ping localhost}

yourhostname [::1]に ping を送信しています 32 バイトのデータ:
::1 からの応答: 時間 <1ms
::1 からの応答: 時間 <1ms
::1 からの応答: 時間 <1ms
::1 からの応答: 時間 <1ms

::1 の ping 統計:
    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 0ms、最大 = 0ms、平均 = 0ms


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 3
Milliseconds      : 171
Ticks             : 31719497
TotalDays         : 3.6712380787037E-05
TotalHours        : 0.000881097138888889
TotalMinutes      : 0.0528658283333333
TotalSeconds      : 3.1719497
TotalMilliseconds : 3171.9497



PS C:\wk>

返却値は、Measure-CommandがTimeSpanオブジェクトを返し、
timeit は文字列のオブジェクトの配列を返すという違いがあります。