ScalaでXMLをパーズする
ネット上にはなんていうか「試しにやってみました」程度の簡単な例しか転がっていないので多少複雑な例を。以下のようなXML(sample-1.xmlとする)があったとして
<?xml version="1.0" encoding="utf-8" ?> <ninjas> <ninja id="ninjaslayer"> <attacks> <attack name="Suriken"> <damage>10</damage> <times>1000</times> </attack> <attack name="Meia lua de compasso"> <damage>50</damage> <times>30</times> </attack> <attack name="Tsuyoi suriken"> <damage>100</damage> <times>2</times> </attack> <attack name="Evil Black Fire"> <damage>500</damage> <times>1</times> <condition>Controlled by Naraku</condition> <condition>Unconsious</condition> </attack> <attack name="Sacred Nunchaku"> <damage>1000</damage> <times>1</times> <condition>Synchronized with Naraku</condition> <condition>Clutch Situation</condition> </attack> </attacks> <name>Ninja Slayer</name> </ninja> <ninja id="forest-sawatari"> <attacks> <attack name="Machete"> <damage>50</damage> <times>10</times> </attack> <attack name="Bio takeyari"> <damage>50</damage> <times>10</times> </attack> </attacks> <name>Forest Sawatari</name> </ninja> <ninja id="frogman"> <attacks> <attack name="Bio Frogs Tongue"> <damage>50</damage> <times>100</times> </attack> <attack name="Makimono Jitsu"> <damage>300</damage> <times>30</times> </attack> </attacks> <name>Frogman</name> </ninja> <ninja id="frogman"> <attacks> <attack name="Bio Frogs Tongue"> <damage>50</damage> <times>100</times> </attack> <attack name="Makimono Jitsu"> <damage>300</damage> <times>30</times> </attack> </attacks> <name>Frogman</name> </ninja> <ninja id="genocide"> <attacks> <attack name="Buzzsaw"> <damage>500</damage> <times>100</times> </attack> </attacks> <name>Genocide</name> </ninja> <ninja id="evolver"> <attacks> <attack name="Ochoko Defence"> <damage>1</damage> <times>100</times> </attack> <attack name="Ju Jitsu"> <damage>50</damage> <times>100</times> </attack> <attack name="Evolution"> <damage>150</damage> <times>100</times> </attack> <attack name="Supersize"> <damage>150</damage> <times>10</times> </attack> <attack name="Monsterize"> <damage>750</damage> <times>1</times> <condition> Supersized </condition> </attack> </attacks> <name>Evolver</name> </ninja> </ninjas>
ニンジャ名の一覧をStringのリストで取りたい。どうするか。
import scala.xml._ val xm = XML.loadFile("sample-1.xml")
で、変数xmにXMLとして取り込んだとする。
単純に
xm \\ "name"
とすると結果は
NodeSeq(<name>Ninja Slayer</name>, <name>Forest Sawatari</name>, <name>Frogman</name>, <name>Frogman</name>, <name>Genocide</name>, <name>Evolver</name>)
となるので
for(x <- xm \\ "name" ) yield x.text
としてあげるとこの式の値が
scala.collection.immutable.Seq[String] = List(Ninja Slayer, Forest Sawatari, Frogman, Frogman, Genocide, Evolver)
となり目的達成。
Scalaのfor文は強力なのでこのようにXMLのパーズと組み合わせるとなかなか威力を発揮するようだ。
multipart/form-dataのバウンダリ文字列生成(その後)
この方がClojureっぽいよ!という指摘をいただきました。
github:gist
char-rangeのところはおいといて(w
確かに昨日のソースは
(defn generateBoundary [] (let [chars "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"] (apply str "-------" (take 40 (repeatedly #(rand-nth chars))))))
にしたほうがよりClojureっぽいですね。
(rand-nth [coll])
はコレクションcollの中からn番目をランダムに選ぶ関数
(repeatedly f)
は関数fを無限回呼び出す遅延シーケンスを返す関数。
#(rand-nth [coll])
として無名関数化してrepeatedlyに渡している。
ただしこいつは
(repeatedly n f)
と書いて無限シーケンスの先頭要素n個を取ることもできるので
(defn generateBoundary [] (let [chars "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"] (apply str "-------" (repeatedly 40 #(rand-nth chars)))))
のようにtakeを省略できる。
乱数については(rand-nth [coll])以外にも(rand-int n) (0〜n-1までの整数の乱数を返す)のもあるのでJavaのRandomのオブジェクトを生成して使わないでもよいケースも多そう。
Scalaでmultipart/form-dataのバウンダリ文字列生成
multipart/form-dataでデータをアップロードする必要が出てきたのでScalaでこりこり書き始めた。
各パートの切れ目を表すboundary文字列を生成するプログラムとしてランダム文字列の生成を見つけた。
以下ソースコード引用。
private String generateBoundary() { String chars = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"; Random rand = new Random(); String boundary = ""; for (int i = 0; i < 40; i++) { int r = rand.nextInt(chars.length()); boundary += chars.substring(r, r + 1); } return "---------------------------" + boundary; }
これをほぼそのまま移してもScalaで動くんだが、せっかくなので多少関数型っぽくする。
private def generateBoundary :String = { val chars = (('0' to '9') ++ ('a' to 'z') ++ ('A' to 'Z' )).mkString + "-_" val rand = new scala.util.Random "----------" + (for( i <- 0 until 40 ) yield chars(rand.nextInt(chars.length))).mkString }
Scalaのfor文はなかなか強力。こうやって「再代入可能な変数を減らしていく」わけですな。
選択される文字一覧には範囲型の結合を使ってみたが、これはまあおまけ。
ついでにClojureでも書いてみたらこんな感じか。
(defn generateBoundary [] (let [chars "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"] [rnd (new java.util.Random)] (str "-------" (apply str (for [i (range 40)] (nth ccc (. rnd nextInt (count ccc))))))))
ふむ、関数型っぽく書いたScalaと同じ骨組みがどこか透けて見えるじゃないですか。
前回の答え
http.request(POST){ uri.path = 'mform.html' body = [status : 'update!', source : 'gegegege'] requestContentType = groovyx.net.http.ContentType.URLENC response.success = { resp -> println("status: ${resp.statusLine}") assert resp.statusLine.statusCode == 200 } }
は
http.request(POST,TEXT){ req -> uri.path = 'mform.html' println(uri.path) send URLENC, [status : 'update!', source : 'gegegege'] // will be url-encoded response.success = { resp, xml -> println("status: ${resp.statusLine}") assert resp.statusLine.statusCode == 200 } }
でうまく行った。ところで書いてて疑問だったんだけど、ここのuriとかsend URLENCとかってどこで定義されてるんだろう。一種のDSLになってるんでHTTPBuilderのどこかにあると思うんだけど。
httpbuilderで大苦戦
GroovyのHTTPBuilderで大苦戦している。
やりたいことはあるWeb APIで「multipart/form-dataでパラメータを送りなさい」というのがあって…
つまり
Content-Type: multipart/form-data; boundary=-AaB03x ---AaB03x Content-Disposition: form-data; name="username" Kumappus ---AaB03x Content-Disposition: form-data; name="secret" sdflasdfjasd;askd;io;ksdjf;lksa;ljふじこ ---AaB03x--
みたいな感じで送らないといけない。で、調べてみるとHTTPBuilderの0.5.2-SNAPSHOTにmultipart form post機能が追加されているらしいので、まずはPOSTしてみようと次のようなGroovyソースを書いてみた
@Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.5.2-SNAPSHOT' ) @Grab(group='org.apache.httpcomponents', module='httpcore', version='4.1.3') @Grab(group='org.apache.httpcomponents', module='httpcore-nio', version='4.1.3') @Grab(group='org.apache.httpcomponents', module='httpclient', version='4.1.2') @Grab(group='org.apache.httpcomponents', module='httpmime', version='4.1.2') import static groovyx.net.http.Method.* import static groovyx.net.http.ContentType.* import org.apache.http.entity.mime.MultipartEntity import org.apache.http.entity.mime.HttpMultipartMode import org.apache.http.entity.mime.content.FileBody import org.apache.http.entity.mime.content.StringBody def http = new groovyx.net.http.HTTPBuilder('http://example.com/') http.request(POST){ uri.path = 'mform.html' body = [status : 'update!', source : 'gegegege'] requestContentType = groovyx.net.http.ContentType.URLENC response.success = { resp -> println("status: ${resp.statusLine}") assert resp.statusLine.statusCode == 200 } }
ところが、これだとなんとNullPointerExceptionが出て
body = [status : 'update!', source : 'gegegege']
のところで失敗する。うーん、何がおかしいんだろう。これができないのでマルチパートの方の実験もできぬわ。
お題:文字列を先頭から見て同じところまで除去
このお題の挑発に乗り、Clojureで書いてみました。
しかしなんかダサい。猛烈にダサい感じが。こんなに関数を並べなくても一発で書けそうな気がするんだが、手続き型っぽく「先頭の一文字が全て同じか」「だったら先頭を切って次へ」的な発想になったのが敗因だろうか。
(defn collect-first [v] (let [c1 (first v)] (cond (not (nil? c1)) (cons (first c1) (collect-first (rest v)))))) (defn all-first-are-same? [v] (= (count (distinct (collect-first v))) 1 )) (defn remove-first [v] (let [c1 (first v)] (cond (not (nil? c1)) (cons (rest c1)(remove-first (rest v)))))) (defn drop-start-same [v] (if (all-first-are-same? v) (drop-start-same (remove-first v)) v)) (println (drop-start-same ["abcdef" "abc123"])) ; => ((d e f) (1 2 3)) (println (drop-start-same ["あいさんさん" "あいどる" "あいうえお" "あいんしゅたいん"])) ; => ((さ ん さ ん) (ど る) (う え お) (ん し ゅ た い ん)) (println (drop-start-same ["12345" "67890" "abc"])) ; => [12345 1267890 abc]
全く一致部分がないとシーケンスのシーケンスじゃなくて元の文字列のシーケンスで返るところもちょっと嫌。
1000以下の回文素数で最大のものを示せ
プログラマ採用にあたり事前にコーディング技術を評価するCodeEval、βを終了して一般公開という記事がTechCrunchに出ていた。
リンク先にはデモのページがある。これによると例題として
「1000以下の最大の回文素数を見つけるプログラムを書きなさい」
とある。
回文数っていうのは「右から読んでも左から読んでも同じ数字になる数」でヒトケタのものも含まれる。例えば
1 2 3 11 22 121 1221 12221 123454321
などは回文数である。回文素数は素数でしかも回文数になっているもの。
手順としては
ことになるのは間違いないが、Clojureだと多少スマートに書ける。
ネットで調べてClojureで素数を見つけるプログラムはここにあったのでいただく(w
以下答:
(defn nums [] (iterate inc 2)) (defn sieve [[p & xs]] (remove #(= (rem % p) 0) xs)) (defn primes [] (map first (iterate sieve (nums)))) (defn palin? [n] (= (String. (into-array Character/TYPE (reverse (str n)))) (str n))) (last (filter palin? (take-while #(< % 1000) (primes))))
答えは 929。
文字のシーケンスを文字列にする関数がないのがちょっとしょぼい>Clojure。
(count (filter palin? (take-while #(< % 1000) (primes))))
で1000までの回文素数は20個あることもわかる。