Знати послідовність за її подальшими ознаками


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. Вони були згенеровані, взявши три випадкові поплавці між 0 і 1, взявши їх середнє значення a, а потім перевернувши aнахилену кількість монет n. Ви набираєте середню кількість дзвінків уlen_lcs цих послідовностях, нижчий бал - кращий. У вашому записі має бути записана кількість дзвінків Немає часових обмежень, за винятком того, що перед запуском програми слід запустити програму у файлі.

Ваше подання має бути детермінованим. PRNG дозволені, але вони повинні використовувати сьогоднішню дату 200116(або найближчий еквівалент) як випадкове насіння. Вам не дозволяється оптимізувати ваші подання стосовно цих конкретних тестових випадків. Якщо я підозрюю, що це відбувається, я генерую нову партію.

Це не код гольфу, тому вам рекомендується писати читабельний код. Код Розетта має сторінку про найдовшу загальну послідовність ; ви можете використовувати це для втілення len_lcsвашою мовою.


Класна ідея! Чи є в цьому додатки?
недолік

@flawr Я не знаю жодного прямого застосування. Ідея виникла з теорії складності запитів , підполя інформатики, що містить безліч додатків.
Згарб

Я думаю, що було б здорово повторити той самий виклик, але там, де можна отримати доступ 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 ЛВП () дзвінки

Як це працює

Приклад: Наша лінія, яку потрібно реконструювати, є 00101. Спочатку ми з'ясовуємо, скільки нулів існує, порівнюючи (тут порівнюючи = обчислюючи 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

Оптимізація

Це просто "наївна" реалізація, можливо, можна було б знайти більш досконалий алогритм, який перевіряє відразу кілька позицій. Але я не впевнений, чи дійсно є кращий (наприклад, на основі обчислення бітів парності, аналогічного коду Хеммінга), оскільки ви завжди можете просто оцінити довжину загальної підрядки.

Для одного заданого рядка цифр цей алгоритм потребує точно #ofDigitsUntilTheLastOccurenceOf1 + 1перевірки. (Суб'єктна, якщо останні цифри є 1.)

EDIT: Одна невелика оптимізація: Якщо ми щойно перевірили другу останню цифру і нам все-таки потрібно вставити a 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, здається, що ви могли б зробити в середньому краще, якщо після першої здогадки вам сказати, що більше 0, ніж 1, ви переходите на полювання на 0 позицій, а не на 1 посаду. Ви навіть могли це зробити кілька разів.
гістократ

1
@histocrat Я думаю, що він вже зупиняється, коли він використовує останнє 1, що еквівалентно тому, що залишилися лише нулі.
Мартін Ендер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.