Итерация и обрезка строки в зависимости от условий в искре Scala - PullRequest
0 голосов
/ 30 августа 2018

У меня есть фрейм данных 'regexDf', как показано ниже

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

Если длина регулярного выражения превышает некоторую максимальную длину, например, 50, то я хочу удалить последний текстовый токен в разделенной строке регулярного выражения, разделенной символом '|' для превышенного идентификатора. В приведенном выше фрейме данных длина 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"))) 

Согласно предложению Сатьяна и Паша, я изменил рекурсивный метод как функцию.

      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")))
      }

Теперь я получаю исключение, так как "рекурсивное значение shrinkString нуждается в типе"

    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 голосов
/ 30 августа 2018

Рекурсия:

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; и т. Д.) Должны быть решены автором вопроса, чтобы избежать бесконечного цикла рекурсии.

0 голосов
/ 31 августа 2018

Просто чтобы добавить к @ 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(.*)                           |
+---+------------------------------------------------------------------------+

Теперь вы можете использовать функцию сжатия @ pasha701, используя udf

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(.*)|
+---+---------------------------------------------+
0 голосов
/ 30 августа 2018

Вот как бы я это сделал.

Во-первых, функция для удаления последнего токена из регулярного выражения:

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(.*)|(.*)
// (.*)|(.*)
// (.*)|(.*)
// (.*)|(.*)
// (.*)|(.*)
// (.*)|(.*)
// (.*)|(.*)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...