En Hızlı En Uzun Ortak Bulgu Bulucu


11

Senin görevin 1000 uzunluğu n dizeleri için en uzun ortak alt sıra sorunu çözmek için .

İki veya daha fazla dizeleri LCS sorununa geçerli bir çözüm S 1 , ... S n herhangi dizedir T karakterlerin öyle ki maksimal uzunluğu T tüm görünür S i de aynı sırada, T .

O Not T olmayan bir alt olmak dize ait S i .

Bu sorunu zaten en kısa miktarda kodla çözdük . Bu sefer boyut önemli değil.

Misal

Dizeler axbyczve xaybzcuzunluğu 3 ortak 8 alt dizileri var:

abc abz ayc ayz xbc xbz xyc xyz

Bunlardan herhangi biri LCS problemi için geçerli bir çözüm olacaktır.

ayrıntılar

LCS sorununu, yukarıda açıklandığı gibi, aşağıdaki kurallara uyarak çözen tam bir program yazın:

  • Giriş, kod noktaları 0x30 ile 0x3F arasında olan ASCII karakterlerinden oluşan iki veya daha fazla uzunluk 1000 dizesinden oluşacaktır.

  • Girişi STDIN'den okumalısınız.

    Giriş formatı için iki seçeneğiniz vardır:

    • Her dizeyi (sonuncusu dahil) bir satır besleme takip eder.

    • Dizeler, ayırıcı ve takip eden satır beslemesi olmadan zincirlenir.

  • Dizelerin sayısı, programınıza bir komut satırı parametresi olarak geçirilir.

  • Çıktıyı, yani geçerli çözümlerden herhangi birini LCS'ye, STDOUT'a ve ardından bir satır beslemesine yazmanız gerekir.

  • Seçtiğiniz dilin işletim sistemim için ücretsiz (biradaki gibi) bir derleyici / yorumlayıcı olması gerekir (Fedora 21).

  • Herhangi bir derleyici bayrağına veya belirli bir tercümana ihtiyacınız varsa, lütfen yazınızda belirtin.

puanlama

Kodunuzu geçerli bir çözüm yazdırmak 120 saniyeden uzun sürene kadar 2, 3 vb. Dizelerle çalıştıracağım. Bu , n'nin her değeri için 120 saniyeniz olduğu anlamına gelir .

Kodunuzun zamanında bitirildiği en yüksek dize miktarı puanınızdır.

N puanının bağlı olması durumunda n dizeler için problemi en kısa sürede çözen başvuru kazanan ilan edilecektir.

Tüm gönderimler makinemde zamanlanacak (Intel Core i7-3770, 16 GiB RAM, takas yok).

N dizeleri (n-1) inci testi çağırarak oluşturulur rand n(talep edilmesi halinde, ve linefeeds sıyırma), burada randtanımlandığı gibidir, aşağıdaki gibi:

rand()
{
    head -c$[500*$1] /dev/zero |
    openssl enc -aes-128-ctr -K 0 -iv $1 |
    xxd -c500 -ps |
    tr 'a-f' ':-?'
}

Anahtar 0yukarıdaki kodda, ama ben kimse çıkış (kısmen) kodlama şüpheli varsa açıklanmayan bir değere değiştirme hakkını saklı tutar.


İstisnalar atabilir miyiz?
HyperNeutrino

@JamesSmith Çıktı doğru olduğu sürece, emin olun.
Dennis

Arabellek okuyucuyla okuduğum için, ioexception'ı atabilir miyim public static void main(...)?
HyperNeutrino

@JamesSmith Java'yı gerçekten bilmiyorum, bu yüzden ne olduğunu bilmiyorum, ancak istisnalar hakkında endişelenmeyin.
Dennis

4
@JamesSmith Kod uzunluğu bu zorluk için önemli olmadığından, istisnaları yakalayamaz mısınız?
Reto Koradi

Yanıtlar:


5

C, n = ~ 7 saniyede 3

Algoritma

Algoritma, standart dinamik programlama çözümünün ndizilere doğrudan genelleştirilmesidir . 2 dize Ave Bstandart yineleme şöyle görünür:

L(p, q) = 1 + L(p - 1, q - 1)           if A[p] == B[q]
        = max(L(p - 1, q), L(p, q - 1)) otherwise

3 dizeleri için A, B, Cben kullanın:

L(p, q, r) = 1 + L(p - 1, q - 1, r - 1)                          if A[p] == B[q] == C[r]
           = max(L(p - 1, q, r), L(p, q - 1, r), L(p, q, r - 1)) otherwise

Kod, bu mantığı, n .

verim

Kodumun karmaşıklığı O (s ^ n), sdizelerin uzunluğu ile. Bulduğum şeylere dayanarak, sorun NP-tamamlanmış gibi görünüyor. Bu nedenle, yayınlanan algoritma daha büyük değerleri için çok verimsiz nolsa da, aslında daha iyi yapmak mümkün olmayabilir. Gördüğüm tek şey, küçük alfabe verimliliğini artıran bazı yaklaşımlar. Alfabe burada orta derecede küçük olduğu için (16), bu bir gelişmeye yol açabilir. Hala kimsenin n = 42 dakikadan daha uzun süren ve n = 4zaten iddialı görünen meşru bir çözüm bulamayacağını tahmin ediyorum .

İlk uygulamada bellek kullanımını azalttım, böylece n = 4yeterince zaman verebilsin. Ama sadece dizinin uzunluğunu üretti, dizinin kendisini değil. Kodu görmek için bu yayının düzeltme geçmişini kontrol edin.

kod

N boyutlu matrisler üzerindeki döngüler, sabit döngülerden daha fazla mantık gerektirdiğinden, en düşük boyut için sabit bir döngü kullanıyorum ve kalan boyutlar için yalnızca genel döngü mantığını kullanıyorum.

#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#define N_MAX 4

int main(int argc, char* argv[]) {
    int nSeq = argc - 1;
    if (nSeq > N_MAX) {
        nSeq = N_MAX;
    }

    char** seqA = argv + 1;

    uint64_t totLen = 1;
    uint64_t lenA[N_MAX] = {0};
    uint64_t offsA[N_MAX] = {1};
    uint64_t offsSum = 0;
    uint64_t posA[N_MAX] = {0};

    for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
        lenA[iSeq] = strlen(seqA[iSeq]);
        totLen *= lenA[iSeq] + 1;

        if (iSeq + 1 < nSeq) {
            offsA[iSeq + 1] = totLen;
        }

        offsSum += offsA[iSeq];
    }

    uint16_t* mat = calloc(totLen, 2);
    uint64_t idx = offsSum;

    for (;;) {
        for (uint64_t pos0 = 0; pos0 < lenA[0]; ++pos0) {
            char firstCh = seqA[0][pos0];
            int isSame = 1;
            uint16_t maxVal = mat[idx - 1];

            for (int iSeq = 1; iSeq < nSeq; ++iSeq) {
                char ch = seqA[iSeq][posA[iSeq]];
                isSame &= (ch == firstCh);

                uint16_t val = mat[idx - offsA[iSeq]];
                if (val > maxVal) {
                    maxVal = val;
                }
            }

            if (isSame) {
                mat[idx] = mat[idx - offsSum] + 1;
            } else {
                mat[idx] = maxVal;
            }

            ++idx;
        }

        idx -= lenA[0];

        int iSeq = 1;
        while (iSeq < nSeq && posA[iSeq] == lenA[iSeq] - 1) {
            posA[iSeq] = 0;
            idx -= (lenA[iSeq] - 1) * offsA[iSeq];
            ++iSeq;
        }
        if (iSeq == nSeq) {
            break;
        }

        ++posA[iSeq];
        idx += offsA[iSeq];
    }

    int score = mat[totLen - 1];

    char* resStr = malloc(score + 1);
    resStr[score] = '\0';

    for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
        posA[iSeq] = lenA[iSeq] - 1;
    }

    idx = totLen - 1;
    int resIdx = score - 1;

    while (resIdx >= 0) {
        char firstCh = seqA[0][posA[0]];
        int isSame = 1;
        uint16_t maxVal = mat[idx - 1];
        int maxIdx = 0;

        for (int iSeq = 1; iSeq < nSeq; ++iSeq) {
            char ch = seqA[iSeq][posA[iSeq]];
            isSame &= (ch == firstCh);

            uint16_t val = mat[idx - offsA[iSeq]];
            if (val > maxVal) {
                maxVal = val;
                maxIdx = iSeq;
            }
        }

        if (isSame) {
            resStr[resIdx--] = firstCh;
            for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
                --posA[iSeq];
            }
            idx -= offsSum;
        } else {
            --posA[maxIdx];
            idx -= offsA[maxIdx];
        }
    }

    free(mat);

    printf("score: %d\n", score);
    printf("%s\n", resStr);

    return 0;
}

Koşu Talimatları

Koşmak:

  • Kodu bir dosyaya kaydedin, örn lcs.c.
  • Yüksek optimizasyon seçenekleriyle derleyin. Kullandım:

    clang -O3 lcs.c
    

    Linux'ta şunu denerdim:

    gcc -Ofast lcs.c
    
  • Komut satırı bağımsız değişkenleri olarak verilen 2 ila 4 sıra ile çalıştırın:

    ./a.out axbycz xaybzc
    

    Örnekler için kullanılan alfabe kabuk özel karakterleri içerdiğinden, gerekirse komut satırı bağımsız değişkenlerini tek tırnak içine alın.

Sonuçlar

test2.shve test3.shDennis'in test dizileridir. Doğru sonuçları bilmiyorum, ancak çıktı en azından makul görünüyor.

$ ./a.out axbycz xaybzc
score: 3
abc

$ time ./test2.sh 
score: 391
16638018802020>3??3232270178;47240;24331395?<=;99=?;178675;866002==23?87?>978891>=9<6<9381992>>7030829?255>6804:=3>:;60<9384=081;0:?66=51?0;5090724<85?>>:2>7>3175?::<9199;5=0:494<5<:7100?=95<91>1887>33>67710==;48<<327::>?78>77<6:2:02:<7=5?:;>97<993;57=<<=:2=9:8=118563808=962473<::8<816<464<1==925<:5:22?>3;65=>=;27?7:5>==3=4>>5>:107:20575347>=48;<7971<<245<??219=3991=<96<??735698;62?<98928

real  0m0.012s
user  0m0.008s
sys   0m0.003s

$ time ./test3.sh 
score: 269
662:2=2::=6;738395=7:=179=96662649<<;?82?=668;2?603<74:6;2=04=>6;=6>=121>1>;3=22=3=3;=3344>0;5=7>>7:75238;559133;;392<69=<778>3?593?=>9799<1>79827??6145?7<>?389?8<;;133=505>2703>02?323>;693995:=0;;;064>62<0=<97536342603>;?92034<?7:=;2?054310176?=98;5437=;13898748==<<?4

real  0m7.218s
user  0m6.701s
sys   0m0.513s

Bu açık olmasaydı özür diler, ancak LCS'yi yazdırmanız gerekir, sadece uzunluğunu değil.
Dennis

@Dennis anlıyorum. O zaman optimizasyonlarımdan bazıları boşuna kaldı. Dizeyi yeniden oluşturabilmem için tam matrisi saklayan bir sürüme geri dönmem gerekecek. Bu n = 4 için çalıştırılamaz, ancak n = 3 için yine de 10 saniyenin altında bitmesi gerekir. Sanırım hala tam matrisim olduğunda yaklaşık 6-7 saniye kaldım.
Reto Koradi

Tekrar üzgünüm. Bu konuda soru çok net değildi ... Çıktınızı yayınladığınızda, bunu BrainSteel'inkiyle karşılaştırabileceğim. Programınızın rapor ettiği uzunluk, çıktısının uzunluğunu n = 2 için 5 aşar. Bu arada, N_MAXbir makro olarak tanımlamak ve -std=c99kodunuzu GCC ile derlemek için derleyici bayrağını eklemek zorunda kaldım .
Dennis

@Dennis Sorun değil. Çözüm "bir dize" olduğunu söyledi, bu yüzden yeterince açık olmalı. Neredeyse sadece C ++ kullanıyorum, bu yüzden C'de neye izin verildiğinden emin değilim. Bu kod C ++ olarak başladı, ancak bir kez gerçekten herhangi bir C ++ özelliği kullanmadığımı fark ettiğimde, Mac'imde C. clang'a geçtim memnun kaldı, ancak muhtemelen varsayılan olarak farklı bir C sürümü kullanıyor veya daha yumuşak.
Reto Koradi

1
@Dennis Tamam, geri izleme mantığını ekledim, böylece dizeyi üretebiliyorum. N = 3 için şimdi yaklaşık 7 saniye sürüyor.
Reto Koradi

3

Bu yanıt şu anda bir hata nedeniyle bozuldu. Yakında düzeltme ...

C, ~ 35 saniyede 2 dize

Bu, devam eden bir çalışma (korkunç dağınıklık ile gösterildiği gibi), ancak umarım bazı iyi cevaplar çıkarır!

Kod:

#include "stdlib.h"
#include "string.h"
#include "stdio.h"
#include "time.h"

/* For the versatility */
#define MIN_CODEPOINT 0x30
#define MAX_CODEPOINT 0x3F
#define NUM_CODEPOINT (MAX_CODEPOINT - MIN_CODEPOINT + 1)
#define CTOI(c) (c - MIN_CODEPOINT)

#define SIZE_ARRAY(x) (sizeof(x) / sizeof(*x))

int LCS(char** str, int num);
int getshared(char** str, int num);
int strcount(char* str, char c);

int main(int argc, char** argv) {
    char** str = NULL;
    int num = 0;
    int need_free = 0;
    if (argc > 1) {
        str = &argv[1];
        num = argc - 1;
    }
    else {
        scanf(" %d", &num);
        str = malloc(sizeof(char*) * num);
        if (!str) {
            printf("Allocation error!\n");
            return 1;
        }

        int i;
        for (i = 0; i < num; i++) {
            /* No string will exceed 1000 characters */
            str[i] = malloc(1001);
            if (!str[i]) {
                printf("Allocation error!\n");
                return 1;
            }

            scanf(" %1000s", str[i]);

            str[i][1000] = '\0';
        }

        need_free = 1;
    }

    clock_t start = clock();

    /* The call to LCS */
    int size = LCS(str, num);

    double dt = ((double)(clock() - start)) / CLOCKS_PER_SEC;
    printf("Size: %d\n", size);
    printf("Elapsed time on LCS call: %lf s\n", dt);

    if (need_free) {
        int i;
        for (i = 0; i < num; i++) {
            free(str[i]);
        }
        free(str);
    }

    return 0;
}

/* Some terribly ugly global variables... */
int depth, maximum, mapsize, lenmap[999999][2];
char* (strmap[999999][20]);
char outputstr[1000];

/* Solves the LCS problem on num strings str of lengths len */
int LCS(char** str, int num) {
    /* Counting variables */
    int i, j;

    if (depth + getshared(str, num) <= maximum) {
        return 0;
    }

    char* replace[num];
    char match;
    char best_match = 0;
    int earliest_set = 0;
    int earliest[num];
    int all_late;
    int all_early;
    int future;
    int best_future = 0;
    int need_future = 1;

    for (j = 0; j < mapsize; j++) {
        for (i = 0; i < num; i++)
            if (str[i] != strmap[j][i])
                break;
        if (i == num) {
            best_match = lenmap[j][0];
            best_future = lenmap[j][1];
            need_future = 0;
            if (best_future + depth < maximum || !best_match)
                goto J;
            else {
                match = best_match;
                goto K;
            }
        }
    }

    for (match = MIN_CODEPOINT; need_future && match <= MAX_CODEPOINT; match++) {

    K:
        future = 1;
        all_late = earliest_set;
        all_early = 1;
        for (i = 0; i < num; replace[i++]++) {
            replace[i] = strchr(str[i], match);
            if (!replace[i]) {
                future = 0;
                break;
            }

            if (all_early && earliest_set && replace[i] - str[i] > earliest[i])
                all_early = 0;
            if (all_late && replace[i] - str[i] < earliest[i])
                all_late = 0;
        }
        if (all_late) {
            future = 0;
        }

    I:
        if (future) {
            if (all_early || !earliest_set) {
                for (i = 0; i < num; i++) {
                    earliest[i] = (int)(replace[i] - str[i]);
                }
            }

            /* The recursive bit */
            depth++;
            future += LCS(replace, num);
            depth--;

            best_future = future > best_future ? (best_match = match), future : best_future;
        }
    }

    if (need_future) {
        for (i = 0; i < num; i++)
            strmap[mapsize][i] = str[i];
        lenmap[mapsize][0] = best_match;
        lenmap[mapsize++][1] = best_future;
    }

J:
    if (depth + best_future >= maximum) {
        maximum = depth + best_future;
        outputstr[depth] = best_match;
    }

    if (!depth) {
        mapsize = 0;
        maximum = 0;
        puts(outputstr);
    }

    return best_future;
}

/* Return the number of characters total (not necessarily in order) that the strings all share */
int getshared(char** str, int num) {
    int c, i, tot = 0, min;
    for (c = MIN_CODEPOINT; c <= MAX_CODEPOINT; c++) {
        min = strcount(str[0], c);
        for (i = 1; i < num; i++) {
            int count = strcount(str[i], c);
            if (count < min) {
                min = count;
            }
        }
        tot += min;
    }

    return tot;
}

/* Count the number of occurrences of c in str */
int strcount(char* str, char c) {
    int tot = 0;
    while ((str = strchr(str, c))) {
        str++, tot++;
    }
    return tot;
}

Tüm LCS hesaplamasını gerçekleştiren ilgili işlev, işlevdir LCS. Yukarıdaki kod bu işleve kendi çağrısını zamanlayacaktır.

Farklı kaydet main.cve şununla derle:gcc -Ofast main.c -o FLCS

Kod, komut satırı bağımsız değişkenleriyle veya stdin aracılığıyla çalıştırılabilir. Stdin kullanırken, bir dizi dizenin ardından dizelerin kendisinin gelmesini bekler.

~ Me$ ./FLCS "12345" "23456"
2345
Size: 4
Elapsed time on LCS call: 0.000056 s

Veya:

~ Me$ ./FLCS
6 
2341582491248123139182371298371239813
2348273123412983476192387461293472793
1234123948719873491234120348723412349
1234129388234888234812834881423412373
1111111112341234128469128377293477377
1234691237419274737912387476371777273
1241231212323
Size: 13
Elapsed time on LCS call: 0.001594 s

1.7Ghz Intel Core i7 ve Dennis'in sağladığı test senaryosuna sahip bir Mac OS X kutusunda, 2 dize için aşağıdaki çıktıyı alıyoruz:

16638018800200>3??32322701784=4240;24331395?<;=99=?;178675;866002==23?87?>978891>=9<66=381992>>7030829?25265804:=3>:;60<9384=081;08?66=51?0;509072488>2>924>>>3175?::<9199;330:494<51:>748571?153994<45<??20>=3991=<962508?7<2382?489
Size: 386
Elapsed time on LCS call: 33.245087 s

Bu yaklaşım, burada daha önceki zorluklara yaklaşımımla çok benzer. . Önceki optimizasyona ek olarak, artık her yinelemede dizeler arasında toplam paylaşılan karakter sayısını kontrol ediyoruz ve zaten mevcut olandan daha uzun bir alt dize elde etmenin bir yolu yoksa erken çıkıyoruz.

Şimdilik, 2 dizeyi iyi işliyor ancak daha fazla çökme eğilimi gösteriyor. Daha fazla iyileştirme ve daha iyi bir açıklama!


1
Sanırım bir şey kaçırdım. 2 dizeyle bu çözmek için yaklaşık 1000 ^ 2 adım atması gereken klasik bir dinamik programlama sorunu değil mi? Başka bir deyişle, saniyenin bir kısmı.

@Lembik Evet, olmalı. Bu yöntem, 2'den fazla dizeyi işlemek için oluşturulmuştur, ancak iyi sonuçlara sahip olması için dizgi uzunluğuyla çok zayıf ölçeklendirilmiştir. Kolumda çok daha fazla numara var ve eğer herhangi biri gerçekten işe yarıyorsa ... Her şey son derece gelişmelidir.
BrainSteel

Bir yerde bir sorun var gibi görünüyor. @ RetoKoradi'nin kodu n = 2 için 391 uzunluğunda geçerli bir ortak alt dize bulurken, kodunuz 386 uzunluğunu bildirir ve 229 uzunluğunda bir dize yazdırır.
Dennis

@Dennis Umm ... Evet, evet öyle ... Ah canım. Bu utanç verici. Üzerinde çalışıyorum :) Hatayı yansıtmak için cevabı düzenleyeceğim.
BrainSteel
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.