Может ли это регулярное выражение быть дополнительно оптимизировано? - PullRequest
4 голосов
/ 19 августа 2011

Я написал это регулярное выражение для разбора записей из файлов SRT.

(?s)^\d++\s{1,2}(.{12}) --> (.{12})\s{1,2}(.+)\r?$

Я не знаю, имеет ли это значение, но это делается с использованием языка программирования Scala (Java Engine, но буквальных строк, чтобы мне не приходилось удваивать обратную косую черту).

s{1,2} используется, потому что некоторые файлы будут иметь только разрывы строк \n, а другие будут иметь разрывы строк и возврат каретки \n\r Первый (?s) включает режим DOTALL, так что третья группа захвата может также соответствовать переносам строк.

Моя программа в основном разбивает файл srt, используя \n\r?\n в качестве разделителя, и использует функцию сопоставления с образцом Scala, чтобы прочитать каждую запись для дальнейшей обработки:

val EntryRegex = """(?s)^\d++\s{1,2}(.{12}) --> (.{12})\s{1,2}(.+)\r?$""".r

def apply(string: String): Entry = string match {
  case EntryRegex(start, end, text) => Entry(0, timeFormat.parse(start),
    timeFormat.parse(end), text);
}

Пример записи:

Одна строка:

1073
01:46:43,024 --> 01:46:45,015
I am your father.

Две строки:

160
00:20:16,400 --> 00:20:19,312
<i>Help me, Obi-Wan Kenobi.
You're my only hope.</i>

Дело в том, что профилировщик показывает мне, что этот метод синтаксического анализа является безусловно самой трудоемкой операцией в моем приложении (которая требует значительных временных затрат и даже может перекодировать файл в несколько раз быстрее, чем требуется для чтения и анализа записей).

Значит, какие-нибудь мастера регулярных выражений могут помочь мне оптимизировать это? Или, может быть, я должен пожертвовать краткостью сопоставления регулярных выражений и шаблонов и попробовать подход старой школы java.util.Scanner?

Приветствия

Ответы [ 4 ]

4 голосов
/ 19 августа 2011
(?s)^\d++\s{1,2}(.{12}) --> (.{12})\s{1,2}(.+)\r?$

В Java $ означает конец ввода или начало переноса строки, непосредственно предшествующего концу ввода.\z означает недвусмысленно конец ввода, поэтому, если это также семантика в Scala, то \r?$ является избыточным и $ будет работать так же хорошо.Если вам действительно нужен только CR в конце, а не CRLF, то \r?\z может быть лучше.

(?s) также должен сделать (.+)\r? избыточным, поскольку + является жадным, .всегда следует расширять, чтобы включить \r.Если вы не хотите, чтобы \r был включен в эту третью группу захвата, тогда сделайте совпадение ленивым: (.+?) вместо (.+).

Возможно

(?s)^\d++\s\s?(.{12}) --> (.{12})\s\s?(.+?)\r?\z

Другой высокий штрафальтернативы регулярным выражениям, которые будут работать внутри JVM & |CLR включает JavaCC и ANTLR .Для решения только для Scala см. http://jim -mcbeath.blogspot.com / 2008/09 / scala-parser-combinators.html

2 голосов
/ 19 августа 2011

Проверьте это:

(?m)^\d++\r?+\n(.{12}) --> (.{12})\r?+\n(.++(?>\r?+\n.++)*+)$

Это регулярное выражение соответствует полной записи файла .srt вместо .Вам не нужно сначала разбивать содержимое на разрывы строк;это огромная трата ресурсов.

Регулярное выражение использует тот факт, что существует ровно один разделитель строк (\n или \r\n), разделяющий строки внутри записи (несколько разделителей строк используются для разделения записейдруг от друга).Использование \r?+\n вместо \s{1,2} означает, что вы никогда не сможете случайно сопоставить два разделителя строк (\n\n), если вы хотите сопоставить только один.

Таким образом, вам также не нужно полагаться на. в режиме (?s).@Jacob был прав насчет этого: это на самом деле не помогает вам, а убивает ваше выступление.Но (?m) mode полезен для правильности и производительности.

Вы упомянули java.util.Scanner;это регулярное выражение будет очень хорошо работать с findWithinHorizon(0).Но я был бы удивлен, если бы Scala не предлагал хороший идиоматичный способ его использования.

2 голосов
/ 19 августа 2011

Я не оптимистичен, но вот две вещи, которые можно попробовать:

  1. , которые вы можете сделать, это переместить (?s) прямо перед тем, как вам это понадобится.* удалите \ r? $ и используйте жадный .++ для текста .+

    ^ \ d ++ \ s {1,2} (. {12}) -> (. {12}) \ s {1,2} (? s) (. ++) $

Чтобы действительно получить хорошую производительность, я бы реорганизовал код и регулярное выражение для использования findAllIn.Текущий код выполняет регулярное выражение для каждого Entry в вашем файле.Я думаю, сингл findAllIn с регулярным выражением будет работать лучше ... Но, возможно, нет ...

1 голос
/ 19 августа 2011

Я бы не использовал java.util.Scanner или даже строки. Все, что вы делаете, будет отлично работать в байтовом потоке, если вы можете принять кодировку UTF-8 ваших файлов (или отсутствие юникода). Вы должны быть в состоянии ускорить процесс как минимум в 5 раз.


Редактировать: это просто многоуровневое переключение байтов и индексов. Вот что-то, основанное на том, что я делал раньше, и кажется примерно в 2–5 раз быстрее, в зависимости от размера файла, кэширования и т. Д. Я здесь не разбираю дату, а просто возвращаю строки и предполагаю файлы достаточно малы, чтобы поместиться в один блок памяти (т. е. <2G). Это довольно педантично осторожно; если, например, вы знаете, что формат строки даты всегда в порядке, анализ может быть еще быстрее (просто посчитайте символы после первой строки цифр). </p>

import java.io._
abstract class Entry {
  def isDefined: Boolean
  def date1: String
  def date2: String
  def text: String
}
case class ValidEntry(date1: String, date2: String, text: String) extends Entry {
  def isDefined = true
}
object NoEntry extends Entry {
  def isDefined = false
  def date1 = ""
  def date2 = ""
  def text = ""
}

final class Seeker(f: File) {
  private val buffer = {
    val buf = new Array[Byte](f.length.toInt)
    val fis = new FileInputStream(f)
    fis.read(buf)
    fis.close()
    buf
  }
  private var i = 0
  private var d1,d2 = 0
  private var txt,n = 0
  def isDig(b: Byte) = ('0':Byte) <= b && ('9':Byte) >= b
  def nextNL() {
    while (i < buffer.length && buffer(i) != '\n') i += 1
    i += 1
    if (i < buffer.length && buffer(i) == '\r') i += 1
  }
  def digits() = {
    val zero = i
    while (i < buffer.length && isDig(buffer(i))) i += 1
    if (i==zero || i >= buffer.length || buffer(i) != '\n') {
      nextNL()
      false
    }
    else {
      nextNL()
      true
    }
  }
  def dates(): Boolean = {
    if (i+30 >= buffer.length) {
      i = buffer.length
      false
    }
    else {
      d1 = i
      while (i < d1+12 && buffer(i) != '\n') i += 1
      if (i < d1+12 || buffer(i)!=' ' || buffer(i+1)!='-' || buffer(i+2)!='-' || buffer(i+3)!='>' || buffer(i+4)!=' ') {
        nextNL()
        false
      }
      else {
        i += 5
        d2 = i
        while (i < d2+12 && buffer(i) != '\n') i += 1
        if (i < d2+12 || buffer(i) != '\n') {
          nextNL()
          false
        }
        else {
          nextNL()
          true
        }
      }
    }
  }
  def gatherText() {
    txt = i
    while (i < buffer.length && buffer(i) != '\n') {
      i += 1
      nextNL()
    }
    n = i-txt
    nextNL()
  }
  def getNext: Entry = {
    while (i < buffer.length) {
      if (digits()) {
        if (dates()) {
          gatherText()
          return ValidEntry(new String(buffer,d1,12), new String(buffer,d2,12), new String(buffer,txt,n))
        }
      }
    }
    return NoEntry
  }
}

Теперь, когда вы это видите, разве вы не рады, что решение для регулярных выражений было настолько быстрым для кодирования?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...