Эффективная техника для замены вхождения в последовательности с изменяемым или неизменным состоянием - PullRequest
4 голосов
/ 07 января 2010

Я ищу эффективную технику, чтобы найти последовательность Op вхождений в Seq[Op]. Когда найден случай, я хочу заменить этот случай определенной заменой и снова выполнить тот же поиск, пока список не перестанет меняться.

Сценарий:

У меня есть три типа Op тематических классов. Pop() расширяет Op, Push() расширяет Op и Nop() расширяет Op. Я хочу заменить вхождение Push(), Pop() на Nop(). В основном код может выглядеть как seq.replace(Push() ~ Pop() ~> Nop()).

Проблема:

Теперь, когда я позвоню seq.replace(...), мне придется искать в последовательности вхождение Push(), Pop(). Все идет нормально. Я нахожу событие. Но теперь мне придется соединить вхождение в списке и вставить замену.

Теперь есть два варианта. Мой список может быть изменяемым или неизменным. Если я использую неизменный список, я боюсь производительности, потому что эти последовательности обычно имеют размер более 500 элементов. Если я заменю много вхождений типа A ~ B ~ C ~> D ~ E, я создам много новых объектов, если я не ошибаюсь. Однако я мог бы также использовать изменяемую последовательность, такую ​​как ListBuffer[Op].

В основном, из фона связанного списка, я бы просто немного согнул указатель, и после четырех операций я сделал замену без создания новых объектов. Вот почему я сейчас беспокоюсь о производительности. Тем более что это критичная для меня операция.

Вопрос:

Как бы вы реализовали метод replace() в стиле Scala и какую структуру данных вы бы использовали, имея в виду, что это критичная для производительности операция?

Я доволен ответами, которые указывают мне правильное направление или псевдокод. Нет необходимости писать метод полной замены.

Спасибо.

1 Ответ

4 голосов
/ 07 января 2010

Хорошо, некоторые соображения должны быть сделаны. Во-первых, напомним, что в списках tail не создает объекты, а предоплата (::) создает только один объект для каждого предварительно добавленного элемента. Вообще говоря, это почти так же хорошо, как вы можете получить.

Один из способов сделать это будет следующим:

def myReplace(input: List[Op], pattern: List[Op], replacement: List[Op]) = {
  // This function should be part of an KMP algorithm instead, for performance
  def compare(pattern: List[Op], list: List[Op]): Boolean = (pattern, list) match {
    case (x :: xs, y :: ys) if x == y => compare(xs, ys)
    case (Nil, Nil)                   => true
    case _                            => false
  }

  var processed: List[Op] = Nil
  var unprocessed: List[Op] = input
  val patternLength = pattern.length
  val reversedReplacement = replacement.reverse

  // Do this until we finish processing the whole sequence
  while (unprocessed.nonEmpty) {

    // This inside algorithm would be better if replaced by KMP

    // Quickly process non-matching sequences
    while (unprocessed.nonEmpty && unprocessed.head != pattern.head) {
      processed ::= unprocessed.head
      unprocessed = unprocessed.tail
    }

    if (unprocessed.nonEmpty) {
      if (compare(pattern, unprocessed)) {
        processed :::= reversedReplacement
        unprocessed = unprocessed drop patternLength
      } else {
      processed ::= unprocessed.head
      unprocessed = unprocessed.tail
      }          
    }
  }

  processed.reverse
}

Вы можете увеличить скорость, используя KMP, особенно если искомый шаблон длинный.

Теперь, в чем проблема с этим алгоритмом? Проблема в том, что он не будет проверять, вызывает ли замененный шаблон совпадение перед этой позицией. Например, если я заменю ACB на C, и у меня будет входной AACBB, то результатом этого алгоритма будет ACB вместо C.

Чтобы избежать этой проблемы, вы должны создать возврат. Сначала вы проверяете, в какой позиции в вашем шаблоне может произойти замена:

val positionOfReplacement = pattern.indexOfSlice(replacement)

Затем вы модифицируете заменяющую часть алгоритма следующим образом:

      if (compare(pattern, unprocessed)) {
        if (positionOfReplacement > 0) {
          unprocessed :::= replacement
          unprocessed :::= processed take positionOfReplacement
          processed = processed drop positionOfReplacement 
        } else {
          processed :::= reversedReplacement
          unprocessed = unprocessed drop patternLength
        }
      } else {

Этого достаточно, чтобы решить проблему.

Однако этот алгоритм не будет эффективно работать с шаблонами умножения в одно и то же время, что, я полагаю, именно туда, куда вы идете. Для этого вам, вероятно, потребуется некоторая адаптация KMP, чтобы сделать это эффективно, или, в противном случае, использовать DFA для контроля возможных совпадений. Становится еще хуже, если вы хотите сопоставить AB и ABC.

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

EDIT

Я забыл закончить свои рассуждения. Если по какой-то причине этот метод не работает, то мой совет - использовать неизменный древовидный вектор. Древовидные векторы позволяют заменять частичные последовательности небольшим количеством копий.

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

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