Biết một chuỗi bởi các chuỗi của nó


18

Giới thiệu

Giả sử bạn và bạn của bạn đang chơi một trò chơi. Bạn của bạn nghĩ về một số chuỗi nbit cụ thể và nhiệm vụ của bạn là suy ra chuỗi bằng cách đặt câu hỏi cho họ. Tuy nhiên, loại câu hỏi duy nhất bạn được phép hỏi là "Phần tiếp theo phổ biến dài nhất của chuỗi của bạn là bao lâu S", trong đó Scó bất kỳ chuỗi bit nào. Càng ít câu hỏi bạn cần, càng tốt.

Nhiệm vụ

Nhiệm vụ của bạn là viết một chương trình hoặc hàm lấy đầu vào là một số nguyên dương nvà một chuỗi Rđộ dài nhị phân n. Chuỗi có thể là một mảng các số nguyên, một chuỗi hoặc một số loại hợp lý khác mà bạn chọn. Chương trình của bạn sẽ xuất trình tự R.

Chương trình của bạn không được phép truy cập chuỗi Rtrực tiếp. Điều duy nhất nó được phép làm Rlà cung cấp nó làm đầu vào cho hàm len_lcscùng với một chuỗi nhị phân khác S. Hàm len_lcs(R, S)trả về độ dài của chuỗi con chung dài nhất RS. Điều này có nghĩa là chuỗi bit dài nhất xảy ra như một chuỗi con (không nhất thiết phải liền kề) trong cả hai RS. Các đầu vào trong len_lcsđó có thể có độ dài khác nhau. Chương trình nên gọi hàm này Rvà các chuỗi khác một số lần, và sau đó xây dựng lại chuỗi Rdựa trên thông tin đó.

Thí dụ

Hãy xem xét các đầu vào n = 4R = "1010". Đầu tiên, chúng tôi có thể đánh giá len_lcs(R, "110"), đưa ra 3, vì đây "110"là chuỗi con chung dài nhất của "1010""110". Sau đó, chúng ta biết rằng Rcó được từ "110"bằng cách chèn một bit tại một số vị trí. Tiếp theo, chúng tôi có thể thử len_lcs(R, "0110"), trả về 3vì các chuỗi chung dài nhất là "110""010", vì vậy "0110"không chính xác. Sau đó, chúng tôi cố gắng len_lcs(R, "1010"), mà trở lại 4. Bây giờ chúng ta biết điều đó R == "1010", vì vậy chúng ta có thể trả về chuỗi đó là đầu ra chính xác. Điều này yêu cầu 3 cuộc gọi đến len_lcs.

Quy tắc và tính điểm

Trong kho lưu trữ này , bạn sẽ tìm thấy một tệp subsequence_data.txtcó chứa 100 chuỗi nhị phân ngẫu nhiên có độ dài trong khoảng từ 75 đến 124. Chúng được tạo bằng cách lấy ba lần thả ngẫu nhiên trong khoảng từ 0 đến 1, lấy trung bình của chúng a, sau đó lật một lần axu xu hướng n. Điểm số của bạn là số cuộc gọi trung bìnhlen_lcs trên các chuỗi này, điểm thấp hơn sẽ tốt hơn. Trình của bạn nên ghi lại số lượng cuộc gọi. Không có giới hạn thời gian, ngoại trừ việc bạn nên chạy chương trình của mình trên tệp trước khi gửi.

Trình của bạn sẽ được xác định. PRNG được phép, nhưng họ phải sử dụng ngày hôm nay, 200116(hoặc tương đương gần nhất), làm hạt giống ngẫu nhiên. Bạn không được phép tối ưu hóa trình của mình đối với các trường hợp thử nghiệm cụ thể này. Nếu tôi nghi ngờ điều này xảy ra, tôi sẽ tạo ra một đợt mới.

Đây không phải là mã golf, vì vậy bạn được khuyến khích viết mã có thể đọc được. Rosetta Code có một trang về chuỗi con chung dài nhất ; bạn có thể sử dụng nó để thực hiện len_lcstrong ngôn ngữ của bạn lựa chọn.


Ý tưởng tuyệt vời! Cái này có ứng dụng nào không?
flawr

@flawr Tôi không biết bất kỳ ứng dụng trực tiếp nào. Ý tưởng xuất phát từ lý thuyết phức tạp truy vấn , một lĩnh vực của khoa học máy tính và có vô số ứng dụng.
Zgarb

Tôi nghĩ sẽ rất tuyệt nếu có lại thử thách tương tự nhưng nơi bạn có thể truy cập lcsthay vì len_lcs.
flawr

@flawr Điều đó sẽ không thú vị lắm, kể từ khi lcs(R, "01"*2*n)trở về R. ;) Nhưng điều đó có thể hoạt động nếu gọi lcs(R, S)sẽ tăng điểm len(S)thay vì 1, hoặc đại loại như thế ...
Zgarb

1
Tôi rất thích xem các câu trả lời khác = S
flawr

Câu trả lời:


10

Java, 99,04 98,46 97,66 LCS () cuộc gọi

Làm thế nào nó hoạt động

Exaple: Dòng của chúng tôi sẽ được xây dựng lại là 00101. Đầu tiên chúng ta tìm ra có bao nhiêu số không, bằng cách so sánh (ở đây so sánh = tính toán lcs với chuỗi gốc) bằng một chuỗi số không 00000. Sau đó, chúng tôi đi qua từng vị trí, lật 0sang a 1và kiểm tra xem bây giờ chúng tôi có chuỗi con chung dài hơn không. Nếu có, chấp nhận và đi đến vị trí tiếp theo, nếu không, lật ngược dòng điện 1trở lại a 0và chuyển sang vị trí tiếp theo:

For our example of "00101" we get following steps:
input  lcs  prev.'best'
00000  3    0           //number of zeros
̲10000  3    3           //reject
0̲1000  3    3           //reject
00̲100  4    3           //accept
001̲10  4    4           //reject
0010̲1  5    4           //accept

Tối ưu hóa

Đây chỉ là một triển khai "ngây thơ", có lẽ có thể tìm thấy một thuật toán phức tạp hơn kiểm tra nhiều vị trí cùng một lúc. Nhưng tôi không chắc có thực sự cái nào tốt hơn không (ví dụ dựa trên cách tính các bit chẵn lẻ tương tự mã Hamming), vì bạn luôn có thể đánh giá độ dài của chuỗi con chung.

Đối với một dòng chữ số đã cho, thuật toán này cần #ofDigitsUntilTheLastOccurenceOf1 + 1kiểm tra chính xác . (Trừ một nếu các chữ số cuối là một 1.)

EDIT: Một tối ưu hóa nhỏ: Nếu chúng tôi chỉ kiểm tra chữ số cuối cùng thứ 2 và chúng tôi vẫn cần chèn a 1, chúng tôi biết chắc chắn rằng nó phải ở vị trí cuối cùng và có thể bỏ qua kiểm tra tương ứng.

EDIT2: Tôi chỉ nhận thấy bạn có thể áp dụng ý tưởng trên cho knhững người cuối cùng .

Tất nhiên có thể đạt được điểm thấp hơn một chút với tối ưu hóa này, bằng cách sắp xếp lại tất cả các dòng trước, bởi vì có thể, bạn nhận được nhiều dòng hơn ở cuối nhưng rõ ràng là và tối ưu hóa cho hiện tại trường hợp thử nghiệm mà không còn buồn cười.

Thời gian chạy

Giới hạn trên là O(#NumberOfBits).

Mã đầy đủ

Đây là mã đầy đủ:

package jcodegolf;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

// http://codegolf.stackexchange.com/questions/69799/know-a-sequence-by-its-subsequences

public class SequenceReconstructor { 
    public static int counter = 0;
    public static int lcs(String a, String b) { //stolen from http://rosettacode.org/wiki/Longest_common_subsequence#Java
        int[][] lengths = new int[a.length()+1][b.length()+1];

        // row 0 and column 0 are initialized to 0 already

        for (int i = 0; i < a.length(); i++)
            for (int j = 0; j < b.length(); j++)
                if (a.charAt(i) == b.charAt(j))
                    lengths[i+1][j+1] = lengths[i][j] + 1;
                else
                    lengths[i+1][j+1] =
                        Math.max(lengths[i+1][j], lengths[i][j+1]);

        // read the substring out from the matrix
        StringBuffer sb = new StringBuffer();
        for (int x = a.length(), y = b.length();
             x != 0 && y != 0; ) {
            if (lengths[x][y] == lengths[x-1][y])
                x--;
            else if (lengths[x][y] == lengths[x][y-1])
                y--;
            else {
                assert a.charAt(x-1) == b.charAt(y-1);
                sb.append(a.charAt(x-1));
                x--;
                y--;
            }
        }

        counter ++;
        return sb.reverse().toString().length();
    }


    public static String reconstruct(String secretLine, int lineLength){
        int current_lcs = 0; 
        int previous_lcs = 0;
        char [] myGuess = new char[lineLength];
        for (int k=0; k<lineLength; k++){
            myGuess[k] = '0';
        }

        //find the number of zeros:
        int numberOfZeros = lcs(secretLine, String.valueOf(myGuess));
        current_lcs = numberOfZeros;
        previous_lcs = numberOfZeros;

        if(current_lcs == lineLength){ //were done
            return String.valueOf(myGuess);
        }


        int numberOfOnes = lineLength - numberOfZeros;
        //try to greedily insert ones at the positions where they maximize the common substring length
        int onesCounter = 0;
        for(int n=0; n < lineLength && onesCounter < numberOfOnes; n++){

            myGuess[n] = '1';
            current_lcs = lcs(secretLine, String.valueOf(myGuess));

            if(current_lcs > previous_lcs){ //accept

                previous_lcs = current_lcs;
                onesCounter ++;

            } else { // do not accept
                myGuess[n]='0';     
            }

            if(n == lineLength-(numberOfOnes-onesCounter)-1 && onesCounter < numberOfOnes){ //lets test if we have as many locations left as we have ones to insert
                                                                // then we know that the rest are ones
                for(int k=n+1;k<lineLength;k++){
                    myGuess[k] = '1';
                }
                break;
            }

        }

        return String.valueOf(myGuess);
    }

    public static void main(String[] args) {
        try {

            //read the file
            BufferedReader br;

            br = new BufferedReader(new FileReader("PATH/TO/YOUR/FILE/LOCATION/subsequence_data.txt"));

            String line;

            //iterate over each line
            while ( (line = br.readLine()) != null){

                String r = reconstruct(line, line.length());
                System.out.println(line);     //print original line
                System.out.println(r);        //print current line
                System.out.println(counter/100.0);  //print current number of calls
                if (! line.equals(r)){
                    System.out.println("SOMETHING WENT HORRIBLY WRONG!!!");
                    System.exit(1);
                }

            }


        } catch(Exception e){
            e.printStackTrace();;
        }

    }

}

1
Vì bạn nhận được ít cuộc gọi hơn khi có các số 1 theo dõi, có vẻ như bạn có thể làm tốt hơn trung bình nếu sau lần đoán đầu tiên cho bạn biết có nhiều số 0 hơn số 1 bạn chuyển sang săn tìm 0 vị trí thay vì 1 vị trí. Bạn thậm chí có thể làm điều đó nhiều lần.
lịch sử

1
@histocrat Tôi nghĩ rằng anh ấy đã dừng lại một khi anh ấy sử dụng lần cuối 1, tương đương với việc chỉ còn lại số không.
Martin Ender
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.