정수 시퀀스 검색


14

다음 설명으로 줄인 상당히 복잡한 검색 문제가 있습니다. 인터넷 검색을 해왔지만 내 문제에 잘 맞는 알고리즘을 찾지 못했습니다. 특히 임의의 정수를 건너 뛸 필요가 있습니다. 어쩌면 여기 누군가가 나를 가리킬 수 있습니까?

예를 들어 (1 2 3 4)의 정수 A를 취하십시오.

다양한 정수 시퀀스를 취하여 A와 일치하는 것이 있는지 테스트하십시오.

  1. A는 테스트 된 순서의 모든 정수를 포함합니다
  2. 테스트 된 순서에서 정수의 순서는 A에서 동일합니다.
  3. 테스트 시퀀스에없는 A의 정수는 신경 쓰지 않습니다.
  4. 우리는 첫 번째 테스트뿐만 아니라 모든 일치하는 테스트 시퀀스를 원합니다.

A = (1 2 3 4)
B = (1 3)
C = (1 3 4)
D = (3 1)
E = (1 2 5)

B는 A와 일치

C는 A와 일치

순서가 다르므로 D가 A와 일치하지 않습니다.

A가 아닌 정수를 포함하므로 E는 A와 일치하지 않습니다.

이 설명이 충분히 명확하기를 바랍니다. 내가 관리 한 최선의 방법은 테스트 시퀀스의 트리를 구성하고 A를 반복하는 것입니다. 정수를 건너 뛸 수 있어야하기 때문에 검색 경로가 많이 실패하게됩니다.

감사

몇 가지 제안을 읽고 너무 모호하게 남겨둔 몇 가지 요점을 명확히해야한다고 생각합니다.

  1. 반복되는 숫자가 허용됩니다. 사실 이것은 단일 테스트 시퀀스가 ​​A와 여러 가지 방법을 일치시킬 수 있기 때문에 매우 중요합니다.

    A = (1234356), B = (236), 일치는 -23 --- 6 또는 -2--3-6 일 수 있습니다.

  2. 최소한 수천 개에 이르는 매우 많은 수의 테스트 시퀀스가있을 것으로 예상하고 시퀀스 A는 최대 길이가 20 일 가능성이 높습니다. 따라서 반복하여 각 테스트 시퀀스를 하나씩 하나씩 일치시키는 것은 매우 비효율적입니다.

이것이 명확하지 않으면 죄송합니다.


4
하위 시퀀스 ( en.wikipedia.org/wiki/Subsequence ) 를 단순히 감지하려는 것처럼 들립니다 . 그게 다야? 그런 다음 "subsequence algorithm"을 검색하십시오.
Kilian Foth

솔직히, 최대 길이가 20보다 작은 수천 개의 시퀀스는 나에게 큰 소리를 내지 않습니다. 간단한 무차별 대입 방식으로 트릭을 수행해야합니다. 또는 수천 개의 가능한 서브 시퀀스에 대해 각각 테스트 할 수천 개의 시퀀스 "A"가 있습니까?
Doc Brown

시퀀스 A의 연속 스트림이 있지만 서로 완전히 독립적입니다. 그러나 처리 지연이 다른 모든 처리를 직접 지연 시키므로 속도가 중요합니다.
David Gibson

1
알파벳이 얼마나 큽니까? 실제로 임의의 정수가 있습니까, 아니면 사전 계산을 수행 할 수있는 유한 한 값 범위가 있습니까?
Frank

정수의 가능한 범위는 10 만여입니다
데이비드 깁슨

답변:


18

흠, 나는 두 가지 가능한 알고리즘을 생각할 수 있습니다 : A 시퀀스를 통한 선형 스캔 또는 인덱스의 일정한 시간 조회로 사전을 작성합니다.

하나의 큰 시퀀스 A 에 대해 많은 잠재적 하위 시퀀스 B를 테스트 하는 경우 사전에 변형을 사용하는 것이 좋습니다.

선형 스캔

기술

시퀀스 A에 대한 커서를 유지합니다 . 그런 다음 하위 시퀀스 B의 모든 항목을 반복합니다 . 각 항목에 대해 일치하는 항목을 찾을 때까지 커서를 A 에서 앞으로 이동합니다 . 일치하는 항목이 없으면 B 는 하위 시퀀스가 ​​아닙니다.

이것은 항상 O (seq.size) 에서 실행됩니다 .

의사 코드

명령 스타일 :

def subsequence? seq, subseq:
  i = 0
  for item in subseq:
    i++ while i < seq.size and item != seq[i]
    return false if i == seq.size
  return true

기능적 스타일 :

let rec subsequence? = function
| _ [] -> true
| [] _ -> false
| cursor::seq item::subseq ->
  if   cursor = item
  then subsequence? seq subseq
  else subsequence? seq item::subseq

구현 예 (Perl) :

use strict; use warnings; use signatures; use Test::More;

sub is_subsequence_i ($seq, $subseq) {
  my $i = 0;
  for my $item (@$subseq) {
    $i++ while $i < @$seq and $item != $seq->[$i];
    return 0 if $i == @$seq;
  }
  return 1;
}

sub is_subsequence_f ($seq, $subseq) {
  return 1 if @$subseq == 0;
  return 0 if @$seq == 0;
  my ($cursor, @seq) = @$seq;
  my ($item, @subseq) = @$subseq;
  return is_subsequence_f(\@seq, $cursor == $item ? \@subseq : $subseq);
}

my $A = [1, 2, 3, 4];
my $B = [1, 3];
my $C = [1, 3, 4];
my $D = [3, 1];
my $E = [1, 2, 5];

for my $is_subsequence (\&is_subsequence_i, \&is_subsequence_f) {
  ok $is_subsequence->($A, $B), 'B in A';
  ok $is_subsequence->($A, $C), 'C in A';
  ok ! $is_subsequence->($A, $D), 'D not in A';
  ok ! $is_subsequence->($A, $E), 'E not in A';
  ok $is_subsequence->([1, 2, 3, 4, 3, 5, 6], [2, 3, 6]), 'multiple nums';
}

done_testing;

사전 조회

기술

시퀀스 A 의 항목 을 해당 인덱스에 매핑합니다 . 그런 다음 B의 각 항목에 적합한 인덱스를 찾고 작은 인덱스를 건너 뛰고 가능한 가장 작은 인덱스를 하한으로 선택합니다. 인덱스가 없으면 B 는 하위 시퀀스가 ​​아닙니다.

O (subseq.size · k) 와 같이 실행됩니다 . 여기서 k 는 중복 된 수를 나타 seq냅니다. 또한 O (seq.size) 오버 헤드

이 솔루션의 장점은 룩업 테이블을 구축하는 오버 헤드를 지불하면 부정적인 결정에 훨씬 더 빠르게 도달 할 수 있다는 것입니다 (일정 시간까지).

의사 코드 :

명령 스타일 :

# preparing the lookup table
dict = {}
for i, x in seq:
  if exists dict[x]:
    dict[x].append(i)
  else:
    dict[x] = [i]

def subsequence? subseq:
  min_index = -1
  for x in subseq:
    if indices = dict[x]:
      suitable_indices = indices.filter(_ > min_index)
      return false if suitable_indices.empty?
      min_index = suitable_indices[0]
    else:
      return false
  return true

기능적 스타일 :

let subsequence? subseq =
  let rec subseq-loop = function
  | [] _ -> true
  | x::subseq min-index ->
    match (map (filter (_ > min-index)) data[x])
    | None -> false
    | Some([]) -> false
    | Some(new-min::_) -> subseq-loop subseq new-min
  in
    subseq-loop subseq -1

구현 예 (Perl) :

use strict; use warnings; use signatures; use Test::More;

sub build_dict ($seq) {
  my %dict;
  while (my ($i, $x) = each @$seq) {
    push @{ $dict{$x} }, $i;
  }
  return \%dict;
}

sub is_subsequence_i ($seq, $subseq) {
  my $min_index = -1;
  my $dict = build_dict($seq);
  for my $x (@$subseq) {
    my $indices = $dict->{$x} or return 0;
    ($min_index) = grep { $_ > $min_index } @$indices or return 0;
  }
  return 1;
}

sub is_subsequence_f ($seq, $subseq) {
  my $dict = build_dict($seq);
  use feature 'current_sub';
  return sub ($subseq, $min_index) {
    return 1 if @$subseq == 0;
    my ($x, @subseq) = @$subseq;
    my ($new_min) = grep { $_ > $min_index } @{ $dict->{$x} // [] } or return 0;
    __SUB__->(\@subseq, $new_min);
  }->($subseq, -1);
}

my $A = [1, 2, 3, 4];
my $B = [1, 3];
my $C = [1, 3, 4];
my $D = [3, 1];
my $E = [1, 2, 5];

for my $is_subsequence (\&is_subsequence_i, \&is_subsequence_f) {
  ok $is_subsequence->($A, $B), 'B in A';
  ok $is_subsequence->($A, $C), 'C in A';
  ok ! $is_subsequence->($A, $D), 'D not in A';
  ok ! $is_subsequence->($A, $E), 'E not in A';
  ok $is_subsequence->([1, 2, 3, 4, 3, 5, 6], [2, 3, 6]), 'multiple nums';
}

done_testing;

사전 검색 변형 : 유한 상태 머신으로 인코딩

기술

더 많은 메모리에서 거래 하면 알고리즘 복잡도를 O (subseq.size) 로 줄일 수 있습니다 . 요소를 인덱스에 매핑하는 대신 각 노드가 인덱스의 요소를 나타내는 그래프를 만듭니다. 가장자리는 가능한 전이를 보여줍니다. 예를 들어 시퀀스 a, b, a에는 가장자리가 a@1 → b@2, a@1 → a@3, b@2 → a@3있습니다. 이 그래프는 유한 상태 기계와 같습니다.

조회하는 동안 처음에는 트리의 첫 번째 노드 인 커서를 유지합니다. 그런 다음 하위 목록 B의 각 요소에 대해 가장자리를 걷습니다 . 그러한 엣지가 존재하지 않으면 B 는 서브리스트가 아닙니다. 모든 요소 다음에 커서에 유효한 노드 가 있으면 B 는 서브리스트입니다.

의사 코드

명령 스타일 :

# preparing the graph
graph = {}
for x in seq.reverse:
  next_graph = graph.clone
  next_graph[x] = graph
  graph = next_graph

def subseq? subseq:
  cursor = graph
  for x in subseq:
    cursor = graph[x]
    return false if graph == null
  return true

기능적 스타일 :

let subseq? subseq =
  let rec subseq-loop = function
  | [] _ -> true
  | x::subseq graph -> match (graph[x])
    | None -> false
    | Some(next-graph) -> subseq-loop subseq next-graph
  in
    subseq-loop subseq graph

구현 예 (Perl) :

use strict; use warnings; use signatures; use Test::More;

sub build_graph ($seq) {
  my $graph = {};
  for (reverse @$seq) {
    $graph = { %$graph, $_ => $graph };
  }
  return $graph;
}

sub is_subsequence_i ($seq, $subseq) {
  my $cursor = build_graph($seq);
  for my $x (@$subseq) {
    $cursor = $cursor->{$x} or return 0;
  }
  return 1;
}

sub is_subsequence_f ($seq, $subseq) {
  my $graph = build_graph($seq);
  use feature 'current_sub';
  return sub ($subseq, $graph) {
    return 1 if @$subseq == 0;
    my ($x, @subseq) = @$subseq;
    my $next_graph = $graph->{$x} or return 0;
    __SUB__->(\@subseq, $next_graph);
  }->($subseq, $graph);
}

my $A = [1, 2, 3, 4];
my $B = [1, 3];
my $C = [1, 3, 4];
my $D = [3, 1];
my $E = [1, 2, 5];

for my $is_subsequence (\&is_subsequence_i, \&is_subsequence_f) {
  ok $is_subsequence->($A, $B), 'B in A';
  ok $is_subsequence->($A, $C), 'C in A';
  ok ! $is_subsequence->($A, $D), 'D not in A';
  ok ! $is_subsequence->($A, $E), 'E not in A';
  ok $is_subsequence->([1, 2, 3, 4, 3, 5, 6], [2, 3, 6]), 'multiple nums';
}

done_testing;

제쳐두고, 당신은 어떻게 study작동 하는지 찔렀 습니까? 그리고 적용되는 알고리즘이 여기에 실용적인 적용을 가질 수 있습니까?

1
@MichaelT 잘 모르겠습니다… 저는 저학년이지만 실제로 공부하는 방법을 아직 찾지 못했습니다 </ joke>. Perl 내장 기능에 대해 이야기하고 있다면 요즘에는 작동하지 않습니다. 현재 구현은 수십 줄의 이전 버전과의 호환성입니다. 정규식 엔진은 가변 크기 패턴을 일치시키기 전에 상수 문자열을 검색하는 것과 같은 휴리스틱을 직접 사용합니다. study이전의 두 번째 솔루션과 달리 문자 대 위치 조회 테이블을 작성했습니다.
amon

더 나은 알고리즘으로 업데이트
amon

해당 FSM에 대해 자세히 설명하면 모든 테스트 시퀀스를 하나의 FSM으로 '컴파일'한 다음 전체 시퀀스를 실행할 수 있습니다. 마지막에 있었던 상태에 따라 일치하는 서브 시퀀스가 ​​결정됩니다. 확실히 사소하지 않은 컴퓨터를 위해 컴퓨터를 직접 사용하는 것보다는 컴퓨터를 사용하는 것입니다.

@MichaelT 당신은 우리 이런 식으로 인식기를 구축 할 있다는 것이 맞습니다 . 그러나 우리는 이미 O (f (A))의 n · O (B) + 초기화 비용으로 하향 조정되었습니다 . 모든 B의 trie-like 구조를 구축하는 것은 O (n · B) 와 같 으며, 일치하는 것은 O (A) 입니다. 이론적으로는 저렴할 수 있습니다 (제 3 솔루션에서 그래프를 작성하는 것은 비용이 많이 들지만 일회성 비용입니다). 트라이는 A ≫ n · B에 더 적합하며 스트리밍 입력을 처리 할 수 ​​없다는 단점이 있습니다. 일치하기 전에 모든 B를로드해야합니다. 아마 6 시간 안에 답변을 업데이트 할 것입니다.
amon

6

다음은 자신 만의 알고리즘을 구현하는 "고된 작업"을 피하고 "바퀴를 재창조"하는 것을 피하는 실용적인 접근법입니다 . 문제에 정규식 엔진을 사용하십시오.

A의 모든 숫자를 문자열에 넣고 B의 모든 숫자를 정규식으로 구분 된 문자열에 넣으십시오 (.*). ^시작과 $끝에 문자를 추가하십시오 . 그런 다음 좋아하는 정규식 엔진이 모든 일치 항목을 검색하도록하십시오. 예를 들어

A = (1234356), B = (236)

B와 같은 정규 표현식을 작성하십시오 ^(.*)2(.*)3(.*)6(.*)$. 이제 전역 정규식 검색을 실행하십시오. 하위 시퀀스와 일치하는 A의 위치를 ​​찾으려면 처음 세 하위 일치의 길이를 확인하십시오.

정수 범위가 0에서 9 사이 인 경우 먼저이 문자를 사용하여이 문자를 인코딩하거나 구분 문자를 사용하여 아이디어를 수정해야합니다.

물론,이 방법의 속도는 사용중인 정규식 엔진의 속도에 크게 좌우되지만 사용 가능한 최적화 된 엔진이 있으며 더 빠른 알고리즘을 "바로"구현하기가 어렵다고 생각합니다. .


정규식과 엔진을 호출하기 위해 항상 갈 필요는 없습니다. 간단한 결정적 유한 오토마타를 사용하여 실행할 수 있습니다. '직선'경로입니다.

@MichaelT : 글쎄, 나는 "제네릭 유한 오토마타"라이브러리를 가지고 있지 않으며, OP는 그가 사용하는 프로그래밍 언어에 대해 알려주지 않았지만 오늘날 거의 모든 진지한 프로그래밍 언어에 대해 정규 표현식을 사용할 수있다. ". 그것은 예를 들어 amon의 솔루션보다 훨씬 적은 코드로 내 제안을 구현하기 매우 쉬워야합니다. IMHO OP는 시도를 해봐야합니다. 너무 느리면 더 복잡한 솔루션이 더 나은 서비스를 제공 할 수 있는지 시도해 볼 수 있습니다.
Doc Brown

일반 라이브러리가 필요하지 않습니다. 필요한 것은 '패턴'의 배열과 배열의 인덱스에 대한 포인터입니다. 색인은 다음 "찾고있는"값을 가리키고 소스에서 읽을 때 색인을 증가시킵니다. 배열의 끝에 도달하면 일치합니다. 끝까지 도달하지 않고 소스의 끝을 읽으면 일치하지 않은 것입니다.

@MichaelT : 그렇다면 왜 그 알고리즘의 스케치를 답으로 게시하지 않습니까?
Doc Brown

대부분은 이미 이미 잘 대답했기 때문에- "시퀀스 A에 대한 커서를 유지합니다. 그런 다음 하위 시퀀스 B의 모든 항목을 반복합니다. 각 항목에 대해 일치하는 항목을 찾을 때까지 커서를 A에서 앞으로 이동합니다. 일치하는 항목을 찾았 으면 B는 하위 시퀀스가 ​​아닙니다. "

0

이 알고리즘은 길이를 가져오고 시퀀스를 반복하는 것이 효율적인 경우 매우 효율적이어야합니다.

  1. 두 시퀀스의 길이를 비교하십시오. 더 길고 sequence짧게 보관하십시오subsequence
  2. 두 시퀀스의 시작 부분에서 시작하여 끝까지 반복합니다 sequence.
    1. 현재 위치의 sequence숫자가 현재 위치의 숫자 와 같 습니까?subsequence
    2. 그렇다면 두 위치를 한 단계 더 이동하십시오
    3. 그렇지 않은 경우 한 위치 만 sequence더 이동
  3. subsequence의 끝에 위치입니다sequence
  4. 그렇다면 두 시퀀스가 ​​일치합니다.
  5. 그렇지 않은 경우 두 시퀀스가 ​​일치하지 않습니다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.