Connaître une séquence par ses sous-séquences

18

introduction

Supposons que vous et votre ami jouiez à un jeu. Votre ami pense à une séquence particulière de nbits et votre tâche consiste à en déduire la séquence en lui posant des questions. Cependant, le seul type de question que vous êtes autorisé à poser est "Quelle est la longueur de la sous-séquence commune la plus longue de votre séquence et S", où se Strouve toute séquence de bits. Moins vous avez besoin de questions, mieux c'est.

La tâche

Votre tâche consiste à écrire un programme ou une fonction qui prend en entrée un entier positif net une séquence binaire Rde longueur n. La séquence peut être un tableau d'entiers, une chaîne ou un autre type raisonnable de votre choix. Votre programme doit sortir la séquence R.

Votre programme n'est pas autorisé à accéder Rdirectement à la séquence . La seule chose qu'il est autorisé à faire Rest de le donner comme entrée à la fonction len_lcsavec une autre séquence binaire S. La fonction len_lcs(R, S)renvoie la longueur de la sous-séquence commune la plus longue de Ret S. Cela signifie la séquence de bits la plus longue qui se présente comme une sous-séquence (pas nécessairement contiguë) dans les deux Ret S. Les entrées len_lcspeuvent être de longueurs différentes. Le programme doit appeler cette fonction Ret d'autres séquences un certain nombre de fois, puis reconstruire la séquence en Rfonction de ces informations.

Exemple

Considérez les entrées n = 4et R = "1010". Tout d'abord, nous pourrions évaluer len_lcs(R, "110"), ce qui donne 3, puisque "110"c'est la sous-séquence commune la plus longue de "1010"et "110". Ensuite, nous savons que cela Rest obtenu "110"en insérant un bit à une certaine position. Ensuite, nous pourrions essayer len_lcs(R, "0110"), qui retourne 3puisque les sous-séquences communes les plus longues sont "110"et "010", ce "0110"n'est donc pas correct. Ensuite, nous essayons len_lcs(R, "1010"), qui revient 4. Nous le savons maintenant R == "1010", nous pouvons donc renvoyer cette séquence comme sortie correcte. Cela a nécessité 3 appels vers len_lcs.

Règles et notation

Dans ce référentiel , vous trouverez un fichier appelé subsequence_data.txtcontenant 100 séquences binaires aléatoires de longueurs comprises entre 75 et 124. Elles ont été générées en prenant trois flottants aléatoires entre 0 et 1, en prenant leur moyenne en tant que a, puis en ainversant un temps de pièce biaisé n. Votre score est le nombre moyen d'appels verslen_lcs ces séquences, un score plus bas étant meilleur. Votre soumission doit enregistrer le nombre d'appels. Il n'y a pas de limite de temps, sauf que vous devez exécuter votre programme sur le fichier avant de le soumettre.

Votre soumission sera déterministe. Les PRNG sont autorisés, mais ils doivent utiliser la date du jour 200116(ou l'équivalent le plus proche) comme valeur de départ aléatoire. Vous n'êtes pas autorisé à optimiser votre soumission par rapport à ces cas de test particuliers. Si je soupçonne que cela se produit, je vais générer un nouveau lot.

Ce n'est pas du golf de code, vous êtes donc encouragé à écrire du code lisible. Rosetta Code a une page sur la sous-séquence commune la plus longue ; vous pouvez l'utiliser pour l'implémenter len_lcsdans la langue de votre choix.

Zgarb
la source
Idée sympa! Cela a-t-il des applications?
flawr
@flawr Je ne connais aucune application directe. L'idée est venue de la théorie de la complexité des requêtes , un sous-domaine de l'informatique, et qui a de nombreuses applications.
Zgarb
Je pense que ce serait formidable d'avoir à nouveau le même défi, mais où vous pouvez accéder à la lcsplace de len_lcs.
flawr
@flawr Ce ne serait pas très intéressant, car il lcs(R, "01"*2*n)revient R. ;) Mais cela pourrait fonctionner si appeler lcs(R, S)augmentait le score au len(S)lieu de 1, ou quelque chose comme ça ...
Zgarb
1
J'aimerais voir d'autres réponses = S
flawr

Réponses:

10

Java, 99,04 98,46 97,66 lcs () appels

Comment ça fonctionne

Exaple: Notre ligne à reconstruire est 00101. Nous découvrons d'abord combien de zéros il y a, en comparant (ici en comparant = calcul des LC avec la chaîne d'origine) par une chaîne de zéros uniquement 00000. Ensuite, nous passons par chaque position, retournons le 0à a 1et vérifions si nous avons maintenant une sous-chaîne commune plus longue. Si oui, acceptez et passez à la position suivante, sinon, retournez le courant 1à a 0et passez à la position suivante:

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

Optimisations

Ceci est juste une implémentation "naïve", il serait peut-être possible de trouver un alogrithme plus sophistiqué vérifiant plusieurs positions à la fois. Mais je ne sais pas s'il vraiment est un meilleur (par exemple basé sur le calcul des bits de parité similaires au code Hamming), comme vous pouvez toujours évaluer juste la longueur de la sous - chaîne commune.

Pour une ligne de chiffres donnée, cet algorithme a besoin de #ofDigitsUntilTheLastOccurenceOf1 + 1vérifications précises. (Soustrayez-en un si les derniers chiffres sont un 1.)

EDIT: Une petite optimisation: si nous venons de vérifier le deuxième dernier chiffre et que nous devons encore insérer un 1, nous savons avec certitude qu'il doit être dans la toute dernière position et peut omettre la vérification correspondante.

EDIT2: Je viens de remarquer que vous pouvez appliquer l'idée ci-dessus aux dernières k.

Il pourrait bien sûr être possible d'obtenir un score légèrement inférieur avec cette optimisation, en réorganisant d'abord toutes les lignes, car il se pourrait que vous obteniez plus de lignes avec celles à la toute fin, mais ce serait évidemment une optimisation pour le courant cas de test qui n'est plus drôle.

Durée

La limite supérieure est O(#NumberOfBits).

Code complet

Voici le code complet:

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();;
        }

    }

}
flawr
la source
1
Étant donné que vous recevez moins d'appels lorsqu'il y a des 1 à la fin, il semble que vous pourriez faire mieux en moyenne si, après la première supposition, il y a plus de 0 que de 1, vous passez à la recherche de 0 position plutôt que de 1 position. Vous pouvez même le faire plusieurs fois.
histocrate
1
@histocrate Je pense qu'il s'arrête déjà une fois qu'il a utilisé le dernier 1, ce qui équivaut à ce qu'il ne reste que des zéros.
Martin Ender