Triggered Executionの対象ファイル

sbtではタスクの前に~(チルダ)をつけて実行すると、ファイルが更新されるたびに、再度タスクが実行される。再度タスクが実行されるためのトリガーは、タスクの依存関係などに関係なくProject traitのwatchPathsが返すパスで示されるファイルのいずれかが更新されることである。
タスクを新たに追加した際に、「タスク」と「タスクが依存するファイル集合」のペアをタスクの再実行のためにどこかに登録する必要があると勘違いしたため、メモ

サブプロジェクトを使う場合の注意

Simple Build Toolではサブプロジェクトを利用する際に、複数のサブプロジェクトで同じ外部Jarを利用する場合であっても、それぞれのプロジェクト毎にJarファイルを保存する。これを防ぐには、外部Jar専用のプロジェクトを作成し、外部Jarを利用するプロジェクトはそのプロジェクトに依存するようにする。

以下のように記述すると、project1/lib_managed、及びproject2/lib_managed以下にlog4jのjarがコピーされる。

import sbt._

class Project(info: ProjectInfo) extends DefaultProject(info) {
  lazy val project1 = project("project1", "Project 1", x => new DefaultProject(x) {
    val protobuf  = "log4j" % "log4j" % "1.2.16"
  })

  lazy val project2 = project("project2", "Project 2", x => new DefaultProject(x) {
    val protobuf  = "log4j" % "log4j" % "1.2.16"
  })
}

これを以下のように外部Jar用のサブプロジェクトを作成するとexternaljars/lib_managed以下のみにlog4jのjarがコピーされ、その上でProject1, Project2からlog4jが利用できる。

import sbt._

class Project(info: ProjectInfo) extends DefaultProject(info) {
  lazy val externaljars = project("externaljars", "External Jar files", x => new DefaultProject(x) {
    val protobuf  = "log4j" % "log4j" % "1.2.16"
  })

  lazy val project1 = project("project1", "Project 1", x => new DefaultProject(x) {
  }, externaljars)

  lazy val project2 = project("project2", "Project 2", x => new DefaultProject(x) {
  }, externaljars)
}

ScalaでHadoopのジョブを書く

HadoopのジョブをScalaで書くためのこんなものがある。

- http://blog.jonhnnyweslley.net/2008/05/shadoop.html

試したところ、サンプルがHadoop 0.20.2では動かないっぽい。
サンプルを動くように改変してみた。

WordCount.scala

import shadoop.SHadoop._

import java.util.Iterator
import org.apache.hadoop.fs._
import org.apache.hadoop.io._
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat
import org.apache.hadoop.mapreduce.Mapper
import org.apache.hadoop.mapreduce.Reducer

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.Path
import org.apache.hadoop.mapreduce.Job
import org.apache.hadoop.util.GenericOptionsParser

import scala.collection.JavaConversions._

object WordCount {

  class Map extends Mapper[AnyRef, Text, Text, IntWritable] {

    val one: IntWritable = 1

    override def map(key: AnyRef, value: Text, output: Mapper[AnyRef, Text, Text, IntWritable]#Context): Unit = {

      for (x <- value.split(" ")) {
        output.write(x, one)
      }
    }
  }

  class Reduce extends Reducer[Text, IntWritable, Text, IntWritable] {

    override def reduce(key: Text, values: java.lang.Iterable[IntWritable], context: Reducer[Text, IntWritable, Text, IntWritable]#Context): Unit = {

      val sum = values.reduceLeft( (x, y) => x + y)
      context.write(key, sum)
    }
  }

  def main(args: Array[String]) = {

    val conf = new Configuration()
    val otherArgs: Array[String] = (new GenericOptionsParser(conf, args)).getRemainingArgs()
    if (otherArgs.length != 2) {
      System.err.println("Usage: wordcount <in> <out>")
      exit(2)
    }
    val job = new Job(conf, "word count")
    job.setJarByClass(WordCount.getClass)
    job.setMapperClass(classOf[Map]);
    job.setCombinerClass(classOf[Reduce]);
    job.setReducerClass(classOf[Reduce]);
    job.setOutputKeyClass(classOf[Text]);
    job.setOutputValueClass(classOf[IntWritable])
    FileInputFormat.addInputPath(job, new Path(otherArgs(0)))
    FileOutputFormat.setOutputPath(job, new Path(otherArgs(1)))

    exit(if (job.waitForCompletion(true)) 0 else 1)
  }
}

overrideしたメソッドの引数のContextが書くのが激しくだるいので、SHadoopで工夫の余地する余地があるけど、他力本願的に何もしない。

mutable.Mapはなんなんだろうか

scala.collection.mutable.Mapの要素の追加が+=演算子なんだけど、これはなんでこんな腐った仕様になってるのだろうか

val a = scala.collection.mutable.Map("hoge" -> 1)
a = a + ("foo" -> 2)

これは当然エラー

val a = scala.collection.mutable.Map("hoge" -> 1)
a += "foo" -> 2

これはOK。
なんでこうなるかはわかるが、なんでこうしたのかわからん。

優先順位付キュー

優先順位付キューを使おうとしたときにヘルプを見ても、使い方がよくわからなかったためメモ。*1

http://www.scala-lang.org/api/current/scala/collection/mutable/PriorityQueue.html

クラスはscala.collection.mutable.PriorityQueue
enqueueで要素を追加

scala> import scala.collection.mutable.PriorityQueue
import scala.collection.mutable.PriorityQueue

scala> val a = new PriorityQueue[Int]()
a: scala.collection.mutable.PriorityQueue[Int] = PriorityQueue()

scala> a.enqueue(1)

scala> a.enqueue(2)

scala> a.enqueue(10)

scala> a
res4: scala.collection.mutable.PriorityQueue[Int] = PriorityQueue(10, 2, 1)

dequeueで要素を取り出す

scala> a.dequeue
res5: Int = 10

scala> a.dequeue
res7: Int = 2

scala> a.dequeue
res8: Int = 1

優先順位を変えるには、以下のようにnewの際に追加でOrdering型の引数を与える。

val b = new PriorityQueue[Int]()(scala.math.Ordering.Int.reverse)

*1:collection汎用のメソッドが多すぎて、enqueueとdequeueを見逃しただけなんですが