ค้นหาการจับคู่ทั้งหมด แต่หนึ่ง


18

ความท้าทายนี้เกี่ยวกับการเขียนรหัสเพื่อแก้ไขปัญหาต่อไปนี้

รับสองสาย A และ B รหัสของคุณควรส่งออกดัชนีเริ่มต้นและสิ้นสุดของสตริงย่อยของ A กับคุณสมบัติดังต่อไปนี้

  • สตริงย่อยของ A ควรจับคู่สตริงย่อยของ B ด้วยการแทนที่อักขระเดี่ยวในสตริงได้สูงสุดหนึ่งรายการ
  • ไม่ควรมีสตริงย่อยของ A ที่สอดคล้องกับคุณสมบัติแรกอีกต่อไป

ตัวอย่างเช่น:

A = xxxappleyyyyyyy

B = zapllezzz

สตริงย่อยที่appleมีดัชนี4 8(การทำดัชนีจาก 1) จะเป็นเอาต์พุตที่ถูกต้อง

คะแนน

คะแนนคำตอบของคุณจะเป็นผลรวมของความยาวของรหัสของคุณเป็นไบต์ + เวลาเป็นวินาทีในคอมพิวเตอร์ของฉันเมื่อทำงานกับสตริง A และ B แต่ละความยาว 1 ล้าน

การทดสอบและการป้อนข้อมูล

ฉันจะเรียกใช้รหัสของคุณในสองสายยาว 1 ล้านนำมาจากสายใน http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/

อินพุตจะเป็นแบบมาตรฐานและจะเป็นสองสตริงโดยคั่นด้วยบรรทัดใหม่

ภาษาและห้องสมุด

คุณสามารถใช้ภาษาใดก็ได้ที่มีคอมไพเลอร์ / ล่าม / อื่น ๆ สำหรับ Linux และไลบรารี่ใด ๆ ที่เป็นโอเพ่นซอร์สและพร้อมใช้งานอย่างอิสระสำหรับ Linux

เครื่องของฉัน การจับเวลาจะทำงานบนเครื่องของฉัน นี่คือการติดตั้ง Ubuntu มาตรฐานบนโปรเซสเซอร์ AMD FX-8350 Eight-Core นี่ก็หมายความว่าฉันต้องสามารถเรียกใช้รหัสของคุณได้ ดังนั้นโปรดใช้ซอฟต์แวร์ฟรีที่มีให้ง่ายและโปรดรวมคำแนะนำทั้งหมดในการรวบรวมและเรียกใช้รหัสของคุณ


คุณต้องการคำนิยามการให้คะแนนที่แน่นอนมากขึ้น ใช้เวลากับคอมพิวเตอร์ของคุณไม่ได้เสียงเหมือนวิธีการให้คะแนนที่ดี
mbomb007

7
@ mbomb007 เป็นวิธีเดียวที่สมเหตุสมผลในการวัดความเร็วโค้ดและเป็นวิธีที่ใช้ในการแข่งขันรหัสที่เร็วที่สุดบน PPCG เสมอ! โดยปกติผู้คนโพสต์คะแนนของพวกเขาบนคอมพิวเตอร์ของตัวเองในคำตอบและรอ OP จากนั้นสร้างคะแนนที่ชัดเจน อย่างน้อย 100% ไม่คลุมเครือ

5
@ mbomb007 นั้นเป็นวิธีการให้คะแนนที่ใช้กันอย่างแพร่หลายสำหรับรหัสที่เร็วที่สุด
เครื่องมือเพิ่มประสิทธิภาพ

if(hash(str1 == test1 && str2 == test2)) print("100,150") else ..- ความคิด?
John Dvorak

2
@FryAmTheEggman ในกรณีที่ไม่เสมอกันมากคำตอบแรกจะชนะ ต้องการสองแทนจะมีการแข่งขันappley apllezบางทีคุณอาจพลาดว่ามันอยู่apllใน B และไม่ใช่applเหรอ?

คำตอบ:


4

เวลา C ++: O (n ^ 2), พื้นที่พิเศษ: O (1)

ใช้เวลา 0.2 วินาทีในการกรอกข้อมูล 15K บนเครื่องของฉัน

เพื่อรวบรวมมันใช้:

g++ -std=c++11 -O3 code.cpp -o code

หากต้องการเรียกใช้ให้ใช้:

./code < INPUT_FILE_THAT_CONTAINS_TWO_LINES_SPERATED_BY_A_LINE_BREAK

ชี้แจง

แนวคิดนี้ง่ายสำหรับสตริงs1และs2เราพยายามชดเชยs2ด้วยi:

s1: abcabcabc
s2: bcabcab

เมื่อชดเชยเป็น 3:

s1: abcabcabc
s2:    bcabcab

จากนั้นแต่ละชดเชยiเราทำการสแกนแบบไดนามิกการเขียนโปรแกรมบนและs1[i:] s2สำหรับแต่ละjให้f[j, 0]มีความยาวสูงสุดดังกล่าวว่าd s1[j - d:j] == s2[j - i - d: j - i]ในทำนองเดียวกันให้f[j, 1]เป็นความยาวสูงสุดdเช่นที่สตริงs1[j - d:j]และs2[j - i - d:j - i]แตกต่างกันได้ไม่เกิน 1 ตัว

ดังนั้นสำหรับ s1[j] == s2[j - i]เรา:

f[j, 0] = f[j - 1, 0] + 1  // concat solution in f[j - 1, 0] and s1[j]
f[j, 1] = f[j - 1, 1] + 1  // concat solution in f[j - 1, 1] and s1[j]

มิฉะนั้น:

f[j, 0] = 0  // the only choice is empty string
f[j, 1] = f[j - 1, 0] + 1  // concat solution in f[j - 1, 0] and s1[j] (or s2[j - i])

และ:

f[-1, 0] = f[-1, 1] = 0 

เนื่องจากเราต้องการเพียง f [j - 1,:] เพื่อคำนวณ f [j,:] จึงมีการใช้พื้นที่พิเศษ O (1) เท่านั้น

ในที่สุดความยาวสูงสุดจะเป็น:

max(f[j, 1] for all valid j and all i)

รหัส

#include <string>
#include <cassert>
#include <iostream>

using namespace std;

int main() {
    string s1, s2;
    getline(cin, s1);
    getline(cin, s2);
    int n1, n2;
    n1 = s1.size();
    n2 = s2.size();
    int max_len = 0;
    int max_end = -1;
    for(int i = 1 - n2; i < n1; i++) {
        int f0, f1;
        int max_len2 = 0;
        int max_end2 = -1;
        f0 = f1 = 0;
        for(int j = max(i, 0), j_end = min(n1, i + n2); j < j_end; j++) {
            if(s1[j] == s2[j - i]) {
                f0 += 1;
                f1 += 1;
            } else {
                f1 = f0 + 1;
                f0 = 0;
            }
            if(f1 > max_len2) {
                max_len2 = f1;
                max_end2 = j + 1;
            }
        }
        if(max_len2 > max_len) {
            max_len = max_len2;
            max_end = max_end2;
        }
    }
    assert(max_end != -1);
    // cout << max_len << endl;
    cout << max_end - max_len + 1 << " " << max_end << endl;
}

ขออภัยฉันกำลังดูรหัสและฉันไม่สามารถหาวิธีที่คำนึงถึงความเป็นไปได้ของการจับคู่สตริงยกเว้นอักขระหนึ่งตัวเช่นในตัวอย่าง "apple" และ "aplle" คุณช่วยอธิบายได้ไหม
rorlork

@rcrmn นั่นคือสิ่งที่ส่วนการวางโปรแกรมแบบไดนามิกกำลังทำอยู่ เพื่อให้เข้าใจได้มีประโยชน์ที่จะลองคำนวณ f [j, 0] และ f [j, 1] ด้วยตนเองสำหรับกรณีง่าย ๆ รหัสก่อนหน้ามีข้อบกพร่องบางอย่างดังนั้นฉันจึงปรับปรุงการโพสต์
Ray

ขอบคุณสำหรับสิ่งนี้. คุณคิดว่าอาจมีวิธีการแก้ปัญหา O (n log n) ด้วยหรือไม่

2

C ++

ฉันพยายามคิดอัลกอริธึมที่ดีในการทำสิ่งนี้ แต่วันนี้ฉันรู้สึกว้าวุ่นใจและไม่สามารถคิดอะไรที่จะทำงานได้ดี สิ่งนี้จะทำงานในเวลา O (n ^ 3) ดังนั้นมันจึงช้าลง ตัวเลือกอื่นที่ฉันคิดว่าน่าจะเร็วกว่าในทางทฤษฎี แต่จะต้องใช้พื้นที่ O (n ^ 2) และด้วยอินพุต 1M มันจะแย่กว่านี้ด้วยซ้ำ

เป็นเรื่องน่าละอายใจใช้เวลา 190 วินาทีสำหรับอินพุต 15K ฉันจะพยายามปรับปรุง แก้ไข:เพิ่มมัลติโปรเซสเซอร์ ตอนนี้ใช้เวลา 37 วินาทีสำหรับอินพุต 15K บน 8 เธรด

#include <string>
#include <vector>
#include <sstream>
#include <chrono>
#include <thread>
#include <atomic>
#undef cin
#undef cout
#include <iostream>

using namespace std;

typedef pair<int, int> range;

int main(int argc, char ** argv)
{
    string a = "xxxappleyyyyyyy";
    string b = "zapllezzz";

    getline(cin, a);
    getline(cin, b);

    range longestA;
    range longestB;

    using namespace std::chrono;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();

    unsigned cores = thread::hardware_concurrency(); cores = cores > 0 ? cores : 1;

    cout << "Processing on " << cores << " cores." << endl;

    atomic<int> processedCount(0);

    vector<thread> threads;

    range* longestAs = new range[cores];
    range* longestBs = new range[cores];
    for (int t = 0; t < cores; ++t)
    {
        threads.push_back(thread([&processedCount, cores, t, &a, &b, &longestBs, &longestAs]()
        {
            int la = a.length();
            int l = la / cores + (t==cores-1? la % cores : 0);
            int lb = b.length();
            int aS = t*(la/cores);

            for (int i = aS; i < aS + l; ++i)
            {
                int count = processedCount.fetch_add(1);
                if ((count+1) * 100 / la > count * 100 / la)
                {
                    cout << (count+1) * 100 / la << "%" << endl;
                }
                for (int j = 0; j < lb; ++j)
                {
                    range currentB = make_pair(j, j);
                    bool letterChanged = false;
                    for (int k = 0; k + j < lb && k + i < la; ++k)
                    {
                        if (a[i + k] == b[j + k])
                        {
                            currentB = make_pair(j, j + k);
                        }
                        else if (!letterChanged)
                        {
                            letterChanged = true;
                            currentB = make_pair(j, j + k);
                        }
                        else
                        {
                            break;
                        }
                    }
                    if (currentB.second - currentB.first > longestBs[t].second - longestBs[t].first)
                    {
                        longestBs[t] = currentB;
                        longestAs[t] = make_pair(i, i + currentB.second - currentB.first);
                    }
                }
            }
        }));
    }

    longestA = make_pair(0,0);
    for(int t = 0; t < cores; ++t)
    {
        threads[t].join();

        if (longestAs[t].second - longestAs[t].first > longestA.second - longestA.first)
        {
            longestA = longestAs[t];
            longestB = longestBs[t];
        }
    }

    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    duration<double> time_span = duration_cast<duration<double>>(t2 - t1);

    cout << "First substring at range (" << longestA.first << ", " << longestA.second << "):" << endl;
    cout << a.substr(longestA.first, longestA.second - longestA.first + 1) << endl;
    cout << "Second substring at range (" << longestB.first << ", " << longestB.second << "):" << endl;
    cout << b.substr(longestB.first, longestB.second - longestB.first + 1) << endl;
    cout << "It took me " << time_span.count() << " seconds for input lengths " << a.length() << " and " << b.length() <<"." << endl;

    char c;
    cin >> c;
    return 0;
}

ฉันขอโทษจริง ๆ ที่มันเป็นทางออกที่ไม่ดี ฉันได้รับการค้นหาสำหรับขั้นตอนวิธีการที่จะบรรลุในเวลานี้ดีขึ้น แต่ไม่พบอะไรตอนนี้ ...
rorlork

ความซับซ้อนของงานที่ต้องการควรอยู่ที่ประมาณ O (n ^ 4) ถึง O (n ^ 5) ดังนั้นเวลาทำงานที่ยาวนานจึงมอบให้
hoffmale

ฉันเชื่อว่ามันควรจะเป็นเหมือน O (n ^ 3) ในกรณีที่แย่ที่สุดอย่างน้อยก็ด้วยอัลกอริธึมของฉัน อย่างไรก็ตามฉันแน่ใจว่ามีบางสิ่งที่สามารถปรับปรุงได้เช่นเดียวกับการค้นหาต้นไม้ แต่ฉันไม่แน่ใจว่าจะนำไปใช้อย่างไร
rorlork

Oh yeah, O (n ^ 3) มันคือ ... มีวิธีการที่แตกต่างกันในใจที่จะได้รับ O (n ^ 4) แต่ที่ไม่มีประโยชน์โดยตอนนี้ xD
hoffmale

คุณสามารถประหยัดเวลาเล็กน้อยหากคุณเปลี่ยนการตรวจสอบในสองนอกสำหรับลูปจากi < a.length()เป็นi < a.length - (longestA.second - longestA.first)(เหมือนกันสำหรับ j และ b.length ()) เนื่องจากคุณไม่จำเป็นต้องประมวลผลการแข่งขันใด ๆ ที่เล็กกว่าอันที่ยาวที่สุดในปัจจุบันของคุณ
hoffmale

2

R

ดูเหมือนว่าฉันกำลังสับสนปัญหากับวิธีแก้ไขปัญหาก่อนหน้านี้ นี่คือเร็วกว่าประมาณ 50% (23 วินาทีในสตริง 15k) กว่าก่อนหน้านี้และค่อนข้างง่าย

rm(list=ls(all=TRUE))
a="xxxappleyyyyyyy"
b="zapllezzz"
s=proc.time()
matchLen=1
matchIndex=1
indexA = 1
repeat {    
    i = 0
    repeat {
        srch = substring(a,indexA,indexA+matchLen+i)
        if (agrepl(srch,b,max.distance=list(insertions=0,deletions=0,substitutions=1)))
            i = i + 1
        else {
            if (i > 0) {
                matchLen = matchLen + i - 1
                matchIndex = indexA
            }
            break
        }
    }
    indexA=indexA+1
    if (indexA + matchLen > nchar(a)) break
}
c(matchIndex, matchLen + matchIndex)
print (substring(a,matchIndex, matchLen + matchIndex))
print(proc.time()-s)

สิ่งนี้จะไม่เป็นคู่แข่งเนื่องจากภาษา แต่ฉันสนุกกับการทำมันมาก
ไม่แน่ใจเกี่ยวกับความซับซ้อนของมัน แต่มีมากกว่าสอง ~ 15k สายมันใช้เวลา 43 วินาทีโดยใช้เธรดเดียว ส่วนที่ใหญ่ที่สุดคือการเรียงลำดับของอาร์เรย์ ฉันลองห้องสมุดอื่น แต่ไม่มีการปรับปรุงที่สำคัญ

a="xxxappleyyyyyyy"
b="zapllezzz"
s=proc.time()
N=nchar
S=substring
U=unlist
V=strsplit
A=N(a)
B=N(b)
a=S(a,1:A)
b=S(b,1:B)
a=sort(a,method="quick")
b=sort(b,method="quick")
print(proc.time()-s)
C=D=1
E=X=Y=I=0
repeat{
    if(N(a[C])>E && N(b[D])>E){
        for(i in E:min(N(a[C]),N(b[D]))){
            if (sum(U(V(S(a[C],1,i),''))==U(V(S(b[D],1,i),'')))>i-2){
                F=i
            } else break
        }
        if (F>E) {
            X=A-N(a[C])+1
            Y=X+F-1
            E=F
        }
        if (a[C]<b[D])
            C=C+1
            else
            D=D+1
    } else
        if(S(a[C],1,1)<S(b[D],1,1))C=C+1 else D=D+1
    if(C>A||D>B)break
}
c(X,Y)
print(proc.time()-s)

วิธี:

  • สร้างอาร์เรย์ต่อท้ายสำหรับแต่ละสตริง
  • สั่งซื้ออาร์เรย์ต่อท้าย
  • ก้าวผ่านแต่ละอาร์เรย์ในลักษณะที่ถูกเปรียบเทียบกันระหว่างจุดเริ่มต้นของแต่ละอาร์เรย์

แน่นอนทางออกที่ง่ายที่สุดใน R คือการใช้ Bioconductor
archaephyrryx

@archaephyrryx วิธีแก้ปัญหาทางชีวภาพจะสนุก

มันจะเป็น ... แต่การอ่านเอกสารอย่างรวดเร็วของฉันเป็นไปในหัวของฉัน บางทีถ้าฉันเข้าใจเงื่อนไข :-)
MickyT

ฉันลบความคิดเห็นแรกของฉันแล้ว แน่นอนคุณสามารถใช้ไลบรารีโอเพนซอร์สที่คุณชอบสำหรับความท้าทายนี้
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.