サブシーケンスでシーケンスを知る


18

前書き

あなたとあなたの友人がゲームをプレイしているとします。あなたの友人はいくつかの特定のシーケンスを考えていますnビットのおり、あなたの仕事は彼らに質問することによってシーケンスを推測することです。ただし、質問できる唯一のタイプの質問は、S「シーケンスの最も長い共通サブシーケンスの長さ」と、Sビットのシーケンスです。必要な質問が少ないほど良い。

タスク

あなたの仕事は、入力として正の整数nR長さのバイナリシーケンスを取るプログラムまたは関数を書くことですnです。シーケンスは、整数の配列、文字列、または選択した他の合理的なタイプです。プログラムはシーケンスを出力しますR

プログラムがシーケンスに直接アクセスすること許可されていませんR唯一行うことが許可されています事は、R関数への入力としてそれを与えるためにあるlen_lcs別のバイナリ・シーケンスと一緒にS。関数は、len_lcs(R, S)の最長共通サブシーケンスの長さを返すRS。これは、両方に(必ずしも連続していない)サブシーケンスとして生じるビットの最長の配列を意味Rし、S。入力のlen_lcs長さは異なる場合があります。プログラムは、この関数Rと他のシーケンスを何度か呼び出してから、Rその情報に基づいてシーケンスを再構築する必要があります。

入力n = 4とを考慮してくださいR = "1010"。まず、我々は評価するかもしれないlen_lcs(R, "110")与える、3以来、"110"の最長共通部分列である"1010""110"。それRから"110"、ある位置に1ビットを挿入することで得られることがわかります。次に、を試してみることができます。これは、最も長い共通サブシーケンスがとであるため、正しくないためにlen_lcs(R, "0110")戻ります。それから、戻ります。今、私たちはそれを知っています3"110""010""0110"len_lcs(R, "1010")4R == "1010"ので、正しい出力としてそのシーケンスを返すことができます。これには、への3つの呼び出しが必要でしたlen_lcs

ルールとスコアリング

ではこのリポジトリは、ファイルと呼ばれる見つけることができますsubsequence_data.txt彼らは、0と1の間の3つのランダムな浮動小数点数を取って彼らの平均をとることによって生成された75と124の間の長さの100のランダムなバイナリ配列を含むa、その後、フリップa-biasedコインn回。スコアは、これらのシーケンスに対する平均呼び出し回数でありlen_lcs、スコアが低いほど優れています。提出には、呼び出し回数を記録する必要があります。時間制限はありませんが、送信する前にファイルでプログラムを実行する必要があります。

あなたの提出は決定論的です。PRNGは許可されますが200116、ランダムシードとして今日の日付(またはそれに最も近い日付)を使用する必要があります。これらの特定のテストケースに対して提出を最適化することはできません。これが発生していると思われる場合は、新しいバッチを生成します。

これはコードゴルフではありませんので、読みやすいコードを書くことをお勧めします。Rosetta Codeには、最も長い共通サブシーケンスに関するページがあります。これを使用してlen_lcs、選択した言語で実装できます。


クールなアイデア!これにはアプリケーションがありますか?
フレイ

@flawr直接的なアプリケーションは知りません。このアイデアは、コンピューターサイエンスのサブフィールドであるクエリ複雑度理論から生まれました。
Zgarb

同じチャレンジを再び受けることは素晴らしいことだと思いますが、lcs代わりにアクセスできますlen_lcs
-flawr

@flawrがlcs(R, "01"*2*n)返されるので、それはあまり面白くないでしょうR。;)しかし、呼び出しlcs(R, S)len(S)1の代わりにスコアを上げるか、またはそのようなものであれば
...-Zgarb

1
私は他の答えを見てみたい= S
flawr

回答:


10

Java、99.04 98.46 97.66 LCS()の呼び出し

使い方

例:再構築されるラインは00101です。最初に、ゼロのみのstringと比較することにより、ゼロの数を調べます(ここでは、=オリジナルの文字列とlcsを計算する)00000。次に、各位置を確認0し、aに1切り替えて、共通部分文字列が長くなったかどうかを確認します。はいの場合、受け入れて次の位置に移動し、いいえの場合、現在の位置1をaに戻し、0次の位置に移動します。

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

最適化

これは単なる「単純な」実装です。おそらく、複数の位置を一度にチェックするより洗練されたアルゴリズムを見つけることができるでしょう。しかし、本当にあるかどうかはわかりませんあなたは常にだけ評価できるように、(例えばハミングコードに似たパリティビットの計算に基づいて)、より良い1 の長さを、共通部分文字列のを。

与えられた1行の数字に対して、このアルゴリズムは正確に必要です #ofDigitsUntilTheLastOccurenceOf1 + 1チェックます。(最後の桁がの場合は、1を減算し1ます。)

編集:1つの小さな最適化:最後の2番目の数字をチェックしただけで、まだ挿入する必要がある場合 1必要がある場合、それが最後の位置にあることが確実にわかり、対応するチェックを省略できます。

EDIT2:最後に上記のアイデアを適用できることに気づいた k

もちろん、この最適化では、最初にすべての行を並べ替えることにより、わずかに低いスコアを達成することが可能かもしれません。もはや面白くないテストケース。

ランタイム

上限はO(#NumberOfBits)です。

完全なコード

ここに完全なコード:

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
後続の1があるとコールが少なくなるので、最初の推測で1よりも0が多いと示された後、1ポジションではなく0ポジションのハンティングに切り替えると、平均して改善できるようです。それを複数回行うことさえできます。
-histocrat

1
@histocrat最後のを使い切ると、彼はすでに停止していると思います1。これは、ゼロだけが残っているのと同じです。
マーティンエンダー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.