scalding 之数据处理

这部分和 Pig 是比较像的。主要参考了这个页面

map-like function

其实和 PIG 的 foreach generate 的语法作用类似,这里包括 map 与 flatMap,后者会将返回的 list flatten 开,核心的语法是

pipe.map (existingFields -> additionalFields){function}

这会产生一个新的 field,使用的就是 function 部分定义的形式。另一个与其类似的是 mapTo 和 flatMapTo,这会将 existingFields 去掉,使用新的 additionalFields 替换。

另外两个仅仅做 field 选择的是 project 和 discard,前者选择保留下来的 field,后者选择被抛弃的 field。

与 PIG 的 filter by 语法类似的是 filter,

pipe.filter (fields){function}

这里 fields 是参与判断的域,而 function 返回 true/false 表示是否保留当前记录。

与 PIG 的 limit 相似的是 limit 操作,

pipe.limit (number)

与 PIG 的 unique 类似的是这里的 unique,这必须通过一次 map/reduce 来做,而不像前面的操作可以在 mapper 侧完成。

比较不同的是 pack 和 unpack,他们可以将一些 field pack 到一个对象里面(这似乎是将 PIG 里面某个 tuple 转换成为了 Java 的对象),但这必须要求这几个部分都支持 reflection 和通过 default constructor 初始化,且使用 getter/setter 进行赋值。

grouping-like functions

这些与 PIG 的 group by 类似。其一是 groupBy,另外一个是 groupAll(与 group all 类似),

pipe.groupBy(fields){ group => ... }

这里 group 后面的映射可以产生 group 后需要生成的额外的域,对应的 group 对象可以使用 size 返回大小,average (field) 计算均值(和 UDF AVG 类似),sizeAveStdev 计算对应的大小、均值和标准差,makeStr (fields, joiner) 将某个域粘和在一起,toList 转换成 list,另有 sum、count 和 sourtBy 都是比较简单的。还可以通过 take、sortWithTake 取出其中若干个。reducers (n) 设定 reducer 个数。

reduce 和 foldLeft 是用来将 group 好的结果处理的,前者会在 mapper 侧使用 combiner 要求必须满足结合律,后者没有这个需要,语法大致如下

group.reduce(field){function}

一个比较特别的操作是 pivot/unpivot,提供的是行列表示的转换。

joining-like functions

这个和 PIG 的 join、cogroup by 很类似。主要表示两个 pipe 之间的关系。如 joinWithLarger、joinWithSmaller 以及 joinWithTiny 提供了三种可能的 join 情形(分别有一些优化),基本语法例如

pipe1.joinWithSmaller(fields, pipe2)

我们可以通过 joiner 设置 join 时哪些为 null 的时候需要保留,我们只需要 joiner = new Left/Right/OuterJoiner 即可。

与 PIG 的 cross 类似也有 crossWithTiny 这类。

小结

值得注意的是 PIG 在这类问题里面的 function 一般都得使用 UDF 来实现,PIG 本身提供的支持很少。为了简化 UDF 的编写,PIG 使用的策略是让程序员可以使用 scripting language。而 scala 是允许直接嵌入一个 scala 的函数来就地解决问题的。

——————-
And Abraham set seven ewe lambs of the flock by themselves.

scalding 之数据处理

scala 再探

scala 除了上次学习到的一些粗浅的 idea 以外,其实还有很多。这部分内容来自 Beginning Scala 一书。

functional programming

这个解释来自 python 的文档,functional programming 中使用的 function 是我们所谓的 regular function,即输出仅仅依赖于输入,换言之,函数本身不依赖于外部变量(全局变量)也不依赖于内部变量(如 static 类型),求解问题的时候会给定一个函数序列,数据从中穿过得到输出。因此从这个角度来看使用 functional programming 处理数据似乎是非常方便的选择了。

解释执行与编译执行

除了使用 scalac 进行编译这类与 java 类似的执行以外,我们还可以直接通过 scala 启动 REPL 模式(read-eval-print-loop),这得到了一个类似 python 的交互式环境,我们甚至可以直接通过 scala 解释执行对应的 scala 源文件而不经过 scalac 的显式编译阶段(当然会变慢)。可以尝试玩一下下面的程序(该自书中的例子,需要注意的是书上的 API 是 2.7 的,现在这个是按 2.9 的写的)

import scala.io._

def toInt (in: String): Option[Int] =
  try {
    Some (Integer.parseInt (in.trim))
  } catch {
    case e: NumberFormatException => None
  }

def sum (in: Seq[String]) = {
  val ints = in.flatMap (s => toInt (s))
  ints.foldLeft (0)((a, b) => a + b)
}

println("Enter some numbers and press ctrl-D (Unix/Mac) ctrl-C (Windows)")
val input = Source.fromInputStream (System.in)
val lines = input.getLines.toSeq
println ("Sum "+sum (lines))

其实从文件的组织形式来看 scala 与 python 也更加相似。值得注意的是这段程序里面这几个东西

  • 函数的返回值不用 return,而是最后一个表达式
  • 匿名函数直接用 =>
  • 语句组用 {}
  • Option 这个东西可以为某些类提供 wrap 上的 None 返回值,非 None 时通过 Some 返回,换言之 Some 是 Option 的 case class

变量声明

有 var、val 和 lazy val 三种,var 和 Java 里面的变量类似,设定后后面可以更改什么的,val 只是记录了后面的 code block,被使用的时候才会 evaluate;lazy val 类似,但是仅仅被 evaluate 一次,后面直接用值

call-by-name

这个是一个奇怪的东西,现在想来当时觉得 R 这语言奇怪可能也是这个原因。我们知道 call-by-value 和 call-by-reference,即传递值还是引用,前者传递了一个 copy 过去,因此函数里面对其修改不会影响到原变量,而后者会,相当于只是传递了一个别名过去。而 call-by-name 是指传递的是对应的 code block,但是这个 block 仅在函数中被用到的时候才被 evaluate。

def nano () = {
  println ("getting nano")
  System.nanoTime
}

def delayed (t: => Long) = {
  println ("in delayed method")
  println ("param: " + t)
  t
}

def not_delayed (t: Long) = {
  println ("in delayed method")
  println ("param: " + t)
  t
}

delayed (nano ())
println ("-----------")
not_delayed (nano ())

这个例子里面的 delayed 和 not_delayed 仅仅在表达参数类型上有所区别,前者多了一个 => 表示 call-by-name,因此函数体里面两次调用 t 于是 nano 就调用了两次。

compiler magic

一个是关于 apply 的,因为 JVM 不支持传递“函数指针”,而对 functional language 肯定需要将函数本身进行传递的,于是有一些 tricky,这主要依赖于 Function1(类似有 Function2、PartialFunction)这类东西,他们是一个 trait(或者interface),可以用其 apply 获得函数的值,作为 gramma sugar,实现了这个 trait 的对象可以直接用 operator () 来获得调用 apply 的结果。

另一个是关于 update,这个是为了实现类似 a.(…) = b 这类操作,他通过 update 来实现。

class Foo {
  def apply (a: Int) : String = {
    println (a)
    a.toString
  }
}

val a = new Foo
a (2)

class Bar {
  def update (k: Int, v: String) = println ("debug: " + k + ", " + v)
}

val b = new Bar
b (1) = "end"

case class

case class 与正常的 class 的区别是它实现了 toString、hashCode 和 equals,并且提供了 object 类用于构造以及将数据解析成 tuple。使用 case class 可以节省一些额外的 code。

case class CStuff (name: String, age: Int)

class NStuff (name: String, age:Int)

val a = CStuff ("DC", 4)
val b = new NStuff ("DC", 4)
// val b = NStuff ("DC", 4)

println (a.toString)
println (b.toString)

if (a == CStuff ("DC", 4)) println ("deep comparison for CStuff")
if (b == new NStuff ("DC", 4)) println ("deep comparison for NStuff")

println (a.name)
//println (b.name)

class SStuff (val name: String, val age:Int) {
  override def toString: String = "SStuff(" + name + "," + age + ")"
  override def hashCode = name.hashCode + age
  override def equals (other: Any) = other match {
    case s: SStuff => name == s.name && age == s.age
    case _ => false
  }
}

object SStuff {
  def apply (name: String, age: Int) = new SStuff (name, age)
  def unapply (s: SStuff) = Some ((s.name, s.age))
}

val c = SStuff ("DC", 4)
println (c.toString)
if (c == SStuff ("DC", 4)) println ("deep comparison for SStuff")
println (c.name)

从这个例子可以看出来我们需要额外提供多少东西,被注释的 code 是会导致编译错误的。

pattern matching

除了 case class 以外,pattern matching 可以出现的地方比起 C 里面非常有限制的用法还是灵活多了,除了匹配常量以外,结合 scala 自己的通配符 _,可以部分的匹配某些 pattern,或者类(上面例子里面有)。

if/else 与 while

这里的 if/else 是一个语句,并且可以在 = 后面换行。while 更加简单,似乎没见 do while 循环,估计也支持。

for

写法不大一样,还可以配上 guards。

for {i <- 1 to 9
     j <- 1 to i}
  println (i + " * " + j + " = " + i * j)

for {i <- 1 to 9} {
  for {j <- 1 to i}
    print (i + " * " + j + " = " + i * j + "\t")
  println ("")
}

如果需要改变步长,使用 by,使用 if 增加 guard。

throw/try/catch/finally

与 Java 类似,只是 catch 时使用 case 进行 match。

comments

与 C++/Java 类似,但是 /* */ 类型的注释可以嵌套。

—————–
And Abraham took sheep and oxen, and gave them to Abimelech; and both of them made a covenant.

scala 再探