Scala /功能性服务方式

时间:2015-08-05 19:13:37

标签: scala functional-programming dataframe

我正在使用scala编写一个使用数据框从csv文件中读取数据的spark应用程序(这些细节都不重要,任何擅长函数式编程的人都可以回答我的问题)

我习惯于顺序编程,需要一段时间才能以功能的方式思考问题。

我基本上想从csv文件中读取列(a,b)并跟踪那些b< 0.

我实现了这个,但它几乎是我如何做Java而我想用Scala的功能代替:

val ValueDF = fileDataFrame.select("colA", "colB")                                           
val ValueArr = ValueDF.collect()

        for ( index <- 0 until (ValueArr.length)){
            var row = ValueArr(index)
            var A = row(0).toString()
            var B = row(1).toString().toDouble

            if (B < 0){
                //write A and B somewhere
            }
        }

将数据帧转换为数组会破坏分布式计算的目的。 那么我怎么可能得到相同的结果,而不是形成一个数组并遍历它,我宁愿想要对数据框本身进行一些转换(例如map / filter / flatmap等)。

我希望很快就能开始,只需要一些例子来包围它。

1 个答案:

答案 0 :(得分:4)

你基本上是在做一个过滤操作(如果不是(B < 0)则忽略)和映射(从每一行得到A和B /用A和B做一些事情)。

你可以这样写:

val valueDF = fileDataFrame.select("colA", "colB")                                           
val valueArr = valueDF.collect()

val result = valueArr.filter(_(1).toString().toDouble < 0).map{row => (row(0).toString(), row(1).toString().toDouble)}

// do something with result

您还可以先进行映射,然后进行过滤:

val result = valueArr.map{row => (row(0).toString(), row(1).toString().toDouble)}.filter(_._2 < 0)

Scala还为此类操作提供了更方便的版本(感谢Sascha Kolberg),称为withFiltercollectwithFilter优于filter的优势在于它不会创建新的集合,为您节省一遍,有关详细信息,请参阅this answer。使用collect,您还可以在一次传递中进行映射和过滤,传递允许进行模式匹配的部分函数,​​例如,这answer

在你的情况下collect看起来像这样:

val valueDF = fileDataFrame.select("colA", "colB")                                           
val valueArr = valueDF.collect()

val result = valueArr.collect{
  case row if row(1).toString().toDouble < 0) => (row(0).toString(), row(1).toString().toDouble)
}
// do something with result

(我认为有一种更优雅的方式来表达这一点,但这只是一种练习;)

此外,还有一个名为&#34;序列理解&#34;的轻量级符号。有了这个,你可以写:

val result = for (row <- valueArr if row(1).toString().toDouble < 0) yield (row(0).toString(), row(1).toString().toDouble)

或更灵活的变体:

val result = for (row <- valueArr) yield {
  val double = row(1).toString().toDouble
  if (double < 0) {
    (row(0).toString(), double)
  }
}

或者,您可以使用foldLeft

val valueDF = fileDataFrame.select("colA", "colB")                                           
val valueArr = valueDF.collect()

val result = valueArr.foldLeft(Seq[(String, Double)]()) {(s, row) =>
  val a = row(0).toString()
  val b = row(1).toString().toDouble

  if (b < 0){
    s :+ (a, b) // append tuple with A and B to results sequence
  } else {
    s // let results sequence unmodified 
  }
}

// do something with result

所有这些都被认为是功能性的...你喜欢哪一个在很大程度上是品味问题。前两个示例(过滤器/映射,映射/过滤器)与其他示例相比确实具有性能劣势,因为它们遍历序列两次。

请注意,在FP中,最大限度地减少副作用/将它们与主逻辑隔离是非常重要的。 I / O(&#34;在某处写A和B&#34;)是副作用。所以你通常会编写你的功能,使他们没有副作用 - 只需输入 - &gt;输出逻辑,不影响或从周围环境中检索数据。获得最终结果后,您可以进行副作用。在这个具体案例中,一旦你有result(这是AB元组的序列),你就可以遍历它并打印它。这样,您可以轻松更改打印方式(您可能希望打印到控制台,发送到远程位置等),而无需触及主逻辑。

此外,您应该尽可能选择不可变值(val),这样更安全。即使在您的循环中,rowAB也未被修改,因此没有理由使用var

(顺便说一句,我更正了值名称以小写字母开头,请参阅conventions)。