下面的代码将一个字符串列表分为类型List [(String,List [String])] 在长度为5的字符串中遇到所有大写的情况下,这是标识符,标识符后面的所有数据都被分组到一个列表中。每个组的终止因素是遇到的空行。所以" line"转换为:
(IDENT,List(p1text, p2text))
(IDENY,List(p2text, p3text, p4text))
在Scala / Spark中有更实用的方法吗?
可能使用带谓词的groupBy
调用?
理想情况下,数据结构的类型为RDD [(String,List [String])]而不是List [(String,List [String])]
val lines = List[String]("line1",
" ",
"line2",
" ",
" IDENT",
"p1text",
"p2text",
" ",
" IDENY",
"p2text",
"p3text",
"p4text",
" ",
"some text") //> lines : List[String] = List(line1, "
//|
//|
//| ", line2, "
//|
//|
//| ", " IDENT", p1text, p2text, "
//|
//|
//| ", " IDENY", p2text, p3text, p4text, "
//| ", some text)
def getItems(i: Int): List[String] = {
var iter = i;
val l = new scala.collection.mutable.ArrayBuffer[String]()
while (!lines(iter).trim.isEmpty) {
iter = iter + 1
if(!lines(iter).trim.isEmpty)
l.append(lines(iter).trim)
}
l.toList
} //> getItems: (i: Int)List[String]
val regex = "\\w{5}" //> regex : String = \w{5}
val u: List[(String , List[String])] = lines.zipWithIndex.map({
case (s, i) => {
if (s.trim.toUpperCase.matches(regex)) {
(s.trim, getItems(i))
} else {
("" , List())
}
}
}) //> u : List[(String, List[String])] = List((line1,List()), ("",List()), (line
//| 2,List()), ("",List()), (IDENT,List(p1text, p2text)), ("",List()), ("",List
//| ()), ("",List()), (IDENY,List(p2text, p3text, p4text)), ("",List()), ("",Li
//| st()), ("",List()), ("",List()), ("",List()))
val fi : List[(String, List[String])] = u.filterNot(f => f._2.isEmpty || f._2(0).trim.isEmpty)
//> fi : List[(String, List[String])] = List((IDENT,List(p1text, p2text)), (ID
//| ENY,List(p2text, p3text, p4text)))
fi.foreach(println) //> (IDENT,List(p1text, p2text))
//| (IDENY,List(p2text, p3text, p4text))
答案 0 :(得分:1)
您可以从惯用的方式开始在Scala中编写拆分:作为递归函数。
def getItems(l: List[String]): List[(String, List[String])] = {
if (l.isEmpty) List()
else {
val caps = "[A-Z]+".r
val (beg, end) = l.span(_.trim.nonEmpty)
if (beg.nonEmpty)
beg.head.trim match {
case caps() => (beg.head.trim, beg.tail) :: getItems(end.drop(1))
case _ => getItems(end.drop(1))
}
else
getItems(end.tail)
}
}
然后你可以通过使它成为尾递归函数来加速它。
import scala.annotation.tailrec
def getItemsFast(l: List[String]): List[(String, List[String])] = {
@tailrec
def getItemsAux(l: List[String], res: List[(String, List[String])]): List[(String, List[String])] = {
if (l.isEmpty) res.reverse
else {
val caps = "[A-Z]+".r
val (beg, end) = l.span(_.trim.nonEmpty)
if (beg.nonEmpty)
beg.head.trim match {
case caps() => getItemsAux(end.drop(1), (beg.head.trim, beg.tail)::res)
case _ => getItemsAux(end.drop(1), res)
}
else
getItemsAux(end.tail, res)
}
}
getItemsAux(l,List())
}
然后,如果你有RDD的行,那么在Spark中检索它的简单(但不正确,见下文)是你的RDD上的mapPartition
。
myRDDOfLines.mapPartitions(lines => {
getItemsFast(lines)
})
这应该主要起作用,但是这将无法注意到已经分区的记录,使得标识符在一个分区中,但是某些“它的”行在下一个分区中落后。
错误是你将记录构建为可分区单元的方式:你真正想要的是记录的RDD(一条记录是上面输出列表的一个元素,应该清楚键和值应该是什么) 。这不是sc.textFile
给你的。有可能将这些数据更好地加载到Spark中。例如:
wholeTextFiles
TextInputFormat
和RecordReader
hadoop.Configuration
对象提供分隔符...