Tamsayı dizilerini arama


14

Aşağıdaki açıklamaya azaltmayı başardığım oldukça karmaşık bir arama sorunum var. Google'da çalışıyorum, ancak sorunuma temiz bir şekilde uyan bir algoritma bulamadım. Özellikle keyfi tamsayıları atlamak gerekir. Belki burada biri beni bir şeye yönlendirebilir?

Örneğin bir tamsayı A dizisi alın (1 2 3 4)

Çeşitli tamsayı dizileri alın ve bunlardan herhangi birinin A ile eşleşip eşleşmediğini test edin.

  1. A test edilen sıradaki tüm tam sayıları içerir
  2. Test edilen sıradaki tamsayıların sıralaması A'da aynıdır
  3. A'da test sırasında olmayan hiçbir tamsayıyı umursamıyoruz
  4. Sadece birincisi değil, eşleşen tüm test dizilerini istiyoruz.

Bir örnek

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

B, A ile eşleşir

C, A ile eşleşir

D, sipariş farklı olduğundan A ile eşleşmiyor

E, A'da olmayan bir tamsayı içerdiğinden A ile eşleşmiyor

Umarım bu açıklama yeterince açıktır. Yapabildiğim en iyi şey, test dizilerinin bir ağacını oluşturmak ve A üzerinde yineleme yapmaktır.

Teşekkürler

Bazı önerileri okurken çok belirsiz bıraktığım birkaç noktayı açıklığa kavuşturmak zorunda olduğumu hissediyorum.

  1. Tekrarlanan sayılara izin verilir, aslında bu tek bir test dizisinin A ile eşleşmesine izin verdiği için çok önemlidir.

    A = (1234356), B = (236), eşleşmeler -23 --- 6 veya -2--3-6 olabilir

  2. En azından binlercede çok sayıda test dizisinin olmasını bekliyorum ve A dizisi maksimum 20 uzunluğa sahip olma eğiliminde olacaktır. Bu nedenle, her test dizisini tek tek yinelemeyle eşleştirmeye çalışmak son derece verimsiz hale gelir.

Bu net değilse özür dilerim.


4
Sadece alt dizileri tespit etmek istiyormuş gibi ses çıkarıyorsunuz ( en.wikipedia.org/wiki/Subsequence ). Öyle mi? Ardından "alt algoritma" araması yapmayı deneyin.
Kilian Foth

Dürüst olmak gerekirse, maksimum uzunluğu <= 20 olan birkaç bin dizi bana çok fazla gelmiyor. Basit bir kaba kuvvet yaklaşımı hile yapmalıdır. Ya da her biri binlerce olası alt diziyi test etmek için binlerce "A" dizisi var mı?
Doc Brown

A sekanslarının sürekli bir akışı vardır, ancak birbirlerinden tamamen bağımsızdırlar. Bununla birlikte, bir işlemede gecikme, diğerlerini doğrudan geciktirir, bu nedenle hız önemlidir.
David Gibson

1
Alfabeniz ne kadar büyük? Gerçekten rasgele tamsayılarınız var mı veya bazı önceden hesaplamalar yapabilmemiz için sınırlı bir değer aralığı var mı?
Frank

Olası tamsayı aralığı 100.000'lerde
David Gibson

Yanıtlar:


18

Hmm, iki olası algoritmayı düşünebilirim: A sekansı boyunca doğrusal bir tarama veya indekslerin sürekli zaman aramasıyla bir sözlük oluşturma.

Birçok potansiyel alt B'yi tek bir büyük A dizisine karşı test ediyorsanız , varyantı sözlükle kullanmanızı öneririm.

Doğrusal Tarama

Açıklama

A dizisi için bir imleç tutuyoruz . Daha sonra B alt sekansındaki tüm maddeleri tekrarlıyoruz . Her bir öğe için, eşleşen bir öğe bulana kadar imleci A'da ilerletiriz . Eşleşen bir öğe bulunamazsa, B bir alt sıra değildir.

Bu daima O (seq.size) biçiminde çalışır .

pseudocode

Emir Kipi:

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

Fonksiyonel tarzı:

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

Örnek uygulama (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;

Sözlük araması

Açıklama

A dizisindeki öğeleri endeksleriyle eşleştiriyoruz . Ardından, B'deki her bir öğe için uygun endeksleri ararız, küçük olan indeksleri atlarız ve mümkün olan en küçük endeksi alt limit olarak seçeriz. Endeks bulunmadığında, B bir alt dizidir.

O , k ( kaç tane sayı) olduğunu açıklayan O (alt boyut) · k) gibi bir şeyde çalışır seq. Artı bir O (sıralı boyut) ek yükü

Bu çözümün avantajı, arama tablosunu oluşturmanın yükünü ödediğinizde negatif bir karara çok daha hızlı (sabit zamana kadar) ulaşılabilmesidir.

pseudocode:

Emir Kipi:

# 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

Fonksiyonel tarzı:

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

Örnek uygulama (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;

Sözlük Arama Değişkeni: Sonlu Durum Makinesi Olarak Kodlama

Açıklama

Daha fazla bellekte işlem yaparsak algoritmik karmaşıklığı O (alt boyut) değerine kadar azaltabiliriz . Öğeleri dizinleriyle eşlemek yerine, her düğümün dizinindeki bir öğeyi temsil ettiği bir grafik oluştururuz. Kenarlar olası geçişleri gösterir, örneğin dizinin a, b, akenarları olabilir a@1 → b@2, a@1 → a@3, b@2 → a@3. Bu grafik sonlu durum makinesine eşdeğerdir.

Arama sırasında, başlangıçta ağacın ilk düğümü olan bir imleç tutuyoruz. Daha sonra B alt listesindeki her eleman için kenardan yürürüz . Böyle bir kenar yoksa, B alt liste değildir. Tüm öğelerden sonra imleç geçerli bir düğüm içeriyorsa, B bir alt listedir.

pseudocode

Emir Kipi:

# 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

Fonksiyonel tarzı:

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

Örnek uygulama (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;

Bir kenara, nasıl studyçalıştığına ve uyguladığınız algoritmaların burada bazı pratik uygulamalara sahip olabileceğini düşündünüz mü?

1
@MichaelT Anladığımdan emin değilim… Bir lisans öğrencisiyim, ama aslında nasıl çalışacağımı henüz keşfetmedim </joke>. Perl yerleşik işlevinden bahsediyorsanız: Bugünlerde hayır. Mevcut uygulama sadece bir düzine geriye dönük uyumluluk çizgisidir. Normal ifade motoru, değişken boyutlu desenleri eşleştirmeden önce sabit dizeleri aramak gibi doğrudan sezgisel tarama kullanır. studydaha önce ikinci çözümümden farklı olarak karakter-konum arama tabloları oluşturmuştu.
amon

daha da iyi bir algoritma ile güncellendi
amon

Bu FSM üzerinde daha fazla ayrıntı vererek, tüm test sekanslarını bir FSM'de 'derleyebilir' ve ardından tüm sekans boyunca çalışabilirsiniz. Sonunda hangi duruma geldiğinize bağlı olarak hangi alt dizilerin eşleştiğini belirler. Bu kesinlikle bir bilgisayar önemsiz olmayan biri için elle yapmak yerine yapmak olurdu şey.

Sen o doğru @MichaelT olabilir bir tanıyıcı bu şekilde kurmak. Ancak, zaten O (f (A)) n · O (B) + başlatma maliyetine düştük . Tüm Bs'lerin üçlü benzeri yapısını oluşturmak, O (n · B) gibi bir şey alır ve eşleşme O (A) 'dadır . Bunun teorik olarak daha ucuz olma şansı var (grafiği 3. çözümde oluşturmak pahalı olabilir, ancak bu sadece bir kerelik bir maliyettir). Ben bir trie A ≫ n · B için daha uygun olduğunu ve akış girişi işleyemez dezavantajı var - tüm Bs eşleştirme önce yüklenmelidir. Cevabı muhtemelen 6 saat içinde güncelleyeceğim.
amon

6

İşte kendi algoritmanızı uygulamak için "sıkı çalışma" ve "tekerleği yeniden icat" önlemek için pratik bir yaklaşım: sorun için düzenli bir ifade motoru kullanmak .

Tüm A sayılarını bir dizeye ve tüm B sayılarını normal ifadeyle ayrılmış bir dizeye koymanız yeterlidir (.*). ^Başına ve $sonuna bir karakter ekleyin . Ardından, favori normal ifade motorunuzun tüm eşleşmeleri aramasına izin verin. Örneğin,

A = (1234356), B = (236)

B gibi bir reg exp oluşturun ^(.*)2(.*)3(.*)6(.*)$. Şimdi global bir normal ifade araması yapın. A alt dizinizde hangi konumlarda eşleştiğini bulmak için ilk 3 alt eşlemenin uzunluğunu kontrol etmeniz yeterlidir.

Tamsayı aralığınız 0 ile 9 arasında kalırsa, bu işi yapmak için önce tek harflerle kodlamayı düşünebilirsiniz veya fikri bir ayırma karakteri kullanarak uyarlamanız gerekebilir.

Tabii ki, bu yaklaşımın hızı kullandığınız reg exp motorunun hızına çok bağlı olacaktır, ancak son derece optimize edilmiş motorlar vardır ve sanırım "kutudan" daha hızlı bir algoritma uygulamak zor olacaktır .


Bir regex ve motoru çağırmak için sonuna kadar gitmek gerekmez. Çalıştırmak için basit bir deterministik sonlu otomata kullanmak mümkün olacaktır. Bu bir 'düz çizgi' yol boyunca.

@MichaelT: Eh, elimde bir "jenerik sonlu otomata" kütüphanem yok ve OP bize kullandığı programlama dilini anlatmadı, ama hemen hemen her ciddi programlama dili için düzenli ifadeler mevcut "kutunun dışında ". Bu, benim önerimin, örneğin amon'un çözümünden çok daha az kodla uygulanmasını çok kolay hale getirmelidir. IMHO OP denemeli, eğer onun için çok yavaşsa, daha karmaşık bir çözümün ona daha iyi hizmet edip etmeyeceğini deneyebilir.
Doc Brown

Genel bir kütüphaneye ihtiyacınız yok. Tek ihtiyacınız olan 'desen' dizisi ve dizideki dizine bir işaretçi. Dizin, bir sonraki "aranan" değere işaret eder ve kaynaktan okuduğunuzda dizini artırır. Dizinin sonuna geldiğinizde diziyle eşleştiniz. Kaynağın sonunu sonuna ulaşmadan okursanız, eşleştirmediniz.

@MichaelT: o zaman neden bu algoritmanın bir taslağını cevap olarak göndermiyorsunuz?
Doc Brown

Çoğunlukla zaten daha iyi yanıtlandığı için - "A sekansı için bir imleç tutuyoruz. Sonra B alt dizisindeki tüm öğeleri yineliyoruz. Her bir öğe için, eşleşen bir öğe bulana kadar imleci A'da ilerletiyoruz. eşleşen öğe bulundu, ardından B bir alt dizidir. "

0

Bu algoritma, uzunluğu elde etmek ve sekansı tekrarlamak verimli ise oldukça etkili olmalıdır.

  1. Her iki dizinin uzunluğunu karşılaştırın. Daha uzun sequenceve daha kısasubsequence
  2. Her iki dizinin başında başlayın ve sonuna kadar döngü yapın sequence.
    1. Geçerli konumdaki sayı, geçerli konumdaki sayıya sequenceeşit mi?subsequence
    2. Cevabınız evet ise, her iki pozisyonu bir ileri taşı
    3. Değilse, yalnızca sequencebir diğerinin konumunu hareket ettirin
  3. Konumudur subsequencesonundasequence
  4. Evet ise, iki dizi eşleşir
  5. Değilse, iki dizi eşleşmiyor
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.