根据Spark Scala中的条件迭代和修剪字符串

时间:2018-08-30 14:42:52

标签: scala apache-spark apache-spark-sql

我有如下所示的数据框“ regexDf”

id,regex
1,(.*)text1(.*)text2(.*)text3(.*)text4(.*)|(.*)text2(.*)text5(.*)text6(.*)
2,(.*)text1(.*)text5(.*)text6(.*)|(.*)text2(.*)

如果正则表达式的长度超过某个最大长度(例如50),那么我想删除以“ |”分隔的正则表达式字符串中的最后一个文本标记对于超出的ID。在上面的数据帧中,id 1的长度大于50,因此应删除每个分割的正则表达式字符串中的最后一个标记'text4(。)'和'text6(。)'。即使删除了ID 1中的正则表达式字符串的长度也仍然超过50,所以应该再次删除最后一个标记'text3(。)'和'text5(。)'。数据框将为

id,regex
1,(.*)text1(.*)text2(.*)|(.*)text2(.*)
2,(.*)text1(.*)text5(.*)text6(.*)|(.*)text2(.*)

我可以使用以下代码修剪最后的令牌

  val reducedStr = regex.split("|").foldLeft(List[String]()) {
    (regexStr,eachRegex) => {
      regexStr :+ eachRegex.replaceAll("\\(\\.\\*\\)\\w+\\(\\.\\*\\)$", "\\(\\.\\*\\)")
    }
  }.mkString("|")

我尝试使用while循环检查长度,并在无法正常工作的迭代中修整文本标记。我也想避免使用var和while循环。是否可以不使用while循环来实现。

         val optimizeRegexString = udf((regex: String) => {
              if(regex.length >= 50) {
                var len = regex.length;
                var resultStr: String = ""
                while(len >= maxLength) {
                  val reducedStr = regex.split("|").foldLeft(List[String]()) {
                    (regexStr,eachRegex) => {
                      regexStr :+ eachRegex
    .replaceAll("\\(\\.\\*\\)\\w+\\(\\.\\*\\)$", "\\(\\.\\*\\)")
                    }
                  }.mkString("|")
                  len = reducedStr.length
                  resultStr = reducedStr
                }
                resultStr
              } else {
                regex
              }
            })
            regexDf.withColumn("optimizedRegex", optimizeRegexString(col("regex"))) 

根据SathiyanS和Pasha的建议,我将递归方法更改为函数。

      def optimizeRegex(regexDf: DataFrame): DataFrame = {
        val shrinkString= (s: String) =>   {
          if (s.length > 50) {
            val extractedString: String = shrinkString(s.split("\\|")
.map(s => s.substring(0, s.lastIndexOf("text"))).mkString("|"))
            extractedString
          }
          else s
        }
        def shrinkUdf = udf((regex: String) => shrinkString(regex))
        regexDf.withColumn("regexString", shrinkUdf(col("regex")))
      }

现在,我由于“递归值rinkleString需要类型”而出现异常

    Error:(145, 39) recursive value shrinkString needs type
            val extractedString: String = shrinkString(s.split("\\|")
.map(s => s.substring(0, s.lastIndexOf("text"))).mkString("|"));

3 个答案:

答案 0 :(得分:1)

这就是我要做的。

首先,一个用于从正则表达式中删除最后一个令牌的函数:

def deleteLastToken(s: String): String =
  s.replaceFirst("""[^)]+\(\.\*\)$""", "")

然后,该函数通过删除所有|分隔的字段中的最后一个标记来缩短整个正则表达式字符串:

def shorten(r: String) = {
  val items = r.split("[|]").toSeq
  val shortenedItems = items.map(deleteLastToken)
  shortenedItems.mkString("|")
}

然后,对于给定的输入正则表达式字符串,通过重复应用shorten函数来创建所有缩短的字符串的流。这是一个无限的流,但是它是惰性计算的,因此实际上只计算所需数量的元素:

val regex = "(.*)text1(.*)text2(.*)text3(.*)text4(.*)|(.*)text2(.*)text5(.*)text6(.*)"

val allShortened = Stream.iterate(regex)(shorten)

最后,您可以将allShortened视为任何其他序列。为了解决我们的问题,您可以删除所有不满足长度要求的元素,然后仅保留其余元素中的第一个:

val result = allShortened.dropWhile(_.length > 50).head

您可以通过打印allShortened的某些元素来查看所有中间值:

allShortened.take(10).foreach(println)

// Prints:
// (.*)text1(.*)text2(.*)text3(.*)text4(.*)|(.*)text2(.*)text5(.*)text6(.*)
// (.*)text1(.*)text2(.*)text3(.*)|(.*)text2(.*)text5(.*)
// (.*)text1(.*)text2(.*)|(.*)text2(.*)
// (.*)text1(.*)|(.*)
// (.*)|(.*)
// (.*)|(.*)
// (.*)|(.*)
// (.*)|(.*)
// (.*)|(.*)
// (.*)|(.*)

答案 1 :(得分:1)

递归:

def shrink(s: String): String = {
if (s.length > 50)
  shrink(s.split("\\|").map(s => s.substring(0, s.lastIndexOf("text"))).mkString("|"))
else s
}

看起来像函数调用方面的问题,还有一些其他信息。 可以称为静态函数:

object ShrinkContainer  {
  def shrink(s: String): String = {
    if (s.length > 50)
      shrink(s.split("\\|").map(s => s.substring(0, s.lastIndexOf("text"))).mkString("|"))
    else s
  }
}

与数据框链接:

def shrinkUdf = udf((regex: String) => ShrinkContainer.shrink(regex))
df.withColumn("regex", shrinkUdf(col("regex"))).show(truncate = false)

缺点:仅提供了基本示例(方法)。为了避免无限递归循环,某些边缘情况(如果正则表达式不包含“文本”,如果用“ |”分隔的太多部分,例如100;等等)必须由问题作者解决。

答案 2 :(得分:0)

只需添加到@ pasha701答案即可。这是在火花中起作用的解决方案。

val df = sc.parallelize(Seq((1,"(.*)text1(.*)text2(.*)text3(.*)text4(.*)|(.*)text2(.*)text5(.*)text6(.*)"),(2,"(.*)text1(.*)text5(.*)text6(.*)|(.*)text2(.*)"))).toDF("ID", "regex")

df.show()
//prints
+---+------------------------------------------------------------------------+
|ID |regex                                                                   |
+---+------------------------------------------------------------------------+
|1  |(.*)text1(.*)text2(.*)text3(.*)text4(.*)|(.*)text2(.*)text5(.*)text6(.*)|
|2  |(.*)text1(.*)text5(.*)text6(.*)|(.*)text2(.*)                           |
+---+------------------------------------------------------------------------+

现在您可以使用udf使用@ pasha701收缩功能

val shrink: String => String = (s: String) => if (s.length > 50) shrink(s.split("\\|").map(s => s.substring(0,s.lastIndexOf("text"))).mkString("|")) else s

def shrinkUdf = udf((regex: String) => shrink(regex))

df.withColumn("regex", shrinkUdf(col("regex"))).show(truncate = false)

//prints
+---+---------------------------------------------+
|ID |regex                                        |
+---+---------------------------------------------+
|1  |(.*)text1(.*)text2(.*)|(.*)text2(.*)         |
|2  |(.*)text1(.*)text5(.*)text6(.*)|(.*)text2(.*)|
+---+---------------------------------------------+