Kennen Sie eine Sequenz durch ihre Subsequenzen


18

Einführung

Angenommen, Sie und Ihr Freund spielen ein Spiel. Ihr Freund denkt an eine bestimmte Abfolge von nBits, und Ihre Aufgabe ist es, die Abfolge abzuleiten, indem Sie ihm Fragen stellen. Die einzige Art von Frage, die Sie stellen dürfen, ist "Wie lang ist die längste gemeinsame Teilfolge Ihrer Sequenz und S", wobei Ses sich um eine beliebige Folge von Bits handelt. Je weniger Fragen Sie benötigen, desto besser.

Die Aufgabe

Ihre Aufgabe ist es, ein Programm oder eine Funktion zu schreiben, die eine positive ganze Zahl nund eine binäre Folge Rvon Längen als Eingabe verwendet n. Die Sequenz kann ein Array von Ganzzahlen, eine Zeichenfolge oder ein anderer sinnvoller Typ Ihrer Wahl sein. Ihr Programm soll die Sequenz ausgeben R.

Ihr Programm darf nichtR direkt auf die Sequenz zugreifen . Das einzige, was es tun darf, Rist, es als Eingabe für die Funktion len_lcszusammen mit einer anderen Binärsequenz zu geben S. Die Funktion len_lcs(R, S)gibt die Länge der längsten gemeinsamen Teilsequenz von Rund zurück S. Dies bedeutet die längste Folge von Bits, die als (nicht notwendigerweise zusammenhängende) Folge in beiden Rund auftritt S. Die Eingänge len_lcskönnen unterschiedlich lang sein. Das Programm sollte diese Funktion Rund andere Sequenzen einige Male aufrufen und dann die Sequenz Rbasierend auf diesen Informationen rekonstruieren .

Beispiel

Betrachten Sie die Eingänge n = 4und R = "1010". Erstens könnten wir beurteilen len_lcs(R, "110"), was gibt 3, da "110"ist die längste gemeinsame Teilfolge von "1010"und "110". Dann wissen wir , dass Raus erhalten wird "110"durch Einfügen eines Bits an einer bestimmten Position. Als nächstes könnten wir versuchen len_lcs(R, "0110"), was zurückgibt, 3da die längsten gemeinsamen Teilfolgen sind "110"und "010"somit "0110"nicht korrekt ist. Dann versuchen wir len_lcs(R, "1010"), was zurückkehrt 4. Jetzt wissen wir das R == "1010", sodass wir diese Sequenz als die richtige Ausgabe zurückgeben können. Dies erforderte 3 Anrufe zu len_lcs.

Regeln und Wertung

In diesem Repository finden Sie eine Datei mit dem Namen " subsequence_data.txt100 zufällige binäre Sequenzen mit Längen zwischen 75 und 124". Sie wurden generiert, indem drei zufällige Gleitkommazahlen zwischen 0 und 1 verwendet wurden, der Durchschnitt als angegeben awurde und anschließend eine aMünze mit Voreinstellung geworfen nwurde. Ihre Punktzahl ist die durchschnittliche Anzahl der Anrufe inlen_lcs diesen Sequenzen, wobei eine niedrigere Punktzahl besser ist. Ihr Beitrag sollte die Anzahl der Anrufe aufzeichnen. Es gibt keine zeitlichen Beschränkungen, außer dass Sie Ihr Programm für die Datei ausführen sollten, bevor Sie sie senden.

Ihre Vorlage soll deterministisch sein. PRNGs sind zulässig, aber sie müssen das heutige Datum 200116(oder das nächstgelegene Äquivalent) als zufälligen Startwert verwenden. Es ist Ihnen nicht gestattet, Ihre Einreichung für diese speziellen Testfälle zu optimieren. Wenn ich vermute, dass dies geschieht, werde ich einen neuen Stapel generieren.

Dies ist kein Codegolf, daher wird empfohlen, lesbaren Code zu schreiben. Rosetta Code hat eine Seite mit der längsten gemeinsamen Untersequenz . Sie können das verwenden, um es len_lcsin der Sprache Ihrer Wahl zu implementieren .


Tolle Idee! Hat dies irgendwelche Anwendungen?
Fehler

@flawr Ich kenne keine direkten Anwendungen. Die Idee kam von der Theorie der Abfragekomplexität, einem Teilgebiet der Informatik, und das hat eine Menge Anwendungen.
Zgarb

Ich denke, es wäre großartig, wieder die gleiche Herausforderung zu haben, aber wo Sie lcsstattdessen zugreifen können len_lcs.
Fehler

@flawr Das wäre nicht sehr interessant, da lcs(R, "01"*2*n)kehrt R. ;) Aber das könnte funktionieren, wenn der Anruf lcs(R, S)die Punktzahl um len(S)1 erhöhen würde , oder so ähnlich ...
Zgarb

1
Ich würde gerne andere Antworten sehen = S
Fehler

Antworten:


10

Java, 99.04 98.46 97.66 LCS () Anrufe

Wie es funktioniert

Beispiel: Unsere zu rekonstruierende Linie ist 00101. Zuerst ermitteln wir, wie viele Nullen es gibt, indem wir (hier compare = computing lcs with the original string) mit einer Zeichenfolge vergleichen, die nur Nullen enthält 00000. Dann gehen wir jede Position durch, drehen die Taste 0auf a 1und prüfen, ob wir jetzt eine längere gemeinsame Teilzeichenfolge haben. Wenn ja, akzeptiere und gehe zur nächsten Position, wenn nein, drehe die aktuelle 1zurück zu a 0und gehe zur nächsten Position:

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

Optimierungen

Dies ist nur eine "naive" Implementierung. Vielleicht könnte man einen ausgefeilteren Alogrithmus finden, der mehrere Positionen gleichzeitig überprüft. Aber ich bin nicht sicher , ob es wirklich ist ein besseres (zB auf der Grundlage der Berechnung der Parity - Bits ähnlich den Hamming - Code), wie Sie immer nur die beurteilen Länge der gemeinsamen Teilkette .

Für eine bestimmte Ziffernreihe muss dieser Algorithmus genau #ofDigitsUntilTheLastOccurenceOf1 + 1überprüft werden. (Subtrahieren Sie eine, wenn die letzte Ziffer eine ist 1.)

BEARBEITEN: Eine kleine Optimierung: Wenn wir gerade die zweitletzte Ziffer überprüft haben und wir noch eine einfügen müssen 1, wissen wir sicher, dass sie sich an der allerletzten Position befinden muss, und können die entsprechende Prüfung auslassen.

EDIT2: Mir ist gerade aufgefallen, dass Sie die obigen Gedanken auf die letzten anwenden kkönnen.

Es könnte natürlich möglich sein, mit dieser Optimierung eine etwas niedrigere Punktzahl zu erzielen, indem Sie zuerst alle Zeilen neu anordnen, da es sein könnte, dass Sie am Ende mehr Zeilen mit Einsen erhalten, aber das wäre natürlich eine Optimierung für den aktuellen Wert Testfälle, die nicht mehr lustig sind.

Laufzeit

Die Obergrenze liegt bei O(#NumberOfBits).

Vollständiger Code

Hier der vollständige Code:

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
Da Sie bei nachgestellten Einsen weniger Anrufe erhalten, könnten Sie im Durchschnitt besser abschneiden, wenn Sie nach der ersten Schätzung mehr Nullen als Einsen haben und auf die Jagd nach 0 Positionen anstatt nach 1 Positionen wechseln. Sie könnten das sogar mehrmals tun.
Histokrat

1
@histocrat Ich glaube, er hört schon auf, wenn er den letzten verbraucht hat 1, was bedeutet, dass nur noch Nullen übrig sind.
Martin Ender
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.