Conosci una sequenza per le sue sottosequenze


18

introduzione

Supponiamo che tu e il tuo amico stiate giocando. Il tuo amico pensa a una particolare sequenza di nbit e il tuo compito è quello di dedurre la sequenza ponendo loro delle domande. Tuttavia, l'unico tipo di domanda che ti è consentito porre è "Quanto tempo è la sottosequenza comune più lunga della sequenza e S", dove si Strova una sequenza di bit. Meno domande hai bisogno, meglio è.

L'obiettivo

Il tuo compito è scrivere un programma o una funzione che accetta come input un numero intero positivo ne una sequenza binaria Rdi lunghezza n. La sequenza può essere una matrice di numeri interi, una stringa o qualche altro tipo ragionevole di tua scelta. Il programma deve generare la sequenza R.

Il tuo programma non è autorizzato ad accedere Rdirettamente alla sequenza . L' unica cosa che è permesso fare Rè di dargli come input per la funzione len_lcsinsieme a un'altra sequenza binaria S. La funzione len_lcs(R, S)restituisce la lunghezza della sottosequenza comune più lunga di Re S. Ciò significa che la sequenza di bit più lunga che si presenta come una sottosequenza (non necessariamente contigua) in entrambi Re S. I cui input len_lcspossono avere lunghezze diverse. Il programma dovrebbe richiamare questa funzione Re altre sequenze un numero di volte, quindi ricostruire la sequenza Rsulla base di tali informazioni.

Esempio

Considera gli input n = 4e R = "1010". Innanzitutto, potremmo valutare len_lcs(R, "110"), il che dà 3, poiché "110"è la sottosequenza comune più lunga di "1010"e "110". Quindi sappiamo che Rsi ottiene "110"inserendo un bit in una certa posizione. Quindi, potremmo provare len_lcs(R, "0110"), che ritorna 3poiché le sottosezioni comuni più lunghe sono "110"e "010", quindi "0110"non è corretto. Quindi proviamo len_lcs(R, "1010"), che ritorna 4. Ora lo sappiamo R == "1010", quindi possiamo restituire quella sequenza come output corretto. Ciò ha richiesto 3 chiamate a len_lcs.

Regole e punteggio

In questo repository , troverai un file chiamato subsequence_data.txtcontenente 100 sequenze binarie casuali di lunghezze comprese tra 75 e 124. Sono state generate prendendo tre float casuali tra 0 e 1, prendendo la loro media come ae poi lanciando un atempo di moneta parziale n. Il tuo punteggio è il numero medio di chiamate alen_lcs queste sequenze, il punteggio più basso è migliore. Il tuo invio dovrebbe registrare il numero di chiamate. Non ci sono limiti di tempo, tranne che è necessario eseguire il programma sul file prima di inviarlo.

La tua richiesta deve essere deterministica. I PRNG sono consentiti, ma devono utilizzare la data odierna 200116(o l'equivalente più vicino) come seme casuale. Non ti è consentito ottimizzare la tua presentazione rispetto a questi casi di test particolari. Se sospetto che ciò accada, genererò un nuovo batch.

Questo non è un codice golf, quindi sei incoraggiato a scrivere un codice leggibile. Il codice Rosetta ha una pagina sulla sottosequenza comune più lunga ; puoi usarlo per implementare len_lcsnella tua lingua preferita.


Bella idea! Questo ha qualche applicazione?
flawr

@flawr Non conosco alcuna applicazione diretta. L'idea è nata dalla teoria della complessità delle query , un sottocampo dell'informatica e che ha molte applicazioni.
Zgarb,

Penso che sarebbe bello avere di nuovo la stessa sfida, ma a cui puoi accedere lcsinvece di len_lcs.
flawr

@flawr Non sarebbe molto interessante, dal momento che lcs(R, "01"*2*n)ritorna R. ;) Ma potrebbe funzionare se la chiamata lcs(R, S)aumentasse il punteggio len(S)invece di 1, o qualcosa del genere ...
Zgarb

1
Mi piacerebbe vedere altre risposte = S
flawr

Risposte:


10

Chiamate Java, 99.04 98.46 97.66 lcs ()

Come funziona

Esempio: la nostra linea da ricostruire è 00101. Innanzitutto scopriamo quanti zeri ci sono, confrontando (qui confrontando = calcolando lcs con la stringa originale) con una stringa solo zeri 00000. Quindi esaminiamo ciascuna posizione, capovolgiamo 0a a 1e controlliamo se ora abbiamo una sottostringa comune più lunga. In caso affermativo, accetta e vai alla posizione successiva, in caso contrario, capovolgi la corrente corrente 1a 0e vai alla posizione successiva:

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

ottimizzazioni

Questa è solo un'implementazione "ingenua", forse sarebbe possibile trovare un algoritmo più sofisticato che controlli più posizioni contemporaneamente. Ma io non sono sicuro se c'è davvero è uno migliore (per esempio, basato sul calcolo di bit di parità simile al codice di Hamming), come si può sempre e solo valutare la lunghezza della sottostringa comune.

Per una determinata riga di cifre, questo algoritmo richiede #ofDigitsUntilTheLastOccurenceOf1 + 1controlli esatti . (Sottrai uno se l'ultima cifra è una 1.)

EDIT: una piccola ottimizzazione: se abbiamo appena controllato la penultima cifra e dobbiamo ancora inserire un 1, sappiamo per certo che deve essere nell'ultima posizione e possiamo omettere il controllo corrispondente.

EDIT2: Ho appena notato che puoi applicare sopra l'idea agli ultimi k.

Ovviamente potrebbe essere possibile ottenere un punteggio leggermente più basso con questa ottimizzazione, riordinando prima tutte le linee, perché potrebbe essere che si ottengano più linee con quelle alla fine ma ciò sarebbe ovviamente e ottimizzazione per la corrente casi di test che non sono più divertenti.

Runtime

Il limite superiore è O(#NumberOfBits).

Codice completo

Ecco il codice completo:

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
Dato che ricevi meno chiamate quando ci sono 1 in coda, sembra che potresti fare meglio in media se, dopo la prima ipotesi ti dice che ci sono più 0 che 1 passi alla ricerca di 0 posizioni anziché 1 posizioni. Potresti anche farlo più volte.
istocratico,

1
@histocrat Penso che si sia già fermato una volta esaurito l'ultimo 1, il che equivale a lasciare solo zero.
Martin Ender,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.