久々のブログ更新だ。
いやはやClojureが楽しくてしょうがない。 特に何か作っているわけではないが、 ここ3、4ヶ月はHackerRank (https://www.hackerrank.com/) にハマって、パズル解く感覚でClojureを使って問題を解いて遊んでいた。
LISP言語はアルゴリズム&データ構造を試行錯誤するのに最適だな、と。 加え、Clojureは標準で扱えるデータ形式が豊富で、いろいろなマクロがあって シンタックスのバリエーションも多いから、書いてて楽しい。
さて、HackerRankの中で、シンプルでイージーだけど、 プログラムの表現方法を色々試せて楽しめた問題があった。
問題
https://www.hackerrank.com/challenges/mars-exploration/problem
[超概略]
火星探査機が故障したので地球にSOS信号を出しているが、宇宙線の影響でノイズが入ってしまう。
SOSSOSSOSSOS
と出したところ、
SOSSPSSQSSOR
と届いてしまう。このノイズの数を数えなさい。
回答例(C言語)
こういう系統の処理はC言語が得意なんだとなと思う。
#include <stdio.h> int main() { char string[256], *s = string; scanf("%s", string); int cnt = 0; while(*s) { if (*s++ != 'S') cnt++; if (*s++ != 'O') cnt++; if (*s++ != 'S') cnt++; } printf("%d\n", cnt); }
文字列を読むとこは置いといて、実処理while
文以下はポインタを使うとかなりすっきり書けて、自分はC言語のこういうところが好きである。
ポインターと変数を使うことで簡潔に書ける例だと思う。
じゃあ、Clojureだとどうなるのか?というと…
解答例(Clojure)
自分の中では、やり方は3つくらいはあって、その中で一番すっきり書けたのはpartition
を使うもの。
処理の内容をダイレクトに読み取れてC言語に負けないくらい簡潔。
(->> (read-line) (partition 3) (reduce (fn [cnt [c1 c2 c3]] (+ cnt (if (not= c1 \S) 1 0) (if (not= c2 \O) 1 0) (if (not= c3 \S) 1 0))) 0) println)
さて、上のコードはこれはこれで好きなのだが、最近cond->
なるものを知って、
これを使えばより短く、処理の意図もダイレクトに表現できることがわかった。
以下がcond->
を使った場合。
(->> (read-line) (partition 3) (reduce (fn [cnt [c1 c2 c3]] (cond-> cnt (not= c1 \S) inc (not= c2 \O) inc (not= c3 \S) inc)) 0) println)
”カウントする”という処理の意図がinc
を使うことで、よりダイレクトな表現になっている。
しかしまあ、Clojureはcoreライブラリだけでも、本当にいろいろマクロがあるなあ。と。
余談:解答例(Clojureその2)
遠回りで効率も良くないけど、以下のやり方もある。
(let [string (read-line)] (->> (repeat "SOS") (take (/ (count string) 3)) (apply str) (map #(not= %1 %2) string) (filter true?) count println))
この前の解答例は、与えられた文字列を前から順になめっていって文字を比較しているのだが、 このプログラムでは正解の文字列を作って、与えられた文字列と比較している。 (実はこれが最初に思いついた解なのだが^^;)
いやあ、 いろんなやり方をさくっと試せてしまうところが、Clojureの一番の強みで楽しいとこだな!と。