Если бы я пытался это сделать, я бы:
- отказался бы от использования
getLines()
.Это затрудняет поиск цели по нескольким строкам текста. - Откажитесь от использования шаблона регулярного выражения в качестве целевой строки.Найти совпадение, которое может иметь или не иметь несколько
\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()
.Сортировка и возврат.
Не очень эффективно, но он выполняет свою работу.