시퀀스의 발생을 변경 가능 또는 불변 상태로 대체하는 효율적인 기술

StackOverflow https://stackoverflow.com/questions/2020123

문제

나는 일련의 순서를 찾는 효율적인 기술을 찾고 있습니다. 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].

기본적으로 연결된 목록 배경에서 나는 약간의 포인터 굽힘을 수행하고 총 4번의 작업 후에 새 객체를 생성하지 않고 대체 작업을 완료했습니다.그래서 지금은 성능에 대해 걱정하고 있습니다.특히 이것은 나에게 성능이 중요한 작업이기 때문에 더욱 그렇습니다.

질문:

어떻게 구현하시겠습니까? replace() Scala 방식의 메서드와 이것이 성능이 중요한 작업이라는 점을 염두에 두고 어떤 종류의 데이터 구조를 사용하시겠습니까?

올바른 방향이나 의사 코드를 알려주는 답변에 만족합니다.전체 교체 방법을 작성할 필요가 없습니다.

감사합니다.

도움이 되었습니까?

해결책

좋습니다. 몇 가지 사항을 고려해야 합니다.먼저, 목록에서 다음을 기억하세요. 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가 있는 경우 이 알고리즘의 결과는 C 대신 ACB가 됩니다.

이 문제를 방지하려면 역추적을 생성해야 합니다.먼저 패턴의 어느 위치에서 교체가 발생할 수 있는지 확인합니다.

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를 모두 일치시키려는 경우 상황은 더욱 악화됩니다.

실제로 전체 타격 문제는 정규식 일치 및 교체와 동일합니다. 여기서 교체는 일치의 함수입니다.물론 정규식 알고리즘을 조사해 볼 수도 있습니다.

편집하다

나는 추론을 완료하는 것을 잊고 있었다.어떤 이유로든 해당 기술이 작동하지 않으면 변경 불가능한 트리 기반 벡터를 사용하는 것이 좋습니다.트리 기반 벡터를 사용하면 적은 양의 복사로 부분 시퀀스를 대체할 수 있습니다.

그래도 문제가 해결되지 않으면 해결책은 이중 연결 목록입니다.그리고 슬라이스 교체 기능이 있는 라이브러리에서 하나를 선택하세요. 그렇지 않으면 알려졌지만 까다로운 알고리즘을 디버깅하는 데 너무 많은 시간을 소비하게 될 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top