我正在使用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等)。
我希望很快就能开始,只需要一些例子来包围它。
答案 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),称为withFilter
和collect
。 withFilter
优于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
(这是A
和B
元组的序列),你就可以遍历它并打印它。这样,您可以轻松更改打印方式(您可能希望打印到控制台,发送到远程位置等),而无需触及主逻辑。
此外,您应该尽可能选择不可变值(val
),这样更安全。即使在您的循环中,row
,A
和B
也未被修改,因此没有理由使用var
。
(顺便说一句,我更正了值名称以小写字母开头,请参阅conventions)。