2013年12月29日日曜日

[OCaml]文字列のsplit

文字列を任意の文字列で分割するには、Str.splitを使う。
インタプリタで試してみると、

# let xs = Str.split (Str.regexp ",") "a,b,c,d";;
Characters -1--1:
  let xs = Str.split (Str.regexp ",") "a,b,c,d";;
 
Error: Reference to undefined global `Str'
#

怒られた。

Regular Expressions in OCaml を見て解決した。
インタプリタでは、str.cmaをロードしてから、Strモジュールを使う。

# #load "str.cma" ;;
# let xs = Str.split (Str.regexp ",") "a,b,c,d";;
val xs : string list = ["a"; "b"; "c"; "d"]
#

Listモジュールは何もしなくても使えたが、Strは明示的にロードしないと駄目なのか。
よく分からないことだらけだ。

冬休みなので、普段使わないOCamlの学習中。
関数型という同じジャンルのせいか、Haskellとかなり似ている言語だ。
Haskellの文法と、OCamlの文法が頭の中で混ってしまいそう。

2013年12月24日火曜日

[Scheme][Racket]RacketをDebian Wheezy 32bitにインストール

少しだけ読んで積読していたSICP(計算機プログラムの構造と解釈)、冬休みになるし、再読しようと思う。
書中では、Schemeを使うので、グラフィックの描画や、Webサーバの実装が簡単にできそうな、Racket を使おう。

Debian 32bit版にインストールしようとしたが、http://racket-lang.org/download/ を見ると、Linux X86_64 (Debian squeeze) はあるが、32bit版が無いので、ソースからインストールした。

インストール

http://racket-lang.org/download/ から Unix sourceをダウンロードする。

現在は racket-5.3.6-src-unix.tgz が最新版。

~/src/ にダウンロードしたソースを展開する。
$ cd src
$ tar zxf racket-5.3.6-src-unix.tgz

インストール方法は racket-5.3.6/src/README に書いてある。
展開したディレクトリのsrcディレクトリで、
$ cd racket-5.3.6/src
$ mkdir build
$ cd build
$ ../configure
$ make
$ make install

make install には結構時間がかかる。

configure に --prefix を付けていないので、
> If the `--prefix' flag is omitted, the binaries are built for an
> in-place installation (i.e., the parent of the directory containing
> this README will be used directly).
となり、~/src/racket-5.3.6 自身にインストールされる。

起動

racket-5.3.6/bin/ にパスを通して、drracket を実行する。

Emacsとの連携
http://docs.racket-lang.org/guide/Emacs.html を見ると Emacsのmajorモードもあるようなので、後で試してみよう。

参考

2013年12月21日土曜日

[Clojure][Emacs]MilkodeでClojureのソースを検索できるようにしよう

仕事では、メソッド名や変数名等をキーワードにして、複数のプロジェクトのソースを横断的に検索することが多い。
結構な規模で、ファイル数が多く、grepによる検索では非常に時間がかかり、効率が悪い。
Milkodeは全文検索エンジンgroongaを使った、ソースコード検索用のツールで、数万ファイルを対象にしても、瞬時に検索できる。
Webアプリとしても動作し、ブラウザを使って検索もできる。
検索するソースの追加も簡単なので、重宝している。

Clojureのソースを検索できるようにしよう。

インストール

MilodeはRubyアプリである。
gem でインストールできる。
rroonga(groongaのRuby用API)
$ gem install rroonga
$ gem install milkode

これでmilkコマンドが使えるようになる。

データベースの作成

$ milk init --default
create     : /home/satoshi/.milkode/milkode.yaml
create     : /home/satoshi/.milkode/db/milkode.db created.

Clojureソースの取得

srcディレクトリにソースリポジトリをcloneする。
$ mkdir src
$ cd src
$:~/src git clone https://github.com/clojure/clojure.git

ソースプロジェクトの登録

$ milk add clojure
package    : clojure
result     : 1 packages, 279 records, 279 add. (1.56sec)
*milkode*  : 1 packages, 279 records in /home/satoshi/.milkode/db/milkode.db.

登録したプロジェクトの確認

$ milk list
clojure ← ◆登録された
*milkode*  : 1 packages, 279 records in /home/satoshi/.milkode/db/milkode.db.

コマンドラインでの検索

gmilkコマンドで検索できる。

$:~/src cd clojure
$:~/src/clojure gmilk RETRY_LIMIT
src/jvm/clojure/lang/LockingTransaction.java:25:public static final int RETRY_LIMIT = 10000;
src/jvm/clojure/lang/LockingTransaction.java:28://public static int COMMUTE_RETRY_LIMIT = 10;
src/jvm/clojure/lang/LockingTransaction.java:252:    for(int i = 0; !done && i < RETRY_LIMIT; i++)

ソースのディレクトリ内でgmilkコマンドを実行しないと、検索できないことに注意。
ソースのディレクトリ外の場合は、パッケージ名をオプション指定する。
詳細は gmilkコマンドのマニュアルhttp://milkode.ongaeshi.me/gmilk.htmlを参照。

Webアプリでの検索

次のコマンドで検索用のWebアプリを起動する。
$ milk web
デフォルトではブラウザを起動する。

ブラウザを起動せずに、外部からの接続を許可した状態で起動するには、以下のコマンドを実行する。
$ milk web -n -o 0.0.0.0

詳細は milk help web コマンドを参照。

次は検索結果の画面。

Emacsでの検索

検索用のELispもある。
https://github.com/ongaeshi/emacs-milkode

auto-installが使えば、
(auto-install-from-url "https://raw.github.com/ongaeshi/emacs-milkode/master/milkode.el")
でインストールできる。

init.elに次の設定を追加する。
(require 'milkode)

検索するには
M-x milkode:search
を実行する。
ミニバッファに gmilk: と表示されるので、検索文字列を入力する。

図は -a RETRY_LIMIT を入力した例。VPSに接続したターミナル内のEmacsなので、少し表示が汚ない。

-a は 全プロジェクトを対象にする場合のオプション。
(-a を付けないと正しく検索できなかった。カレントディレクトリがソースのディレクトリ内ではないから?)

参考


2013年12月18日水曜日

[Clojure][Ruby]RougeでSelenium WebDriverを使う

Ruby + Clojure = Rouge
Selenium WebDriverを使ってGoogle検索してみた。

Rubyは最新版を使用した。
$ ruby -v
ruby 2.0.0p353 (2013-11-22 revision 43784) [i686-linux]

Rougeのインストールと起動

gemでインストールできる。簡単だ。
$ gem install rouge-lang
REPLを起動するには rougeコマンドを使う。
$ rouge
Rouge 0.0.15
user=>

ついついprintlnとやってしまいそうになるが、putsでhello。

user=> (puts "hello, Rouge!")
hello, Rouge!
nil
user=> ^Dで終了

Selenium WebDriverを使ってみる

gemでselenium-webdriverをインストールしておく。
$ gem install selenium-webdriver

Rubyのコード

require "selenium-webdriver"

driver = Selenium::WebDriver.for :firefox
driver.navigate.to "http://google.com"

element = driver.find_element(:name, 'q')
element.send_keys "Clojure Ruby Rouge"
element.submit

puts driver.title

driver.quit


Rougeのコード

(require "selenium-webdriver")

(def driver (Selenium.WebDriver/for :firefox))

(-> driver
  (.navigate)
  (.to "http://www.google.com/"))

(def element (.find_element driver :name "q"))

(.send_keys element "Clojure Ruby Rouge")
(.submit element)

(puts (.title driver))

;; (.quit driver) ;; ブラウザを終了させないようにコメントアウト


Rougeで実行

拡張子はrgのようだ。
上記のコードはgoogle.rgとした。

rougeコマンドに渡せば良い。
最初分からずに(load-file "google.rg")とやってみたが、
load-fileは定義されていなった。
https://github.com/rouge-lang/rouge/blob/master/lib/boot.rg
https://github.com/rouge-lang/rouge/blob/master/lib/rouge.rb
を見ても、load〜は定義されていないみたい。

$ rouge google.rg
Google

Firefoxが立ち上がり、「Clojure Ruby Rouge」を検索する。

感想

Rougeは起動が早くて良い。
Emacs ciderで接続できない。これはかなり残念。
https://github.com/clojure/tools.nrepl をrougeに移植すれば良いのかな?
でも大変そう。
Rubyの豊富なライブラリが利用できるのは便利だ。
エラーが起きても、該当行が表示されないので、デバッグが大変だ。

参考


2013年12月17日火曜日

[Emacs]リージョンを任意の文字列で囲むコマンドを作成した

Emacsで文章を作成していると、

hogehoge
fugafuga

--------------------
hogehoge
fugafuga
--------------------

と----で囲ったり、
Redmineのコメントで、

hogehoge
fugafuga

<pre>
hogehoge
fugafuga
</pre>

と書くことが結構多い。
指定したリージョンの前後に、任意の文字列を挿入できるコマンドがあると便利そうなので作ってみた。

使い方

リージョンを選択して、
M-x enclose-string
ミニバッファで
String(s/t/num):
と聞いてくるので、以下のいずれかを入力する。

(1)任意の文字列で囲む場合
最初の文字にsを指定する。
例: shoge
hogeで囲む。

(2)タグで囲む場合
最初の文字にtを指定する。
例: tpre
<pre>と</pre>で囲む。

(3)指定した回数を繰り返す文字列で囲む場合
例: 30-
------------------------------(「-」30個)で囲む。

ソース

(defun multi-string (n str)
  (loop repeat n concat str))

(defun pre-post-text-string (str)
 (list str str))

(defun pre-post-tag-string (str)
 (list (concat "<" str ">") (concat "</" str ">")))

(defun pre-post-multi-string (n str)
 (let ((n-str (multi-string (string-to-int n) str)))
    (list n-str n-str)))

(defun pre-post-string (str)
 "書式文字列に応じた前後の文字列を取得する。\n
前の文字列と後ろの文字列のリストを返す。\n
最初の文字が s の場合、それ以降の文字列を前後の文字列とする。\n
例: s--- → (\"---\" \"---\")\n
最初の文字が t の場合、それ以降の文字列をタグ名として扱う。\n
例: tpre → (\"<pre>\" \"</pre>\")\n
最初の文字列が数値の場合、数値の後ろの文字列を数値分繰替えした文字列を、前後の文字列とする。
例: 10* → (\"**********\" \"**********\")\n"
 (cond
  ((string-match "^s\\(.*\\)" str)
    (pre-post-text-string (match-string 1 str)))
  ((string-match "^t\\(.*\\)" str)
    (pre-post-tag-string (match-string 1 str)))
  ((string-match "^\\([0-9]+\\)\\(.*\\)" str)
    (pre-post-multi-string (match-string 1 str) (match-string 2 str)))))

(defun enclose-region (start end str)
  "リージョンの前後に文字列を挿入する。"
  (interactive "r\nsString(s/t/num):")
  (destructuring-bind (pre-str post-str) (pre-post-string str)
    (message (concat pre-str ":" post-str))
    (save-excursion
      (save-restriction
    (narrow-to-region start end)
    (goto-char (point-min))
    (insert pre-str "\n")
    (goto-char (point-max))
    (unless (bolp)
      (insert "\n"))
    (insert-before-markers post-str "\n")))))


Emacsは、手軽に機能を拡張できるので、便利だ。

2013年12月16日月曜日

[Clojure][Emacs]EmacsのCiderを起動するLeiningen pluginを作ってみた

$ lein cider
とすると、REPLを起動して、Emacsのciderコマンドを実行して、Emacs側でもREPLを起動するLeiningen pluginを作ってみた。

私はDebianを常用しており、Clojureでの開発には、REPLとして、Emacs上で動作するCiderを使用している。Emacsと同時に複数のターミナルを開いて、Emacsとターミナルの間を行き来している。
lein replは、ターミナルで起動して、Emacsからはciderコマンドで接続していた。
EmacsとターミナルのREPLを同時に使っていることも多い。
毎回ターミナルでlein replした後に、Emacsからciderで再接続するのが面倒なので、このpluginを作ってみた。

便利かなと思って作ってみたものの、cider-jack-inコマンドがあることを考えると、機能的には微妙なプラグインだな...

使い方

~/.lein/profiles.clj に lein-cider の設定を追加する。

{:user {:plugins [[lein-cider "0.1.0-SNAPSHOT"]]}}

Emacsをサーバーとして起動しておく(server-start関数を使用)。
プロジェクトのディレクトリで、
$ lein cider
を実行すると、Emacs上でciderコマンドが実行されて、REPLを起動する。

ソース

ソースはこちら
標準のREPL(lein repl)を起動後に、emacsclientコマンド経由で ELispのcider関数を呼び出している。
REPLが完全に起動した後に、Emacs側でcider関数を実行しないと、
IllegalAccessError pp does not exist clojure.core/refer
というエラーがでてしまうので、手っ取り早く、もう完全に起動した頃かなーという3秒後に、emacsclientコマンドを実行するようにした。

(defn server [project cfg headless?]
  (let [port (apply original-server-func [project cfg headless?])
        host (:host cfg)]
    (future
      (Thread/sleep leiningen.cider/WAIT_TIME) ; ←◆コレ
      (call-cider host port))
    port))


うーむ。まったくもって、行き当たりばったりな実装だ。

Emacsサーバー

こちらを見ると、
Note that since Emacs 23 this is the preferred way to use Emacs in daemon mode. (start-server) is now mostly deprecated.
というコメントがあった。
ほー。今はdaemon modeで使う方が良いようだ。

参考

2013年12月14日土曜日

[Clojure]lein-tryで外部ライブラリを使う

外部のClojureライブラリを使うには、project.cljのdependenciesに記述しなければならない。ちょっとお試しでライブラリを使用したい場合、これは面倒くさかった。

lein-tryプラグインを使うと、project.cljはそのままで、使用したい外部ライブラリを引数に渡すだけで、試すことができるようになる。

準備

以下のように~/.lein/profiles.cljを編集して、lein-tryプラグインを使えるようにする。
{:user {:plugins [[lein-try "0.4.1"]]}}

試してみる

satoshi@debian:~/workspace/clojure$ lein deps
Retrieving lein-try/lein-try/0.4.1/lein-try-0.4.1.pom from clojars
Retrieving lein-try/lein-try/0.4.1/lein-try-0.4.1.jar from clojars
Couldn't find project.clj, which is needed for deps

プラグインを読み込んだことを確認。

clj-timeの最新版を試してみる。

satoshi@debian:~/workspace/clojure$ lein try clj-time
Retrieving clj-time/clj-time/0.6.0/clj-time-0.6.0.pom from clojars ← clj-timeをロードしている
Retrieving joda-time/joda-time/2.2/joda-time-2.2.pom from central
Retrieving clj-time/clj-time/0.6.0/clj-time-0.6.0.jar from clojars
Retrieving joda-time/joda-time/2.2/joda-time-2.2.jar from central
nREPL server started on port 51459 on host 127.0.0.1
REPL-y 0.3.0
Clojure 1.5.1
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (require '[clj-time.core :as t])
nil
user=> t/date-time
#<core$date_time clj_time.core$date_time@6849b9>
user=> (t/date-time 2013 12 13)
#<DateTime 2013-12-13T00:00:00.000Z>
user=>

バージョン番号の指定もできる。

satoshi@debian:~/workspace/clojure$ lein try clj-time "0.5.1"
Retrieving clj-time/clj-time/0.5.1/clj-time-0.5.1.pom from clojars
Retrieving clj-time/clj-time/0.5.1/clj-time-0.5.1.jar from clojars
nREPL server started on port 49481 on host 127.0.0.1
REPL-y 0.3.0
Clojure 1.5.1
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=>

今まで、いちいちproject.cljにライブラリの設定を書いてからlein replしていたので、これはお手軽で、とても便利。

参考


2013年12月13日金曜日

[Clojure]マルコフ連鎖に基づく文生成

先日購入した、はじめてのAIプログラミング C言語で作る人工知能と人工無能を読んでいる。
文生成が面白そうだったので、Clojureで書いてみた。

処理内容としては、以下の通り。
(1)形態素解析ライブラリKuromojiを使用して、元ネタとなる文章を形態素解析する。
(2)その形態素の連鎖をモデル化して、マルコフ連鎖に基づく文生成をする。

プログラム

(ns ai.sentence
  (:import (org.atilika.kuromoji Token Tokenizer)))

(def ^:dynamic *words*
  "マルコフ連鎖のモデル
次のキーと値のマップ
キー: 形態素
値: キーを次の形態素、値を出現数とするマップ"
  (ref {}))

(defn tokenize [text]
  (let [tokenizer (. (Tokenizer/builder) build)]
    (. tokenizer tokenize text)))

(defn token-word [token]
  (.trim (.getSurfaceForm token)))

(defn inc-map-value [m k]
  (if (get m k)
    (update-in m [k] inc)
    (assoc m k 1)))

(defn register-word [m word1 word2]
  (let [word2-map (get m word1 {})]
    (assoc m word1 (inc-map-value word2-map word2))))

(defn load-text [file-name]
  (let [text (slurp file-name)
        tokens (tokenize text)]
    (reduce (fn [m [token1 token2]]
              (let [word1 (token-word token1)
                    word2 (token-word token2)]
                (if (or (= word1 ""))
                  m
                  (register-word m word1 word2))))
            {} (partition 2 1 tokens))))

(defn select-word [word-map]
  (first (rand-nth (seq word-map))))

(defn select-next-word [word-map word]
  (let [next-word-map (get word-map word)]
    (select-word next-word-map)))

(defn create-sentence [word-map word]
  (loop [sentence ""
         word word]
;    (println (str "*" word))
    (if word
      (if (or (= word "。") (= word "?") )
        sentence
        (recur (str sentence word) (select-next-word word-map word)))
      sentence)))

(defn init []
  (dosync
   (ref-set *words*
            (load-text "/home/satoshi/work/sample.txt"))))


サンプルの文章(sample.txt)

夏目漱石 「我輩は猫である」(青空文庫より)の最初の部分を使用した。

吾輩は猫である。
名前はまだ無い。
どこで生れたかとんと見当がつかぬ。
何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。
吾輩はここで始めて人間というものを見た。
しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。
この書生というのは時々我々を捕えて煮て食うという話である。
しかしその当時は何という考もなかったから別段恐しいとも思わなかった。
ただ彼の掌に載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。
掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始であろう。
この時妙なものだと思った感じが今でも残っている。
第一毛をもって装飾されべきはずの顔がつるつるしてまるで薬缶だ。
その後猫にもだいぶ逢ったがこんな片輪には一度も出会わした事がない。
のみならず顔の真中があまりに突起している。
そうしてその穴の中から時々ぷうぷうと煙を吹く。
どうも咽せぽくて実に弱った。
これが人間の飲む煙草というものである事はようやくこの頃知った。

実行結果

user> (in-ns 'ai.sentence)
#<Namespace ai.sentence>
ai.sentence> (init)
{"だ" {"と" 1, "。" 2}, "ニャーニャー" {"泣い" 1}, "一" {"度" 1, "毛" 1}, "何だか" {"フワフワ" 1}, "所" {"で" 1}, "ここ" {"で" 1}, "名前" {"は" 1}, "あっ" {"た" 2}, "
〜略〜
ai.sentence> (create-sentence @*words* "何")
"何というの飲む煙草という人間というのは何でも残って食うという考もだいぶ逢った"
ai.sentence> (create-sentence @*words* "猫")
"猫でニャーニャー泣いてまるで薬缶だと思った時何だかフワフワして書生のがない"
ai.sentence> (create-sentence @*words* "煙草")
"煙草というものを吹く"

# まさに、人工無能...

形態素を使っているので、n-gramを使用した場合より、まともな文章になっているが、文法に従った文生成をしていないため、意味不明な文になっている。
また、本来は、ある形態素の次の形態素を決めるときには、遷移確率を考慮しなければならないが、均等な確率としている(select-word関数のrand-nth関数)。
このような文字列を扱う処理は、C言語よりも圧倒的にClojureの方が書き易い。

2013年12月12日木曜日

[Clojure]トランザクションの最大リトライ回数

トランザクション内で、リファレンスの値の変更に失敗した場合は、トランザクションの最初から処理をリトライする。

トランザクションの処理に時間がかかり、何回繰り返してもリファレンスの値を更新できない場合は Live lock 状態になる。Clojureでは、これを防ぐため、リトライ回数がある一定値を越えると、例外を投げる仕組みになっている。

リトライ回数の最大値はどのくらいかなと、調べてみた。

(def x (ref 0))

(defn test1 []
 (dosync
  @(future (dosync (alter x inc)))
  (ref-set x -1)))


user> (test1)
RuntimeException Transaction failed after reaching retry limit  clojure.lang.Util.runtimeException (Util.java:219)
user> x
#<Ref@843d62: 10000>
user>

最大10000回もリトライしていた。
数十回程度かなと思っていたので、少しびっくり。
この値は変更できるのか、ざっと調べてみたが、変更方法は見付からなかった。
Clojure の STM のドキュメントのどこかに書いてあるのかな?

2013年12月7日土曜日

[Clojure]形態素解析ライブラリKuromojiを使う

オーム社の100周年記念セールで、はじめてのAIプログラミング C言語で作る人工知能と人工無能を購入した。2006年に出版された書籍で、C言語(Borland C)での実装が解説してある。
この類のプログラムをCで実装するのは煩雑になってしまうので、Clojureで作ったらどんなふうになるのかなと思い、文字列の解析時に使用する形態素ライブラリを調べてみた。
Lucene や Solr に対応したlucene-gosen が、結構使われているようだけど、mavenリポジトリにあるバージョンは少し古く、また、多くのライブラリに依存していており、それらのラブラリのバージョンも少し古かった。
その点、Kuromojiは、依存ライブラリがなく、lucene-gosenと同様に辞書も同梱されているので、こちらのライブラリを試してみた。

project.cljにKuromojiの設定を追加

Kuromojiをmavenで使うためには、リポジトリの追加が必要である。
project.cljに次の設定を書く。

:repositories [["Atilika Open Source repository"
                "http://www.atilika.org/nexus/content/repositories/atilika"]]
:dependencies [[[org.atilika.kuromoji/kuromoji "0.7.7"]]


リポジトリのURLの追加方法が最初分からず、いろいろ調べたが、
sample.project.cljのサンプルが参考になった。


サンプルコード

(ns gosen.core
  (:import (org.atilika.kuromoji Token Tokenizer)))

(defn -main []
  (let [tokenizer (. (Tokenizer/builder) build)]
    (doseq [token (. tokenizer tokenize "Javaより楽しいClojure。")]
      (println (str (. token getSurfaceForm) "\t"
                    (. token getAllFeatures))))))


何のことはない。Kuromojiのクラスを使う単純なコードだ。

実行結果

replより実行した。

gosen.core> (-main)
Java    名詞,固有名詞,組織,*,*,*,*
より    助詞,格助詞,一般,*,*,*,より,ヨリ,ヨリ
楽しい    形容詞,自立,*,*,形容詞・イ段,基本形,楽しい,タノシイ,タノシイ
Clojure    名詞,一般,*,*,*,*,*
。    記号,句点,*,*,*,*,。,。,。
nil

ユーザ辞書を追加することも簡単なようなので、試してみる予定。

参考

Java製形態素解析器「Kuromoji」を試してみる

2013年12月2日月曜日

[Clojure][Emacs]Clojure Cheatsheet for Emacs

EmacsでClojureのCheatsheetを閲覧できるELispがあった。
Clojure Cheatsheet for Emacs
試しに使ってみたら、結構便利。

インストール

Emacs24なら、MELPA経由で簡単にインストールできる。

Emacs初期化ファイルに以下のような設定を書いておけば良い。

;; MELPAを有効にする
(add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/") t)


サイトの説明通り、以下のコマンドでインストールする。
M-x package-refresh-contents
M-x package-install RET clojure-cheatsheet

実行

M-x clojure-cheatsheet を実行すると、
ミニバッファに
pattern:
と表示されるので、検索したい文字列を入力する。
sort mapを入力した場合は、次のように表示される。



キーバインドは次の通り。
C-p 前の行
C-n 次の行
RET 選択した項目の表示

clojure-mode時に有効になるような、キーバインドを書いておくと便利かも。
試していないが、Helmのsourceにも対応している。

2013年12月1日日曜日

[Haskell][Clojure]逆ポーランド記法の解析処理

Learn You a Haskell for Great Good で書かれていた逆ポーランド記法の解析処理をClojureで書いてみた。
四則演算のみサポートする単純な処理である。

Haskellの場合

module RPN where
       
solveRPN :: String -> Double
solveRPN = head . foldl func [] . words
    where
      func (x:y:ys) "+" = (y + x) : ys
      func (x:y:ys) "-" = (y - x) : ys
      func (x:y:ys) "*" = (y * x) : ys
      func (x:y:ys) "/" = (y / x) : ys
      func xs num = read num : xs

*RPN> solveRPN "1 2 + 3 * 1 - 2 /"
4.0
     
whereを使うとコードが分かり易くて、なかなか良いね。

Clojureの場合

(ns rpn.core
  (:use [clojure.string :only (split)]))

(defn words [s]
  (split s #"\s+"))

(defn op2 [[x y & ys] f]
  (conj ys (apply f [y x])))

(defn operate [stack item]
  (case item
    "+" (op2 stack +)
    "-" (op2 stack -)
    "*" (op2 stack *)
    "/" (op2 stack /)
    (conj stack (Double/parseDouble item))))

(defn solve-rpn [exprs]
  (first (reduce operate '() (words exprs))))

rpn.core> (solve-rpn "1 2 + 3 * 1 - 2 /")
4.0

solve-rpn関数の中でoperate関数を定義することも可能だが、かえって分かり難くなりそうなので、外出しして関数定義した。
Haskellに比べると冗長になってしまった。
もっと、すっきり書けるのかな?
文字列のdouble型への変換 (Double/parseDouble item) は美しくない。→と思ったときは parse-double関数を作れば良いのだけど。