使用Scala / Apache Spark对数据进行分组

时间:2015-01-15 12:15:07

标签: scala apache-spark

下面的代码将一个字符串列表分为类型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))

1 个答案:

答案 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
  • 实施自定义TextInputFormatRecordReader
  • 如果您可以删除最少数量的空白字符以用作记录分隔符,则可以使用hadoop支持多行记录,通过hadoop.Configuration对象提供分隔符...