この話題は去年の間に収束したとおもっていたのですが、再燃してきているみたいなので記事にしておきます。

Clojure使いを何と呼ぶか。

Clojure使い – skalabeの日記

結論から言うと “Clojurian“。

実はRich本人がすでに言及しています。

The A-Z of Programming Languages: Clojure (Page 3)

interviewer: Perl gurus are ‘Perl Mongers’, Python ones are ‘Pythonistas’. We think Clojure needs something similar. Any suggestions?

Rich: I think everyone has settled on Clojurians.

というわけでもうClojurianでいいよね。> all

「Clojure的な」についてはわかりません。
どなたか知っている方はおられませんか?

前エントリ clojure.lib コーディング規約・訳 から1週間以上がすぎました。

Google Groupsでのディスカッションで合意されたコーディング規約をStuがまとめてアップしてくれました。

Clojure Library Coding Standards | Clojure | Assembla

さっそく和訳してみました。

間違いがあればご指摘ねがいます。 ⇒ @manjilab

【和訳ここから】

免責事項:

  • 規則は破られるためにあります。この規約に倣うも絶対のものとして扱わないこと。

規約:

  • 名前と使用法はよく考えて書くこと。RichはJavaにおける既存のコードとの互換性の維持を尊重しています。練習用のコードであればいつまでもいじってられますが、ひとたび名前と使用法が公開されればそうはいきません。(具体的な実装に興味がなく名前と用法だけを見ている利用者が多いですから)
  • コードの重要そうな関数には型ヒントをつける。もしくは型情報なしでも簡潔なコードに。
    • 本当に必要なものにだけ型ヒントをつける。そうであると確信が持てないのであればつけない。
  • 良い名前をつける。他の名前空間のものとの衝突を恐れない。そのための名前空間。
    • とはいえ用法や意味が違うものを同じ名前で使う場合は、その中にふさわしくないものがないかどうかをよく検討すること。
  • パッケージ依存は明確・最低限に。(useやrequireをする時には :onlyオプションを使う)
  • 関数で実現できる場合はマクロは使わない。マクロの方が使い勝手が良い場 合は関数版も合わせて提供すること。(未決の問題:命名規則は必要? “foo” と “foo*”が多く使われてるけど)
  • コンパイル時にすべての情報がわかっているのであれば、パフォーマンス上有効な部分にマクロを使う。(ログのためのマクロの議論を参考。結局両方のバージョンが必要となった。コンパイル時に完全な情報が得られないケースが多いからだ。)
  • ライブラリレベルで docstringを書くこと。
  • 全関数に対して自動化されたテストを書くこと。
  • オプションの引数は展開すること。呼び出し側がオプション引数をマップで包まなくてもいいように。
    • (release-sharks 2 :laser-beams true) ; 良い
    • (release-sharks 2 {:laser-beams true}) ; 悪い
  • 述語の名前は最後に ? をつける。
    • 注意 – 述語はブール値を返す。
  • docstringをつける。例外として、関数がの定義が明白そのものでdocstringが関数名と引数をただ冗長に示すだけのような場合は書かない。(私もそれに該当するdocstringは削除する方針にした。)
    • これはHTMLドキュメント化のようなツールにとってマイナス?
  • 迷ったら性能のよい方を公開すること。Clojureは求められるパフォーマンスを提供すべきであり、ライブラリも同じです。(それがマルチメソッド版の + をcoreでは提供していない理由です。)ユーザは必要に応じてAPIをハイジャックして新たな型を作ることが出来るのですから。
  • よい名前を使いたいけどcoreと衝突するような場合は意味が同等になるように心がけてください。coreのシーケンス関数に対する文字列処理関数が良い例となるでしょう。
  • assertや pre- post-などの条件処理を気軽に使いましょう。現時点ではあまり使われていませんが、もっと使われるべきです。#250を参考。
  • できるだけ遅延評価で書きましょう。
  • 変数名は pred や coll のように clojure.coreの慣例に従うこと。
  • clojure.coreの起動初期コードの慣用表記に倣ってはいけない。それらはClojureが立ち上がる前の限定された環境のために書かれているから。
  • コードを小さい塊に分割せよ。doseqの定義のような書式はRich以外の人は書かないで。
  • オブジェクトの要素へのアクセスはキーワードを先にもってくる。
    • (:property object-like-map)
  • コレクションから値を取り出すときはコレクションを先にもってくる(コレクションがnilかもしれない場合にはgetを使う)
    • (collection-like-map key) (get collection-like-map key)
    • すべてのコレクションがキーワードをキーとして持つわけではないので注意。
  • 分配束縛はよく用いられる。しかし渡された値の一部を使いたいときには引数書式の所での使用にどめるべきだ。もしくはletの最初の行で。Programming Clojureに載せたスネークゲームのコードはこれに反しているけれど。
  • トランザクションではセットよりも更新を使おう。統一的な更新モデルはもっとも基本的な方法であり、交換可能な処理を発見しやすくなる。それにより更新処理の衝突を少なくできる。
  • 想定していないコレクション型をサポートしてはいけない。アルゴリズムがランダムアクセスを用いるのであれば、受け取る型はランダムアクセス可である必要がある。
  • *earmuffs* は再束縛を意図するものにのみ使用する。
  • bang! はSTMトランザクションで安全でないものにのみ使用する。
  • loop/recurよりもシーケンス・ライブラリの組み合わせを用いる。
  • varの再束縛にはスコープ系マクロをいっしょに。例)*in* と with-in-str
  • 遅延シーケンスは最小限の状態を持つ関数として書く。つまり「頭をかかえない」こと。どの程度の規模で現実化するかは呼び出し側にまかせること。
  • interop は Klass/staticField, (Klass/staticMethod), (Klass.), (.method obj) の型式で。例外はコードを生成するコード中で(. obj method)の方が簡単な場合のみ。
  • 引数の順序はよく考えて。ライブラリのなかでは一貫性を持たせること。
  • パラメータを動的束縛で暗に受け渡すインターフェイスを提供している場合(例: sqlにおけるdb)、パラメータを明確に受け渡せるインターフェイスも合わせて提供する。

【和訳ここまで】

2/13 – 更新された規約はこちらの記事にまとめました。
“Clojureライブラリ・コーディング規約” まとめ

 

Clojure Dev Google groupsで盛り上がっているトピックがあります。

“clojure.lib coding standards: initial draft brain dump Options”

Programming Clojure の著者、Stuart Halloway氏が音頭を取ってclojure.lib用のコーディング規約をみんなで決めようというものです。

興味深かったのでStuの提案したルールを和訳してみました。
思いっきり意訳です。間違いがあればご指摘ねがいます。 ⇒ @manjilab

(掲示板の流れに合わせていくつかの項目を追加・削除しています)

【和訳ここから】

最初に:

  • これは公式でも最終版でもない。議論を誘発するための叩き台です。もし致命的に間違っている項目が含まれていなければ私の手抜きです :-)
  • ルールは破られるためにある。よってどのような規約も絶対ではない。
  • 有言実行:ここで同意が得られた項目に沿うように自分の既存のコードを書き直すつもりです。
  • この文章はClojureの入門者にとって役立つものになるでしょう。参考資料となるリンクも示してあります。

規約:

  • 名前と使用法はよく考えて書きましょう。RichはJavaにおける既存のコードとの互換性の維持を尊重しています。練習用のコードであればいつまでもいじってられますが、ひとたび名前と使用法が公開されればそうはいきません。(具体的な実装に興味がなく名前と用法だけを見ている利用者が多いですから)
  • パフォーマンス的に重要な部分には type hintをつける。
  • 良い名前をつける。他の名前空間のものとの衝突を恐れない。そのための名前空間。
  • パッケージ依存は明確・最小限に。(useやrequireをする時には :onlyオプションを使う)
  • 関数で実現できる場合はマクロは使わない。マクロの方が使い良い場合は関数版も提供すること。(みんなへ質問:命名規則は必要? “foo” と “foo*”が多く使われてるけど)
  • コンパイル時にすべての情報がわかっているのであれば、パフォーマンス上有効な部分にマクロを使う。( ログのためのマクロ の議論を参考。結局両方のバージョンが必要となった。コンパイル時に完全な情報が得られないケースが多いからだ。)
  • ライブラリレベルで docstringを書くこと。
  • 全関数に対して自動化されたテストを書くこと。
  • オプションの引数は展開すること。
    (foo x y {:optional z}) ではなく (foo x y :optional z)
  • 述語(true または falseを返す)関数名の末尾は ‘?’ で。
  • docstringを付けよ。例外として、関数がの定義が明白そのものでdocstringが関数名と引数をただ冗長に示すだけのような場合は書かない。(私もそれに該当するdocstringは削除する方針にした。)
  • 迷ったら性能のよい方を公開すること。Clojureは求められるパフォーマンスを提供すべきであり、ライブラリも同じです。(それがマルチメソッド版の + をcoreでは提供していない理由です。)ユーザは必要に応じてAPIをハイジャックして新たな型を作ることが出来るのですから。
  • よい名前を使いたいけどcoreと衝突するような場合は意味が同等になるように心がけてください。coreのシーケンス関数に対する文字列処理関数が良い例となるでしょう。
  • assertや pre- post-などの条件処理を気軽に使いましょう。現時点ではあまり使われていませんが、もっと使われるべきです。 参考
  • できるだけ遅延評価で書きましょう。
  • 変数名は pred や coll のように clojure.coreの慣例に従うこと。
  • clojure.coreの起動初期コードの慣用表記に倣ってはいけない。それらはClojureが立ち上がる前の限定された環境のために書かれているから。
  • コードを小さい塊に分割せよ。doseqの定義のような書式はRich以外の人は書かないで。
  • マップ要素へのアクセスは (:property object-like-map) 、コレクションへは (collection-like-map key) を使う。もしくは get を使う。(追加)
  • 分配束縛はよく用いられる。しかし渡された値の一部を使いたいときには引数書式の所での使用にどめるべきだ。もしくはletの最初の行で。でも Programming Clojureに載せたのスネークゲームのコード はこれに反しているんだけどね。
  • (トランザクション系の話)セットよりも更新を使おう。理由はたくさんある。統一されたupdateモデルは簡潔で標準的であるので、交換可能な処理を発見しやすくなる。それにより更新しようとしている処理の衝突を少なくできる。
  • 想定していないコレクション型をサポートしてはいけない。アルゴリズムがランダムアクセスを用いるのであれば、受け取る型はランダムアクセスできるものでなければならない。
  • *earmuffs* は再束縛を意図するものにのみ使用する。
  • bang! はSTMトランザクションで安全でないものにのみ使用する。
  • loop/recurよりもシーケンス・ライブラリの組み合わせを用いる。
  • varの再束縛にはスコープ系マクロをいっしょに。例)*in* と with-in-str
  • 遅延シーケンスは最小限の状態を持つ関数として書く。つまり「頭をかかえない」こと。どの程度の規模で現実化するかは呼び出し側にまかせること。
  • interop は (Klass/static) , (Klass.) , (.instance obj) の型式で。 例外はコードを生成するコード中で(. blah)の方が簡単な場合のみ。
  • 引数の順序はよく考えて。ライブラリのなかでは一貫性を持たせること。

【和訳ここまで】

加筆

【2010.2.4 – 10:30】
・『マップ要素へのアクセスは (:property object-like-map)〜』を追加
・bang! の項:「安全でないSTMトランザクション」 ⇒ 「STMトランザクションで安全でないもの」
@athos0220さん、ご指摘ありがとうございました。

Clojureコードバトンが回ってきたので挑戦してみたよ。

数日前もにいちど @nitro_idiotさんから打診されてた のですが渡航中だったのでパスさせていただいてました。

帰国後に @pokarimさんが回してくださった のでありがたく参加させていただくことに。

とはいえgithubもgistも他の人のソースをforkしたことがないんだよね。なんか不安。いいのか?

forkは実際にやってみると簡単でした。gistにログインしてforkボタンを押してあとはweb上で編集するだけ。

渡航中にチェックしたときは僕にもリファクタリングできそうな箇所が2つほどあったので安心してたのですが、先ほど見てみるとキレイに修正済でした。しまった。

いきなりハードルがあがってしまいましたがすでに受けてしまったので、機能追加の方向でなんとかします。

このようになりました。

実行環境が Mac OS X 上の場合のみ、出題時に英単語を音声で出力するようにしました。
speak-osx と osx? が追加部分です。

これを読んだ方で興味をもたれた方は是非バトンを受け取って(forkして)参加してください。おまちしております。


追記

【2010.1.31】
@deltamさんが受け取ってくれました ありがとうございま〜す。

【2010.2.1】
@omasanoriさんにバトンが無事に渡ったようです。 ほっ。

Project EulerをClojureでおさらいシリーズ。
Problem 22

オリジナルはこちら

日本語訳:

5000個以上の名前を含む46Kのテキストファイル name.txt を用いる。 まずアルファベット順にソートすること。 そしてそれぞれの名前のについてアルファベットの数値とリスト内での順番を掛けて名前のスコアを計算する。

たとえばアルファベット順にソートされたリストの938番目に COLIN があるとすると、COLINは 3 + 15 + 12 + 9 + 14 = 53 であるので 938 × 53 = 49714 がスコアとなる。

ファイルに含まれるすべての名前のスコアの合計を求めよ。

外部ファイルからデータを読み込む最初の問題。

以前Rubyで解いたもの:

#!/usr/bin/env ruby
 
f = open('names.txt')
  names_orig = f.read
f.close
 
def score_base(name)
  name.unpack("C*").inject(0){|s,x| s + x - 64}
end
 
names = names_orig.split(',').map{|name| name.tr('"','')}.sort
 
p names.enum_for(:each_with_index).map{|name,i| score_base(name) * (i+1)}.inject(:+)

Clojureではこのように:

(ns euler.p022)
 
(def names (slurp "names.txt"))
 
(defn name-score
  "Return name score.
   ex) COLIN at 938th in list
       -> 938 * (3 + 15 + 12 + 9 + 14) = 49714"
  [pos name]
  (* pos
     (reduce + (map #(-> % int (- 64)) name))))
 
(println "Anster : "
 (reduce + (map name-score
		(iterate inc 1)
		(sort (re-seq #"\w+" names)))))

mapが使える言語だととっても楽チン。

Project EulerをClojureでおさらいシリーズ。
Problem 21

オリジナルはこちら

日本語訳:

n の真の約数の合計を d(n) と定義する。
(真の約数とは整数 n の約数で n 以外のものである)

もし d(a) = b かつ d(b) = a が a ≠ b で成立するならば a と b は 友愛数と呼ばれる。

たとえば220の真の約数は 1,2,4,5,10,11,20,22,44,55,110 で
d(220) = 284 であり、284の真の約数は 1,2,4,71,142 なので
d(284) = 220 となる。

10000未満の友愛数の合計を求めよ。

以前Rubyで解いたもの:

#!/usr/bin/env ruby
 
require 'mathn'
 
def divisor(n)
  result=[1]
  n.prime_division.each do |i|
    result = result + result.map {|x| (1..i[1]).map{|n| x*(i[0]**n)}}.flatten
  end
  result.flatten.sort
end
 
def sum_of_divisors(n)
  divisor(n)[0..-2].inject(:+)
end
 
sum = 0
 
(1...10000).each do |n|
  sod = sum_of_divisors(n)
  next if sod.nil?
  next if n == sod
  sum += n if n == sum_of_divisors(sum_of_divisors(n))
end
 
p sum

約数の和は prime_division で素因数分解したものから約数リストを生成して、最後の要素を除いたものを inject で合計してます。

さてClojureでは:

(ns euler.p021
  (:use clojure.contrib.seq-utils))
 
(defn prime-division
  "Return prime factors of N."
  [number]
  (loop [n number d 2 factors []]
    (cond (<= n d) (frequencies (conj factors d))
	  (zero? (rem n d)) (recur (/ n d) d (conj factors d))
	  :else (recur n (inc d) factors))))
 
(defn exp-prime-factor
  "Expand prime factor exponentially.
   Usage: (exp-prime-factor prime-factor index)
   Example: (exp-prime-factor 3 4) -> [1 3 9 27 81]"
  [pf idx]
  (map #(apply * (replicate % pf)) (range (inc idx))))
 
(defn composite-factors
  "Composite factors collection.
   Usage: (composite-factors coll [prime-factor index])
   Example: (composite-factors [1 2 4] [5 2])
            -> (1 2 4 5 10 20 25 50 100)"
  [last-coll coll]
  (for [x (apply exp-prime-factor coll)
	y last-coll]
    (* x y)))
 
(defn sum-of-proper-divisors
  "Return sum of proper divisor of N."
  [n]
  (apply + (drop-last
	    (reduce composite-factors [1] (prime-division n)))))
 
(def sum-of-proper-divisors (memoize sum-of-proper-divisors))
 
(defn amicable?
  "Return true if N has amicable pair."
  [n]
  (let [candidate (sum-of-proper-divisors n)]
    (if (not= candidate n)
      (= n (sum-of-proper-divisors candidate)))))
 
(println "Answer : "
	 (apply + (filter amicable? (range 10000))))

うわー、なんか長くなった。

Clojureコーディング規約・訳 より

匿名関数は1行で収まる簡単な定義のみに使う。

ということで、素因数分解から約数のリストを生成する工程を幾つかの関数に 切り分けたのが原因。

そして関数の名付けがむずかしくてドキュメントで補足するハメに・・。
まだまだ未熟よのう。

ためしに1関数にまとめてみたらこうなった。

(ns euler.p021
  (:use clojure.contrib.seq-utils))
 
(defn prime-division
  "Return prime factors of N."
  [number]
  (loop [n number d 2 factors []]
    (cond (<= n d) (frequencies (conj factors d))
	  (zero? (rem n d)) (recur (/ n d) d (conj factors d))
	  :else (recur n (inc d) factors))))
 
(defn sum-of-proper-divisors
  "Return sum of proper divisor of N."
  [n]
  (apply + (drop-last
	    (reduce (fn [m-coll coll]
	       (mapcat (fn [m-factor]
		  (map (fn [idx]
		     (* m-factor (. (bigint (first coll)) pow idx)))
		   (range (inc (second coll)))))
		m-coll))
	     [1] (prime-division n)))))
 
(def sum-of-proper-divisors (memoize sum-of-proper-divisors))
 
(defn amicable?
  "Return true if N has amicable pair."
  [n]
  (let [candidate (sum-of-proper-divisors n)]
    (if (not= candidate n)
      (= n (sum-of-proper-divisors candidate)))))
 
(println "Answer : "
	 (apply + (filter amicable? (range 10000))))

累乗はJava側の関数にやらせてみた。 やっぱりこっちの方がいいかな。

Project EulerをClojureでおさらいシリーズ。
Problem 20

オリジナルはこちら

日本語訳:

n! は n × (n-1) × ・・・ × 3 × 2 × 1 を表す。

1000! の各桁の数字の和を求めよ。

瞬殺の予感。

以前Rubyで解いたもの:

#!/usr/bin/env ruby
 
p 1.upto(100).inject(:*).to_s.split('').inject(0){|s,x| s + x.to_i}

Rubyステキ。

Clojureでは:

(ns euler.p020)
 
(defn fact
  "Return factorial of N."
  [n]
  (apply * (range 1 (inc n))))
 
(defn sum-of-digits
  "Return the sum of each digits of N."
  [n]
  (apply + (map #(Integer. (str %)) (str n))))
 
(println "Answer : " (sum-of-digits (fact 100)))

ほとんどいままでの使い回し。

Project EulerをClojureでおさらいシリーズ。
Problem 19

オリジナルはこちら

日本語訳:

まず次の情報を与えておこう。(自分自身で確認してみるのもよいだろう)

  • 1900年1月1日は月曜日
  • 4月、6月、9月、11月は30日まで、
    それ以外と2月を除く月は31日まである。
    2月は28日までだが、閏年には29日までとなる。
  • 閏年は西暦の数字が4で割り切れる年におこる。
    ただし400で割り切れず100で割り切れる場合は除く。

20世紀(1901年1月1日〜2000年12月31日)には月の始めにくる日曜日がいくつあるか求めよ。

グレゴリオ暦を扱う問題。

以前Rubyで解いたもの:

#!/usr/bin/env ruby
 
days_of_month = [[31,28,31,30,31,30,31,31,30,31,30,31],
                 [31,29,31,30,31,30,31,31,30,31,30,31]]
 
Monday = 1
Sunday = 0
 
def is_leap(year)
  year % 4 == 0 && year % 100 != 0 || year % 400 == 0
end
 
count = 0
days_total = Monday + 365 # 1900 is not leap
(1901..2000).each do |year|
  leap = is_leap(year) ? 1 : 0
  (1..12).each do |month|
    count += 1 if days_total % 7 == 0
    days_total += days_of_month[leap][month-1]
  end
end
p count

おもいっきり手続き型ですね。

今回のClojureでは:

(ns euler.p019)
 
(def days-of-month
     [31 28 31 30 31 30 31 31 30 31 30 31])
 
(defn leap-year?
  "Return true if leap year."
  [year]
  (cond
    (zero? (rem year 400)) true
    (zero? (rem year 100)) false
    (zero? (rem year 4)) true
    :else false))
 
(defn leaps-from-origin
  "Return number of leap days form Jan-0 0000."
  [year month day]
  (+ (quot year 4)
     (- (quot year 100))
     (quot year 400)
     (if (leap-year? year)
       (if (< month 3) -1 0)
       0)))
 
(defn days-total-from-origin
  "Return number of days from Jan-0 0000."
  [year month day]
  (+ (* 365 year)
     (apply + (take (dec month) days-of-month))
     day
     (leaps-from-origin year month day)))
 
(defn sunday?
  "Return true if date is sunday."
  [year month day]
  (let [sunday (rem (dec (days-total-from-origin 1900 1 1)) 7)]
       (= sunday
	  (rem (days-total-from-origin year month day) 7))))
 
(println "Answer : "
	 (count
	  (filter #(apply sunday? %)
		  (for [year (range 1901 2001)
			month (range 1 13)]
		    [year month 1]))))

月初の日付を生成して、日曜日の判定を別関数にしました。

さてひととおり書いた後に 「Clojureにも日付を扱うライブラリくらいあるだろ」 と思い探してみましましたが見当たりませんでした。

こういうときはJavaを探すというお約束。→ ありました
グレゴリオ暦のほうじゃないと閏年の修正が異なるので注意。

これを使って書き直すと:

(ns euler.p019
  (:import (java.util GregorianCalendar)))
 
(defn sunday?
  "Return true if date is sunday."
  [year month day]
  (let [date (doto (GregorianCalendar.) (.set year (dec month) day))]
    (= (. date get GregorianCalendar/DAY_OF_WEEK)
       GregorianCalendar/SUNDAY)))
 
(println "Answer : "
	 (count
	  (filter #(apply sunday? %)
		  (for [year (range 1901 2001)
			month (range 1 13)]
		    [year month 1]))))

かなりスッキリしました。

Project EulerをClojureでおさらいシリーズ。
Problem 18

オリジナルはこちら

日本語訳:

下の三角形の頂点から隣接する数字を下まで辿っていくと、その数値の和の最大数は23となる。

3
7 4
2 4 6
8 5 9 3

この例の場合、3 + 7 + 4 + 9 = 23 である。

以下の三角形において、同様に和の最大数を求めよ。

75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23

注意:この例では16384通りのルートしかないのですべての経路を調べることもできる。しかし Problem 67 では同様の問題でも三角形が100行からなるので総当たりで解くのは不可能である。もっと賢い方法が求められる。

選択肢の枝をどのように切り落としていくかが問題。

以前Rubyで解いたもの:

#!/usr/bin/env ruby
 
Rows = 15
tri = []
tri << %w(75)
tri << %w(95 64)
tri << %w(17 47 82)
tri << %w(18 35 87 10)
tri << %w(20 04 82 47 65)
tri << %w(19 01 23 75 03 34)
tri << %w(88 02 77 73 07 63 67)
tri << %w(99 65 04 28 06 16 70 92)
tri << %w(41 41 26 56 83 40 80 70 33)
tri << %w(41 48 72 33 47 32 37 16 94 29)
tri << %w(53 71 44 65 25 43 91 52 97 51 14)
tri << %w(70 11 33 28 77 73 17 78 39 68 17 57)
tri << %w(91 71 52 38 17 14 91 43 58 50 27 29 48)
tri << %w(63 66 04 68 89 53 67 30 73 16 69 87 40 31)
tri << %w(04 62 98 27 23 09 70 98 73 93 38 53 60 04 23)
Tri = tri.map{|line| line.map{|num| num.to_i}}
$cache = tri.map{|line| line.map{|num| nil}}
 
def pick_grater(depth, pos)
  current = Tri[depth][pos]
  return current if depth == Rows - 1
  $cache[depth+1][pos] ||= pick_grater(depth + 1, pos)
  $cache[depth+1][pos+1] ||= pick_grater(depth + 1, pos + 1)
  left  = $cache[depth+1][pos]
  right = $cache[depth+1][pos+1]
  (left > right ) ? (left + current) : (right + current)
end
 
p pick_grater(0, 0)

再帰&キャッシュです。

頂点からスタートして
最大数 =(左右の選択肢が持つ和の大きいほう)+(自分の数字)
で、あとは再帰。

さて、次はClojureで書いてみます。

書籍 “Programming Clojure” より “Six Rules of Clojure FP” の5つめ:

Know the sequence library. You can often write code without using recur or the lazy APIs at all.

最近はこれを心がけています。

Ruby版のは再帰ですが実際の処理としては枝の最下部から切り落とされていきます。

だったら最下層から折りたたんでいけば再帰を使わなくてもいけそうです。

(ns euler.p018)
 
(def triangle-text
  "75
   95 64
   17 47 82
   18 35 87 10
   20 04 82 47 65
   19 01 23 75 03 34
   88 02 77 73 07 63 67
   99 65 04 28 06 16 70 92
   41 41 26 56 83 40 80 70 33
   41 48 72 33 47 32 37 16 94 29
   53 71 44 65 25 43 91 52 97 51 14
   70 11 33 28 77 73 17 78 39 68 17 57
   91 71 52 38 17 14 91 43 58 50 27 29 48
   63 66 04 68 89 53 67 30 73 16 69 87 40 31
   04 62 98 27 23 09 70 98 73 93 38 53 60 04 23")
 
(def triangle
     (for [row (map #(re-seq #"[0-9]+" %)
		     (.split triangle-text "\n"))]
       (map #(Integer. %) row)))
 
(defn merge-rows
  "Return maximum values for upper row."
  [last-row current-row]
  (map +
       current-row
       (map #(apply max %) (partition 2 1 last-row))))
 
(println "Answer : "
	 (first (reduce merge-rows (reverse triangle))))

partition 関数の使い方がミソですね。
“Programming Clojure” の “5.3 Lazier Than Lazy” が参考になりました。

merge-row では左右の和の最大数を比較して、上の階層に渡す和を求めています。あとはreduceで繰り返し。

Project EulerをClojureでおさらいシリーズ。
Problem 17

オリジナルはこちら

日本語訳:

1から5までを英単語で表すと one, two, three, four, five となり、 文字数の合計は 3 + 3 + 5 + 4 + 4 = 19 となる。

1から1000までを英単語で表すと文字数の合計はいくらになるか。

注意:スペースとハイフンはカウントしない。例えば 342 (three hundred and forty-two) は23文字となり、115 (one hundred and fifteen) は20文字となる。 英単語での表記の”and”の使い方はイギリス英語のそれに準じることとする。

fourteen と forty の綴りには注意。あとはそんなに難しくない。

以前Rubyで解いたもの:

#!/usr/bin/env ruby
 
One_to_Nine = %W(#{} one two three four five six seven eight nine)
Teen = %W(#{} #{} #{} thir four fif six seven eigh nine)
Ty = %W(#{} #{} twen thir for fif six seven eigh nine)
Ten_to_Twelve = %w(ten eleven twelve)
Handred = " handred"
Thousand = " thousand"
 
def spell(num)
  r = []
  d = []
  (0..3).each do |digit|
    d[digit] = num % 10**(digit+1) / 10**digit
  end
 
  r << One_to_Nine[d[3]] + Thousand if d[3] > 0
  r << One_to_Nine[d[2]] + Handred  if d[2] > 0
  r << "and" if (d[3] + d[2]) * (d[1] + d[0]) > 0
  if d[1] == 1
    r << Teen[d[0]] + "teen" if d[0] > 2
    r << Ten_to_Twelve[d[0]] if d[0] <= 2
  else
    r << Ty[d[1]] + "ty" if d[1] > 1
    r << One_to_Nine[d[0]] if d[0] > 0
  end
 r.join(" ").sub("ty ", "ty-")
end
 
def count_letter(num)
  spell(num).tr(" -", "").size
end
 
p 1.upto(1000).inject(0){|s,n| s + count_letter(n)}

効率化しようとしてムダに力が入っているな・・。

今回はClojureで:

(ns euler.p017)
 
(def one-to-nineteen
     ["" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"
      "ten" "eleven" "twelve" "thirteen" "fourteen" "fifteen"
      "sixteen" "seventeen" "eighteen" "nineteen"])
 
(def twenty-to-ninety
     ["twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"])
 
(defn spell-num
  "Return number as english-word."
  ([n] (spell-num n ""))
  ([n preword]
     (cond
       (= n 0) ""
       (< n 20) (str preword (nth one-to-nineteen n))
       (< n 100) (str preword
		      (nth twenty-to-ninety (/ (- n 20) 10))
		      (spell-num (rem n 10) "-"))
       (< n 1000) (str preword
		       (spell-num (/ n 100)) " hundred"
		       (spell-num (rem n 100) " and "))
       (< n 10000) (str (spell-num (/ n 1000)) " thousand"
			(spell-num (rem n 1000) " ")))))
 
(defn remove-spaces-and-hyphens
  [word]
  (apply str (re-seq #"\w+" word)))
 
(println "Answer : "
	 (count
	  (apply str
		 (map #(remove-spaces-and-hyphens (spell-num %))
		      (range 1 1001)))))

再帰を中心に考えてみた。

spell-num の中に繰り返しパターンがあるのがちょっと気になる。うーむ。

Get Adobe Flash playerPlugin by wpburn.com wordpress themes

About this blog

私 manjilab のポータル的サイトになっております。日々気付いたこと、考えたこと、発表したいものを載せていきます。

Photostream

    Lisp indent 考察Lisp indent 考察Lisp indent 考察Lisp indent 考察Lisp indent 考察Tips to use Clojure(Lisp) with TextMateTips to use Clojure(Lisp) with TextMateTips to use Clojure(Lisp) with TextMate