Знать последовательность по ее подпоследовательностям

18

Вступление

Предположим, вы и ваш друг играете в игру. Ваш друг думает о какой-то определенной последовательности nбитов, а ваша задача - определить последовательность, задав им вопросы. Тем не менее, единственный тип вопроса, который вам разрешено задавать, это «Какова самая длинная общая подпоследовательность вашей последовательности и S», где S- любая последовательность битов. Чем меньше вопросов вам нужно, тем лучше.

Задание

Ваша задача - написать программу или функцию, которая принимает в качестве входных данных положительное целое число nи двоичную последовательность Rдлины n. Последовательность может быть массивом целых чисел, строкой или другим разумным типом по вашему выбору. Ваша программа должна вывести последовательность R.

Ваша программа не имеет права доступа к последовательности Rнапрямую. Только , что это разрешено сделать , Rэто дать ему в качестве входных данных для функции len_lcsнаряду с другой двоичной последовательностью S. Функция len_lcs(R, S)возвращает длину самой длинной общей подпоследовательности Rи S. Это означает самую длинную последовательность битов, которая встречается как (не обязательно непрерывная) подпоследовательность в обоих Rи S. Входы len_lcsкоторых могут быть разной длины. Программа должна вызывать эту функцию Rи другие последовательности некоторое количество раз, а затем восстанавливать последовательность Rна основе этой информации.

пример

Рассмотрим входы n = 4и R = "1010". Во-первых, мы можем оценить len_lcs(R, "110"), что дает 3, так как "110"это самая длинная общая подпоследовательность "1010"и "110". Тогда мы знаем, что Rполучается "110"путем вставки одного бита в какую-то позицию. Далее мы могли бы попытаться len_lcs(R, "0110"), который возвращает, 3так как самые длинные общие подпоследовательности "110"и "010", следовательно "0110", не верны. Затем мы пытаемся len_lcs(R, "1010"), который возвращается 4. Теперь мы знаем это R == "1010", поэтому мы можем вернуть эту последовательность как правильный вывод. Это потребовало 3 звонка len_lcs.

Правила и оценки

В этом репозитории вы найдете файл, subsequence_data.txtсодержащий 100 случайных двоичных последовательностей длиной от 75 до 124. Они были сгенерированы путем трех случайных операций aс aплавающей запятой в диапазоне от 0 до 1, взяв их среднее значение как , а затем перевернув смещенное nвремя монеты . Вы набираете среднее количество обращений кlen_lcs этим последовательностям, при этом более низкий балл. Ваша заявка должна записать количество звонков. Ограничений по времени нет, за исключением того, что вы должны запустить свою программу в файле перед отправкой.

Ваше представление должно быть детерминированным. ГСЧ разрешены, но они должны использовать текущую дату 200116(или ближайший эквивалент) в качестве случайного начального числа. Вам не разрешено оптимизировать свою работу в соответствии с этими конкретными тестовыми примерами. Если я подозреваю, что это происходит, я создам новую партию.

Это не кодовый гольф, поэтому рекомендуется писать читаемый код. У кода Розетты есть страница с самой длинной общей подпоследовательностью ; Вы можете использовать это для реализации len_lcsна своем языке по вашему выбору.

Zgarb
источник
Классная идея! Есть ли у этого какие-либо приложения?
flawr
@ Flawr Я не знаю никаких прямых приложений. Идея возникла из теории сложности запросов , подотдела информатики, который имеет множество приложений.
Згарб
Я думаю, что было бы здорово снова испытать ту же проблему, но lcsвместо того, чтобы получить доступ к ней len_lcs.
flawr
@flawr Это не было бы очень интересно, так как lcs(R, "01"*2*n)возвращается R. ;) Но это может сработать, если колл lcs(R, S)увеличит счет len(S)вместо 1, или что-то в этом роде ...
Згарб
1
Я хотел бы видеть другие ответы = S
flawr

Ответы:

10

Java, 99.04 98.46 97.66 lcs () вызовы

Как это устроено

Exaple: Наша линия , которая должна быть реконструирована является 00101. Сначала мы выясняем, сколько существует нулей, сравнивая (здесь сравнивая = вычисление lcs с исходной строкой) только строку с нулями 00000. Затем мы проходим через каждую позицию, флип 0к 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

Оптимизации

Это всего лишь «наивная» реализация, возможно, было бы возможно найти более сложный алгоритм, проверяющий несколько позиций одновременно. Но я не уверен, что на самом деле это лучше один (например , на основе вычисления битов четности по аналогии с кодом Хэмминга), как вы всегда можете просто оценить длину от общей подстроки.

Для одной данной строки цифр этот алгоритм нуждается в точных #ofDigitsUntilTheLastOccurenceOf1 + 1проверках. (Вычтите одно, если последние цифры1 .)

РЕДАКТИРОВАТЬ: Одна небольшая оптимизация: если мы только что проверили 2-ю последнюю цифру, и нам все еще нужно вставить 1 , мы точно знаем, что она должна быть в самой последней позиции, и можем пропустить соответствующую проверку.

РЕДАКТИРОВАТЬ 2: Я только что заметил, что вы можете применить вышеупомянутую идею к последней 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();;
        }

    }

}
flawr
источник
1
Поскольку вы получаете меньше вызовов, когда есть трейлинг 1, кажется, что вы могли бы сделать лучше в среднем, если после первого предположения, что больше 0, чем 1, вы переключаетесь на поиск 0 позиций, а не 1 позиций. Вы могли бы даже сделать это несколько раз.
гистократ
1
@histocrat Я думаю, что он уже останавливается, как только израсходует последнее 1, что эквивалентно тому, что остались только нули.
Мартин Эндер