2011年11月04日

Scala -標準ライブラリでXMLのパースとか-

ちょっとだけXMLをパースする必要があったので、メモがてら。

例えば下記のよなtwitterのfollow,followerリストのXMLがあったとして、

<?xml version="1.0" encoding="UTF-8"?>

<my_users>
<follow>
<account><id>1</id><name>相互followさん</name><follower_num>1</follower_num></account>
<account><id>2</id><name>follow1</name><follower_num>2</follower_num></account>
<account><id>3</id><name>follow1</name><follower_num>3</follower_num></account>
</follow>
<follower>
<account><id>4</id><name>follower1</name><follower_num>4</follower_num></account>
<account><id>5</id><name>follower2</name><follower_num>5</follower_num></account>
<account><id>1</id><name>相互followさん</name><follower_num>1</follower_num></account>
</follower>
</my_users>

これを
・followしている人の名前を表示する
・followしている人の中でfollowerが二人以上いる人の名前
・相互followしている人の名前
でパースしたい時は、下記のようなコードになる。

import scala.xml.XML

object TestXMLParse{
def main(args:Array[String]) = {
//XML読み込む
val xml = XML.loadFile(args(0))

val my_follow = xml \ "follow"
val my_follower = xml \ "follower"

//-----followしている人の名前を表示する場合-----//
println("##########followしている人の名前を表示##########")
(my_follow \ "account").foreach(n => println((n \ "name").text))
//↓こっちでもOK
//my_follow.foreach(p => (p \ "account").foreach(n => println((n \ "name").text)))

//-----followしている人の中でfollowerが二人以上いる人の名前を表示-----//
println("##########followしている人の中でfollowerが二人以上いる人の名前を表示##########")
(my_follow \ "account").filter(n => (n \ "follower_num").text.toInt >= 2).foreach(s => println((s \ "name").text))

//-----相互followしている人の名前を表示-----//
println("##########相互followしている人の名前を表示##########")
val follower_ids = (my_follower \ "account").map(n => (n \ "id").text.toInt)
(my_follow \ "account").filter(p => follower_ids.find(_==(p \ "id").text.toInt).getOrElse(null)!=null).foreach(s => println((s \ "name").text))
}
}
TestXMLParse.main(Array("hoge.xml"))

ラクチン、ラクチン。簡単なパースなら標準のxmlパーサーで全然いけますね。


posted by purigen at 11:00| Comment(0) | TrackBack(0) | Scala | このブログの読者になる | 更新情報をチェックする

2011年11月02日

CSVパーサーを書いてみた(Scala)

CSVパーサーを書いてみた

scala.util.parsing.combinator辺りを使うと良いのだろうけど、Scala初心者の僕にはレベルが高いので自力で簡易CSVパーサーを書いてみました。

import java.io._

class CSVParser{
case class Field(val s:String){
override def toString = s
}
case class Record(val fields:List[Field]){
override def toString = {
var s = ""

fields.foreach(i => {
s += i.toString + ","
})
s
}
}
case class CSVFile(val records:List[Record]){
def getRow(row:Int) = {
records(row)
}
def getCell(row:Int,column:Int) = {
records(row).fields(column)
}
}

def read(path:String) = {
val file = new File(path)
val fr = new FileReader(file)
val br = new BufferedReader(fr)

var records = List[Record]()
var line = ""
line = br.readLine()
while(line != null){
records = records :+ Record(List.fromArray(line.split(",")).map(i => {Field(i)}))
line = br.readLine()
}

CSVFile(records)
}
}

使い方は、

val csv = (new CSVParser()).read("./workbook.csv")

//セルの内容をずらーっと表示するだけの例
/*
csv.records.foreach(i => {
println("line")
i.fields.foreach(s => {
println(s.s)
})
})
*/
//getRow,getCell
/*
var i = 0
while(i < csv.records.length){
println(csv.getRow(i))
var record = csv.getRow(i)
var s = 0
while(s < record.fields.length){
//コメント外すとずらーっとセルの内容を表示
//println(csv.getCell(i,s))

s += 1
}

i+=1
}
*/

Cellの内容に","が入ってたら機能しないダメダメパーサーですけどね。
posted by purigen at 11:00| Comment(0) | TrackBack(0) | Scala | このブログの読者になる | 更新情報をチェックする

2011年10月21日

Scala-テスト、テストをするんだ-

Scala-テスト、テストをするんだ-

テストを書くのって大事ですよね。ScalaのテストフレームワークはSpecsが有名っぽいので使ってみます。

Specsをゲットしてくる


とにも各にも手に入れないとね。
specsのDownloadsからゲットします。私の場合は、specs_2.8.0-1.6.5.jarをゲットしました。 パスの通っている所に配置します。

・scalaのirbでテストするなら、「c:\scala\lib」にでも入れればOK(scalaがc:\scalaに入れてあるとして)
・eclipseでやるなら、「プロジェクト」→「右クリック」→「プロパティ」→「Javaのビルド・パス」→「ライブラリー」→「JARの追加(or外部JARの追加)」で追加してやる。

サンプルをやってみる


specsにQuickStartがあったので、ほとんどまんまコピーする。

import org.specs._
object HelloWorld extends Specification {
"'hello world' has 11 characters" in {
"hello world".size must_== 11
}
"'hello world' matches 'h.* w.*'" in {
"hello world" must be matching("h.* w.*")
}
}

サンプルはclassになってるけど、Eclipseで実行するにはobjectのが都合が良いのでobjectに変更&頭文字を大文字にした以外はまんま。

「プロジェクト」→「右クリック」→「デバッグ」→「デバッグの構成」

で、メイン・クラスをHelloWorldにして起動構成を作ります。後は普通に実行したら、specsの実行出力がコンソールに表示されます。 これで、とりあえずの実行は終わり。

基本的な使い方


シンプルな基本形としては、

import org.specs._
object TestSpecs extends Specification{//TestSpecsはお好きなオブジェクト名
"意味のあるテストの塊" should{
"テスト内容の説明" in {
//==でチェックできる内容の場合:チェックした数値 must_== 期待する値
1+1 must_== 2
//文字列の正規表現チェックの場合:"文字列" must be matching("正規表現")
"hello" must be matching("^h.*o$")
}
}
}

比較記述


数値の比較演算の記述は、

"1+1は2" in {
println("hoge1")
1+1 must_== 2
}
"1+1は1じゃにゃい" in {
println("hoge1")
1+1 must_!= 1
}
"1+1は1より大きい" in {
1+1 must be_> (1)
}
"1+1は2以上" in {
1+1 must be_>= (2)
}
"1+1は3より小さい" in {
1+1 must be_< (3)
}
"1+1は2以下" in {
1+1 must be_<= (2)
}



テスト間で変数共有したいよね?


テスト間で変数が共有できると便利ですよね。便利なんです。 ただ、普通に書くと変数は共有できません。

"変数の共有テスト(失敗)" should{
var x = 0
"x=1" in{
x = 1
x must_== 1 //成功
}
"変数共有できていないのでxは0のまま" in{
x must_== 0 //成功
}
}

ひとつ目のテストで、「x=1」と代入しているので、2個目のテストは失敗しそうなものですが、変数が共有できていないので、成功します。 どうすれば変数共有ができるかと言えば

"変数の共有テスト(成功)" should{
setSequential()
shareVariables()
var x = 0
"x=1" in{
x = 1
x must_== 1 //成功
}
"shareVariables()を呼び出しているので共有できているのでxは1" in{
x must_== 1 //成功
}
}

とテスト実行前に

setSequential() //テストの実行順番を定義順にする
shareVariables() //変数を共有する

を実行します。これで、変数xを二つのテスト間で共有する事ができます。

テストのループしたいよねー


少し変数を変えてとか、複数回テストしたい場合がありますよね。あるんです。そういう時は、下記のように書くことができます。

"ループテスト1" should {
//setSequential() ループ処理で呼ぶとダメです
shareVariables()
var counter = 1
doAfter(counter += 1)
until(counter == 3)

"ループ処理1" in {
println("ループ処理1 counter="+counter)
counter must be_>(0)
}
"ループ処理2" in {
println("ループ処理2 counter="+counter)
counter must be_>(0)
}
}

1から3まで(2ループ)「ループ処理1」、「ループ処理2」を実行してくれます。 このループを下記のように書くことも可能です。

var counter = 1
val counters = afterContext(counter += 1).until(counter == 3)
"ループテスト1" ->-(counters) should {
//setSequential() ループ処理で呼ぶとダメです
//shareVariables()

"ループ処理1" in {
println("ループ処理1 counter="+counter)
counter must be_>(0)
}
"ループ処理2" in {
println("ループ処理2 counter="+counter)
counter must be_>(0)
}
}

記述的には綺麗ですが、should外部に変数を作るのがどうかなーというところ。

頼れるSpecContext


テストの前後に初期化や後処理をしたい時に頼れるのがSpecContext。

object TestSpecs extends Specification {
new SpecContext{

before(各テスト開始前に行ないたい処理を書く)
after(各テスト終了後に行ないたい処理を書く)
beforeSpec(テストを始めるまえに行ないたい処理を書く)
afterSpec(テストが全部終わった後に行ないたい処理を書く)
}
}

と各処理を登録しておけば自動でやってくれるのです。ラクチンラクチン。


参考:
DeclareSpecifications
posted by purigen at 11:00| Comment(0) | TrackBack(0) | Scala | このブログの読者になる | 更新情報をチェックする
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。