सबसे तेजी से सबसे लंबे समय तक आम परिणाम खोजक


11

आपका काम लंबाई 1000 के एन स्ट्रिंग्स के लिए सबसे लंबे समय तक सामान्य प्रक्रिया को हल करना है ।

के लिए दो या अधिक तार LCS समस्या का एक मान्य समाधान एस 1 , एस ... n कोई भी स्ट्रिंग है टी अधिक से अधिक लंबाई की ऐसी है कि के पात्रों टी सभी समाचारों में दिखाई एस मैं के रूप में, एक ही क्रम में टी

ध्यान दें कि T को S i का उप स्ट्रिंग होना आवश्यक नहीं है ।

हमने इस समस्या को पहले ही कम से कम मात्रा में कोड में हल कर लिया है । इस बार, आकार कोई फर्क नहीं पड़ता।

उदाहरण

स्ट्रिंग्स axbyczऔर xaybzcलंबाई 3 के 8 सामान्य क्रम हैं:

abc abz ayc ayz xbc xbz xyc xyz

इनमें से कोई भी एलसीएस समस्या के लिए एक वैध समाधान होगा।

विवरण

एक पूर्ण प्रोग्राम लिखें, जो एलसीएस समस्या को हल करता है, जैसा कि ऊपर बताया गया है, निम्नलिखित नियमों का पालन करना:

  • इनपुट में लंबाई 1000 के दो या अधिक तार शामिल होंगे, जिसमें 0x30 और 0x3F के बीच कोड बिंदुओं के साथ ASCII वर्ण शामिल हैं।

  • आपको STDIN से इनपुट पढ़ना होगा।

    इनपुट प्रारूप के लिए आपके पास दो विकल्प हैं:

    • प्रत्येक स्ट्रिंग (अंतिम सहित) के बाद एक लाइनफीड है।

    • स्ट्रिंग्स को बिना किसी विभाजक और बिना ट्रेलिंग लाइनफीड के एक साथ जंजीर से जकड़ा जाता है।

  • स्ट्रिंग्स की संख्या आपके प्रोग्राम में कमांड-लाइन पैरामीटर के रूप में पारित की जाएगी।

  • आपको आउटपुट, यानी LCS के वैध समाधान में से किसी एक को STDOUT पर लिखना होगा, उसके बाद एक लाइनफीड पर।

  • आपकी पसंद की भाषा को मेरे ऑपरेटिंग सिस्टम के लिए एक मुफ्त (बीयर के रूप में) संकलक / दुभाषिया होना चाहिए (फेडोरा 21)।

  • यदि आपको किसी संकलक झंडे या एक विशिष्ट दुभाषिया की आवश्यकता है, तो कृपया अपने पोस्ट में इसका उल्लेख करें।

स्कोरिंग

मैं आपके कोड को 2, 3, आदि स्ट्रिंग्स के साथ चलाऊंगा जब तक कि एक वैध समाधान प्रिंट करने में 120 सेकंड से अधिक समय नहीं लगता। इसका मतलब है कि आपके पास n के प्रत्येक मान के लिए 120 सेकंड हैं ।

तार की सबसे अधिक मात्रा जिसके लिए आपका कोड समय में समाप्त हो गया, वह आपका स्कोर है।

N के एक बंधे स्कोर की स्थिति में, कम समय में n तार के लिए समस्या को हल करने वाले सबमिशन को विजेता घोषित किया जाएगा।

सभी प्रस्तुतियाँ मेरी मशीन (इंटेल कोर i7-3770, 16 GiB RAM, कोई स्वैप नहीं) पर समयबद्ध होंगी।

N के तार (n-1) वें परीक्षण को फोन करके उत्पन्न हो जाएगा rand n(और linefeeds अलग करना यदि अनुरोध), जहां randपरिभाषित किया गया है इस प्रकार है:

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

कुंजी 0उपरोक्त कोड में है, लेकिन मैं इसे अनजान मूल्य पर बदलने का अधिकार सुरक्षित रखता हूं अगर मुझे किसी पर (आंशिक रूप से) हार्डकोडिंग आउटपुट पर संदेह है।


क्या हम अपवादों को फेंक सकते हैं?
हाइपरनेत्रिनो

@ जेमस्मिथ जब तक आउटपुट सही है, निश्चित है।
डेनिस

चूंकि मैं बफ़रड्रेडर के साथ पढ़ रहा हूं, क्या मैं ioexception से फेंक सकता हूं public static void main(...)?
हाइपरन्यूट्रिनो

@ जैमस्मिथ मैं वास्तव में जावा को नहीं जानता, इसलिए मुझे नहीं पता कि वह क्या है, लेकिन अपवादों की चिंता मत करो।
डेनिस

4
@JamesSmith चूंकि इस चुनौती के लिए कोड की लंबाई मायने नहीं रखती, क्या आप केवल अपवादों को नहीं पकड़ सकते?
रेटो कोराडी

जवाबों:


5

सी, एन = 3 ~ 7 सेकंड में

कलन विधि

एल्गोरिथ्म nअनुक्रमों के लिए मानक गतिशील प्रोग्रामिंग समाधान का एक सीधा सामान्यीकरण है । 2 तार के लिए Aऔर B, मानक पुनरावृत्ति इस तरह दिखता है:

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 तार के लिए A, B, Cमैं का उपयोग करें:

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

कोड इस तर्क को मनमाने मूल्यों के लिए लागू करता है n

दक्षता

sस्ट्रिंग्स की लंबाई के साथ मेरे कोड की जटिलता हे (ओं ^ n) है । मुझे जो मिला, उसके आधार पर ऐसा लग रहा है कि समस्या एनपी-पूर्ण है। इसलिए जब पोस्ट एल्गोरिथ्म के बड़े मूल्यों के लिए बहुत अक्षम है n, तो वास्तव में बड़े पैमाने पर बेहतर करना संभव नहीं हो सकता है। केवल एक चीज जो मैंने देखी वह कुछ दृष्टिकोण हैं जो छोटे अक्षर के लिए दक्षता में सुधार करते हैं। चूँकि यहाँ वर्णमाला मामूली रूप से छोटी है (16), जिससे सुधार हो सकता है। मैं अभी भी भविष्यवाणी करता हूं कि किसी को भी एक वैध समाधान नहीं मिलेगा जो n = 42 मिनट से अधिक हो जाता है , और n = 4पहले से ही महत्वाकांक्षी दिखता है।

मैंने प्रारंभिक कार्यान्वयन में मेमोरी उपयोग को कम कर दिया, ताकि यह n = 4पर्याप्त समय दिया जा सके । लेकिन इसने केवल अनुक्रम की लंबाई का उत्पादन किया, न कि स्वयं अनुक्रम का। उस कोड को देखने के लिए इस पोस्ट के संशोधन इतिहास की जाँच करें।

कोड

चूंकि एन-डायमेंशनल मैट्रिस पर लूप्स को निश्चित लूप की तुलना में अधिक तर्क की आवश्यकता होती है, इसलिए मैं सबसे कम आयाम के लिए एक निश्चित लूप का उपयोग कर रहा हूं, और शेष आयामों के लिए केवल सामान्य लूपिंग तर्क का उपयोग कर रहा हूं।

#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;
}

रनिंग के लिए निर्देश

चलाने के लिए:

  • फ़ाइल में कोड सहेजें, जैसे lcs.c
  • उच्च अनुकूलन विकल्पों के साथ संकलन करें। मैंनें इस्तेमाल किया:

    clang -O3 lcs.c
    

    लिनक्स पर, मैं कोशिश करूँगा:

    gcc -Ofast lcs.c
    
  • कमांड लाइन तर्क के रूप में दिए गए 2 से 4 क्रमों के साथ चलाएँ:

    ./a.out axbycz xaybzc
    

    यदि आवश्यक हो तो एकल कमांड लाइन के तर्कों को उद्धृत करें, क्योंकि उदाहरणों के लिए उपयोग की जाने वाली वर्णमाला में शेल विशेष वर्ण होते हैं।

परिणाम

test2.shऔर test3.shडेनिस से टेस्ट सीक्वेंस हैं। मैं सही परिणाम नहीं जानता, लेकिन आउटपुट कम से कम प्रशंसनीय है।

$ ./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

माफी अगर यह स्पष्ट नहीं था, लेकिन आपको एलसीएस को प्रिंट करना होगा, न केवल इसकी लंबाई।
डेनिस

@ डेनिस मैं देख रहा हूं। तब मेरी कुछ आशाएँ व्यर्थ थीं। मुझे एक ऐसे संस्करण पर वापस जाना होगा जो पूर्ण मैट्रिक्स को संग्रहीत करता है ताकि मैं स्ट्रिंग को फिर से संगठित कर सकूं। यह n = 4 के लिए चलाने में सक्षम नहीं होगा, लेकिन फिर भी n = 3 के लिए 10 सेकंड से नीचे समाप्त होना चाहिए। मुझे लगता है कि मैं 6-7 सेकंड के आसपास था जब मेरे पास अभी भी पूर्ण मैट्रिक्स था।
रेटो कोराडी

फिर से क्षमा। इस बारे में सवाल बहुत स्पष्ट नहीं था ... जब आप अपना आउटपुट पोस्ट करते हैं, तो मैं इसकी तुलना ब्रेनसेल के साथ कर पाऊंगा। आपके प्रोग्राम की रिपोर्ट की लंबाई n = 2 के लिए उसके आउटपुट की लंबाई 5 से अधिक है। वैसे, मुझे N_MAXएक मैक्रो के रूप में परिभाषित करना था और -std=c99अपने कोड को जीसीसी के साथ संकलित करने के लिए संकलक ध्वज जोड़ना था ।
डेनिस

@ डेनिस कोई समस्या नहीं है। इसने कहा कि समाधान "एक स्ट्रिंग है", इसलिए यह पर्याप्त स्पष्ट होना चाहिए था। मैं लगभग विशेष रूप से C ++ का उपयोग करता हूं, इसलिए मुझे यकीन नहीं है कि सी में क्या अनुमति है। यह कोड C ++ के रूप में शुरू हुआ था, लेकिन एक बार मुझे एहसास हुआ कि मैं वास्तव में किसी भी सी ++ सुविधाओं का उपयोग नहीं कर रहा था, मैंने इसे सी पर स्विच कर दिया। इसके साथ खुश था, लेकिन यह शायद डिफ़ॉल्ट रूप से एक अलग सी संस्करण का उपयोग करता है, या बस अधिक उदार है।
रेटो कोराडी

1
@ डेनिस ओके, मैंने ट्रेसबैक लॉजिक को जोड़ा ताकि मैं स्ट्रिंग का उत्पादन कर सकूं। N = 3 के लिए अब लगभग 7 सेकंड लगते हैं।
रेटो कोराडी

3

यह उत्तर एक बग के कारण वर्तमान में टूट गया है। जल्द ठीक हो रहा है ...

सी, ~ 35 सेकंड में 2 तार

यह बहुत प्रगति पर काम है (जैसा कि भयानक गड़बड़ी द्वारा दिखाया गया है), लेकिन उम्मीद है कि यह कुछ अच्छे जवाबों को उजागर करता है!

कोड:

#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;
}

प्रासंगिक फ़ंक्शन जो LCS गणना के सभी कार्य करता है, वह फ़ंक्शन है LCS। उपरोक्त कोड इस फ़ंक्शन के लिए स्वयं की कॉल का समय देगा।

इस रूप में सहेजें main.cऔर संकलित करें:gcc -Ofast main.c -o FLCS

कोड को कमांड लाइन के तर्कों के साथ या स्टड के माध्यम से चलाया जा सकता है। स्टडिन का उपयोग करते समय, यह कई तार की अपेक्षा करता है, जिसके बाद स्वयं तार होता है।

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

या:

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

1.7Ghz इंटेल कोर i7 के साथ एक मैक ओएस एक्स बॉक्स और डेनिस द्वारा दिए गए परीक्षण के मामले में, हमें 2 स्ट्रिंग्स के लिए निम्नलिखित आउटपुट मिलते हैं:

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

दृष्टिकोण पहले की चुनौती के लिए मेरे दृष्टिकोण के समान है, यहां । पिछले अनुकूलन के अलावा, अब हम प्रत्येक रिकर्सन में स्ट्रिंग्स के बीच कुल साझा किए गए वर्णों की जांच करते हैं और जल्दी से बाहर निकलते हैं यदि पहले से मौजूद कोई विकल्प नहीं है।

अभी के लिए, यह 2 स्ट्रिंग्स को ठीक करता है, लेकिन अधिक पर क्रैश-वाई प्राप्त करता है। अधिक सुधार और आने के लिए एक बेहतर व्याख्या!


1
मुझे लगता है कि मैंने कुछ मिस किया है। 2 तार के साथ यह एक क्लासिक गतिशील प्रोग्रामिंग समस्या नहीं है जिसे हल करने के लिए लगभग 1000 ^ 2 कदम उठाने चाहिए? दूसरे शब्दों में, एक सेकंड का एक अंश।

@ लिम्बिक हाँ, यह चाहिए। इस पद्धति का निर्माण 2 से अधिक तारों को संभालने के लिए किया गया था, लेकिन इसके अच्छे परिणाम प्राप्त करने के लिए इसे स्ट्रिंग की लंबाई के साथ बहुत खराब कर दिया गया। मुझे अपनी आस्तीन पर कई और तरकीबें मिली हैं, और अगर उनमें से कोई भी वास्तव में काम करता है ... चीजों में बहुत सुधार होना चाहिए।
ब्रेनसैट

कहीं न कहीं कोई समस्या लगती है। @ RetoKoradi का कोड n = 2 के लिए लंबाई 391 का एक वैध आम विकल्प है, जबकि आपका कोड 386 की लंबाई की रिपोर्ट करता है और लंबाई 229 का एक स्ट्रिंग प्रिंट करता है।
डेनिस

@ डेनिस उम्म ... हाँ, हाँ, यह करता है ... ओह प्रिय। खैर, यह शर्मनाक है। मैं इस पर काम कर रहा हूं :) बग को प्रतिबिंबित करने के लिए उत्तर को संपादित करूंगा।
ब्रेनसैट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.