【问题标题】:How to match a particular string/regex split over different lines as seperate strings?如何将不同行上拆分的特定字符串/正则表达式匹配为单独的字符串?
【发布时间】:2019-05-17 03:31:12
【问题描述】:

这是从我编写的代码中获得的内容的演示。我正在使用 Source.fromFile(filePath) 懒惰地读取文件并使用 .getLines() 方法将每一行读取为字符串并对其进行迭代以查找其中是否出现某个单词/模式。

让我们将要匹配的模式视为“.read”。 .如果整个模式出现在同一行, line.contains(".read.") 就可以了。 如果它以下列任何一种方式传播到后续行,就会出现问题:

.
read.

.
read
.

.read
.

我什至无法在 List[String] 中收集文件的全部内容,因为内存消耗太大以至于无法通过使用索引连接上一行或下一行,因为它是一个 bufferedSource 迭代器正在使用。

val bufferedSource = Source.fromFile("C:/code.scala")
val key = ".read."
var lineCounter = 0
for (bufferedline <- bufferedSource.getLines()) {
    lineCounter+=1
    val line = bufferedline.trim
    if (line.length() != 0) {
    if(line.contains(".read."))
        println("Found pattern at : "+lineCounter)
    }
}

当模式分布在多个字符串而不是由 newLine 字符分隔的单个字符串时,我不确定如何合并更改。任何有关如何解决此类问题的帮助将不胜感激。

注意 - 如果要匹配的模式分布在 3 行中,这只是一个简单的示例,但是如果要找到特定字符串说“spark.read.option”并且它分布在 5 个不同的行。

【问题讨论】:

  • 你想对比赛做什么?数他们?从文件数据中删除它们?您正在寻找的模式总是一个具体的字符串还是可能是一个正则表达式模式?如果模式可以拆分为换行符,是否也可以拆分为其他空白字符或多个换行符?
  • 我想找到正在读取的文件中出现这些特定匹配项的行号。它也可能是一个正则表达式模式。该模式将始终仅在换行符之间拆分,因为我通过修剪每一行作为 rea 来处理空格 m,如果有多个新行,我将条件放在 line.length! =0。但是,是的,换行符之后也可以有多个换行符和空格。
  • a.z 这样的正则表达式模式将匹配abzaxz,但它不会匹配ab\nz。因此,除非正则表达式模式考虑了内部任何地方可能存在的换行符,并且所有 5 个文本行都已被读取和缓冲,否则将无法找到拆分为 5 行文本的目标字符串,这会使您不将所有内容都吸入内存的目标变得复杂。跨度>

标签: regex scala multiline


【解决方案1】:

如果我尝试这样做,我会:

  1. 放弃使用getLines()。跨多行文本查找目标变得复杂。
  2. 放弃使用正则表达式模式作为目标字符串。要找到一个匹配项,其中可能包含多个 \n 字符,也可能不包含多个字符。

因此,我会使用逐个字符的搜索来寻找设定的目标。

def findInFile(charItr :Iterator[Char], target :String) :Unit = {
  assert(target.nonEmpty)
  def consumeStr(subci   :Iterator[Char]
                ,str     :String
                ,lineNum :Int
                ) :Option[(Iterator[Char],Int)] =
    if      (str.isEmpty)    Some((subci,lineNum))
    else if (!subci.hasNext) None
    else subci.next() match {
      case '\n'               => consumeStr(subci, str, lineNum + 1)
      case c if c == str.head => consumeStr(subci, str.tail, lineNum)
      case _                  => None
    }

  def loop(ci :Iterator[Char], line :Int) :Unit = if (ci.hasNext) {
    ci.next() match {
      case '\n' => loop(ci, line+1)
      case c if c == target.head =>
        val (oldci,newci) = ci.duplicate
        consumeStr(newci, target.tail, line).fold(loop(oldci, line)){
          case (itr,ln) => println(s"target found: line $line")
                           loop(itr,ln)
        }
      case _ => loop(ci, line)
    }
  }

  loop(charItr, 1)
}

这是我使用的测试文件...

xxx
x
aa
aaaa
a.b
b.c
cccc
a
aa.bb.caaa.bb.cc.dd
xxx

...以及我搜索的测试目标。

val src = io.Source.fromFile("so.txt")
findInFile(src, "aaa.bb.cc")
src.close()
//target found: line 4
//target found: line 9

好的,所以我重新调整了一下findInFile()

def findInFile(charItr :Iterator[Char], target :String) :List[(Int,String)] = {
  assert(target.nonEmpty)
  def consumeStr(subci   :Iterator[Char]
                ,str     :String
                ,lineNum :Int
                ) :Option[(Iterator[Char],Int)] =
    if      (str.isEmpty)    Some((subci,lineNum))
    else if (!subci.hasNext) None
    else subci.next() match {
      case '\n'               => consumeStr(subci, str, lineNum + 1)
      case c if c == str.head => consumeStr(subci, str.tail, lineNum)
      case _                  => None
    }

  def loop(ci :Iterator[Char], line :Int) :List[(Int,String)] =
    if (ci.hasNext) {
      ci.next() match {
        case '\n' => loop(ci, line+1)
        case c if c == target.head =>
          val (oldci,newci) = ci.duplicate
          consumeStr(newci, target.tail, line).fold(loop(oldci, line)){
            (line,target) :: (loop _).tupled(_)
          }
        case _ => loop(ci, line)
      }
    } else Nil

  loop(charItr, 1)
}

有了这个,并使用与以前相同的测试文件,我们可以执行以下操作:

val src1 = io.Source.fromFile("so.txt")  //open twice
val src2 = io.Source.fromFile("so.txt")

"a{2,3}.bb.c[ac]".r                                   //regex pattern
                 .findAllIn(src1.getLines().mkString) //all matches
                 .toSeq.distinct                      //remove duplicates
                 .foldLeft(src2.duplicate -> List.empty[(Int,String)]){
                   case (((srcA,srcB),lst),str) =>
                     (srcA.duplicate, lst ++ findInFile(srcB,str))
                 }._2.sorted
//res0: List[(Int, String)] = List((4,aa.bb.cc), (4,aaa.bb.cc), (8,aaa.bb.ca), (9,aa.bb.cc), (9,aaa.bb.cc))

src1.close()  //close up and go home
src2.close()

这个想法是首先将整个文件作为String 读入内存,不带换行符,然后找到所有正则表达式匹配并将它们转换为所有唯一匹配字符串的列表。然后将每个发送到findInFile()。排序并返回。

效率不高,但可以完成工作。

【讨论】:

  • 我真的很喜欢您为解决问题而采取的方法,但我认为这种逐字符迭代仅适用于我们正在搜索的具体字符串,而不适用于正则表达式。如果正则表达式匹配也是一项强制性要求,您有什么建议?
  • A) 内存效率高(懒惰阅读) B) 目标可以分成多行 C) 报告每个找到的目标开始的行号 D) 使用正则表达式搜索目标。选择 3 的任意组合。
  • 如果必须妥协一件事,我会选择 B、C、D。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-08
  • 2015-03-15
  • 2012-02-09
相关资源
最近更新 更多