Tìm kiếm chuỗi số nguyên


14

Tôi có một vấn đề tìm kiếm khá phức tạp mà tôi đã quản lý để giảm xuống mô tả sau đây. Tôi đã googling nhưng không thể tìm thấy một thuật toán có vẻ phù hợp với vấn đề của tôi một cách sạch sẽ. Đặc biệt cần phải bỏ qua các số nguyên tùy ý. Có lẽ ai đó ở đây có thể chỉ cho tôi một cái gì đó?

Lấy một chuỗi các số nguyên A, ví dụ (1 2 3 4)

Lấy các chuỗi số nguyên khác nhau và kiểm tra xem có bất kỳ số nào khớp với A không.

  1. A chứa tất cả các số nguyên trong chuỗi đã kiểm tra
  2. Thứ tự của các số nguyên trong chuỗi được kiểm tra là giống nhau trong A
  3. Chúng tôi không quan tâm đến bất kỳ số nguyên nào trong A không có trong chuỗi thử nghiệm
  4. Chúng tôi muốn tất cả các chuỗi thử nghiệm phù hợp, không chỉ đầu tiên.

Một ví dụ

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

B phù hợp với A

C khớp với A

D không khớp với A vì thứ tự khác

E không khớp với A vì nó chứa một số nguyên không có trong A

Tôi hy vọng rằng lời giải thích này là đủ rõ ràng. Điều tốt nhất tôi đã làm là tạo ra một cây các chuỗi thử nghiệm và lặp lại A. Nhu cầu có thể bỏ qua các số nguyên dẫn đến rất nhiều đường tìm kiếm không thành công.

Cảm ơn

Đọc một số gợi ý tôi cảm thấy rằng tôi phải làm rõ một vài điểm mà tôi để lại quá mơ hồ.

  1. Các số lặp lại được cho phép, trên thực tế điều này khá quan trọng vì nó cho phép một chuỗi thử nghiệm duy nhất khớp với A là nhiều cách

    A = (1234356), B = (236), các trận đấu có thể là -23 --- 6 hoặc -2--3-6

  2. Tôi hy vọng sẽ có một số lượng rất lớn các chuỗi thử nghiệm, trong ít nhất hàng nghìn và chuỗi A sẽ có xu hướng có độ dài tối đa có thể là 20. Do đó, việc cố gắng khớp từng chuỗi thử nghiệm từng bước một bằng cách lặp đi lặp lại cực kỳ kém hiệu quả.

Xin lỗi nếu điều này không rõ ràng.


4
Bạn có vẻ như chỉ đơn giản là bạn muốn phát hiện các phần tiếp theo ( en.wikipedia.org/wiki/Sub resultence ). Là nó? Sau đó thử tìm kiếm "thuật toán tiếp theo".
Kilian Foth

Thành thật mà nói, một vài nghìn chuỗi có độ dài tối đa <= 20 không phải là một con số lớn đối với tôi. Một cách tiếp cận vũ phu đơn giản nên thực hiện các mẹo. Hay bạn có hàng ngàn chuỗi "A", mỗi chuỗi để kiểm tra hàng ngàn chuỗi có thể có?
Doc Brown

Có một chuỗi liên tục các chuỗi A nhưng chúng hoàn toàn độc lập với nhau. Tuy nhiên, một sự chậm trễ trong việc xử lý một sẽ trực tiếp trì hoãn tất cả những người khác, vì vậy tốc độ là quan trọng.
David Gibson

1
Bảng chữ cái của bạn lớn cỡ nào? Bạn có thực sự có các số nguyên tùy ý, hoặc có một phạm vi hữu hạn của các giá trị để chúng ta có thể thực hiện một số phép tính trước không?
Frank

Phạm vi số nguyên có thể có trong 100.000
David Gibson

Câu trả lời:


18

Hmm, tôi có thể nghĩ về hai thuật toán có thể: Quét tuyến tính thông qua chuỗi A hoặc xây dựng một từ điển với tra cứu liên tục các chỉ số.

Nếu bạn đang thử nghiệm nhiều chuỗi con tiềm năng B dựa trên một chuỗi A lớn hơn , tôi khuyên bạn nên sử dụng biến thể với từ điển.

Quét tuyến tính

Sự miêu tả

Chúng tôi duy trì một con trỏ cho chuỗi Một . Sau đó, chúng ta lặp qua tất cả các mục trong dãy B . Đối với mỗi mục, chúng tôi di chuyển con trỏ về phía trước trong A cho đến khi chúng tôi tìm thấy một mục phù hợp. Nếu không tìm thấy mục phù hợp, thì B không phải là phần tiếp theo.

Điều này luôn chạy trong O (seq.size) .

Mã giả

Phong cách bắt buộc:

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

Phong cách chức năng:

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

Ví dụ thực hiện (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;

Tra cứu từ điển

Sự miêu tả

Chúng tôi ánh xạ các mục của chuỗi A đến các chỉ số của chúng. Sau đó, chúng tôi tìm kiếm các chỉ số phù hợp cho từng mục trong B , bỏ qua các chỉ số nhỏ và chọn chỉ số nhỏ nhất có thể làm giới hạn thấp hơn. Khi không tìm thấy chỉ số nào, thì B không phải là một chuỗi con.

Chạy trong một cái gì đó như O (subseq.size · k) , trong đó k mô tả có bao nhiêu số trùng lặp seq. Cộng với chi phí O (seq.size)

Ưu điểm của giải pháp này là một quyết định tiêu cực có thể đạt được nhanh hơn nhiều (xuống đến thời gian không đổi), một khi bạn đã trả chi phí xây dựng bảng tra cứu.

Mã giả:

Phong cách bắt buộc:

# 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

Phong cách chức năng:

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

Ví dụ thực hiện (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;

Biến thể tra cứu từ điển: Mã hóa dưới dạng máy trạng thái hữu hạn

Sự miêu tả

Chúng ta có thể giảm độ phức tạp thuật toán xuống còn O (subseq.size) nếu chúng ta giao dịch trong nhiều bộ nhớ hơn. Thay vì ánh xạ các phần tử vào các chỉ mục của chúng, chúng ta tạo một biểu đồ trong đó mỗi nút đại diện cho một phần tử tại chỉ mục của nó. Các cạnh hiển thị các chuyển tiếp có thể, ví dụ như chuỗi a, b, asẽ có các cạnh a@1 → b@2, a@1 → a@3, b@2 → a@3. Biểu đồ này tương đương với một máy trạng thái hữu hạn.

Trong quá trình tra cứu, chúng tôi duy trì một con trỏ ban đầu là nút đầu tiên của cây. Sau đó chúng tôi đi cạnh cho mỗi phần tử trong sublist B . Nếu không có cạnh như vậy tồn tại, thì B không có danh sách con. Nếu sau tất cả các phần tử con trỏ chứa một nút hợp lệ, thì B là một danh sách con.

Mã giả

Phong cách bắt buộc:

# 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

Phong cách chức năng:

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

Ví dụ thực hiện (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;

Bên cạnh đó, bạn đã chọc vào cách thức studyhoạt động và nếu các thuật toán áp dụng có thể có một số ứng dụng thực tế ở đây?

1
@MichaelT Tôi không chắc là tôi hiểu được Tôi là một sinh viên chưa tốt nghiệp, nhưng tôi chưa khám phá ra cách học </ đùa>. Nếu bạn đang nói về chức năng dựng sẵn Perl: Ngày nay nó không còn hoạt động nữa. Việc thực hiện hiện tại chỉ là một chục dòng tương thích ngược. Công cụ regex sử dụng các heuristic như vậy trực tiếp, chẳng hạn như tìm kiếm các chuỗi không đổi trước khi khớp các mẫu có kích thước thay đổi. studytrước đây đã xây dựng các bảng tra cứu nhân vật đến vị trí, không giống như giải pháp thứ hai của tôi.
amon

cập nhật với thuật toán tốt hơn
amon

Xây dựng thêm về FSM đó, bạn có thể 'biên dịch' tất cả các chuỗi thử nghiệm thành một FSM và sau đó chạy qua toàn bộ chuỗi. Tùy thuộc vào trạng thái mà bạn đã ở cuối xác định những phần sau được khớp. Đó chắc chắn là điều mà người ta muốn máy tính làm hơn là làm bằng tay cho bất kỳ thứ gì không tầm thường.

@MichaelT Bạn đúng là chúng tôi có thể xây dựng một bộ nhận dạng theo cách này. Tuy nhiên, chúng tôi đã giảm xuống n · O (B) + chi phí khởi tạo trong O (f (A)) . Xây dựng cấu trúc giống như bộ ba của tất cả các Bs sẽ lấy một cái gì đó giống như O (n · B) , với kết quả khớp là O (A) . Điều này có cơ hội thực sự rẻ hơn về mặt lý thuyết (xây dựng biểu đồ trong giải pháp thứ 3 có thể tốn kém, nhưng đó chỉ là chi phí một lần). Tôi nghĩ rằng một trie phù hợp hơn với A · n · B , và có nhược điểm là nó không thể xử lý đầu vào phát trực tuyến - tất cả các Bs phải được tải trước khi khớp. Có lẽ tôi sẽ cập nhật câu trả lời trong 6h.
amon

6

Đây là một cách tiếp cận thực tế để tránh "công việc khó khăn" khi thực hiện thuật toán của riêng bạn và cũng tránh "phát minh lại bánh xe": sử dụng một công cụ biểu thức chính quy cho vấn đề.

Chỉ cần đặt tất cả các số của A vào một chuỗi và tất cả các số của B thành một chuỗi được phân tách bằng biểu thức chính quy (.*). Thêm một ^nhân vật ở đầu và $cuối. Sau đó, hãy để công cụ biểu thức chính quy yêu thích của bạn tìm kiếm cho tất cả các trận đấu. Ví dụ khi

A = (1234356), B = (236)

tạo một reg exp cho B like ^(.*)2(.*)3(.*)6(.*)$. Bây giờ chạy một tìm kiếm regex toàn cầu. Để tìm ra vị trí nào trong A khớp tiếp theo của bạn, chỉ cần kiểm tra độ dài của 3 lần gửi đầu tiên.

Nếu phạm vi số nguyên của bạn từ 0 đến 9, trước tiên bạn có thể xem xét mã hóa chúng bằng các chữ cái duy nhất để thực hiện công việc này hoặc bạn phải điều chỉnh ý tưởng bằng cách sử dụng ký tự phân tách.

Tất nhiên, tốc độ của cách tiếp cận đó sẽ phụ thuộc rất nhiều vào tốc độ của công cụ reg exp mà bạn đang sử dụng, nhưng có những công cụ được tối ưu hóa cao và tôi đoán rằng sẽ khó thực hiện thuật toán nhanh hơn "ngoài luồng" .


Người ta không cần phải tìm mọi cách để gọi regex và engine của nó. Có thể sử dụng một automata hữu hạn xác định đơn giản để chạy nó. Đó là một tuyến 'đường thẳng' đi qua.

@MichaelT: tốt, tôi không có thư viện "automata hữu hạn chung" trong tay và OP không cho chúng tôi biết về ngôn ngữ lập trình mà anh ấy sử dụng, nhưng các biểu thức thông thường hiện có cho hầu hết mọi ngôn ngữ lập trình nghiêm túc " ". Điều đó sẽ làm cho đề xuất của tôi rất dễ thực hiện, với mã ít hơn nhiều so với, ví dụ, giải pháp của amon. IMHO OP nên thử, nếu nó quá chậm đối với anh ta, anh ta vẫn có thể thử nếu một giải pháp phức tạp hơn sẽ phục vụ anh ta tốt hơn.
Doc Brown

Bạn không cần một thư viện chung chung. Tất cả những gì bạn cần là mảng 'mẫu' và một con trỏ tới chỉ mục trong mảng. Chỉ mục trỏ đến giá trị "tìm kiếm" tiếp theo và khi bạn đọc nó từ nguồn, hãy tăng chỉ số. Khi bạn nhấn vào cuối mảng, bạn đã khớp nó. Nếu bạn đọc đến cuối nguồn mà không đi đến cuối, bạn đã không khớp với nguồn đó.

@MichaelT: vậy tại sao bạn không đăng một bản phác thảo thuật toán đó như một câu trả lời?
Doc Brown

Chủ yếu là vì nó đã được trả lời tốt hơn rồi - "Chúng tôi duy trì một con trỏ cho chuỗi A. Sau đó, chúng tôi lặp lại tất cả các mục trong phần tiếp theo B. Đối với mỗi mục, chúng tôi di chuyển con trỏ về phía trước cho đến khi chúng tôi tìm thấy một mục phù hợp. Nếu không mục phù hợp đã được tìm thấy, sau đó B không phải là một phần tiếp theo. "

0

Thuật toán này sẽ khá hiệu quả nếu nhận được độ dài và lặp lại chuỗi là hiệu quả.

  1. So sánh độ dài của cả hai chuỗi. Lưu trữ dài hơn sequencevà ngắn hơn trongsubsequence
  2. Bắt đầu ở đầu của cả hai chuỗi và vòng lặp cho đến khi kết thúc sequence.
    1. Là số ở vị trí hiện tại sequencebằng với số tại vị trí hiện tại củasubsequence
    2. Nếu có, di chuyển cả hai vị trí thêm một
    3. Nếu không, chỉ di chuyển vị trí của sequencemột hơn nữa
  3. Là vị trí của subsequence cuốisequence
  4. Nếu có, hai chuỗi khớp nhau
  5. Nếu không, hai chuỗi không khớp
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.