Bir diziyi alt dizileriyle bilme


18

Giriş

Diyelim ki siz ve arkadaşınız bir oyun oynuyorsunuz. Arkadaşınız belirli bir nbit dizisini düşünüyor ve göreviniz diziyi sorular sorarak çıkarmaktır. Ancak, sormanıza izin verilen tek soru türü, "dizinizin en uzun ortak dizisi ne kadardır S" ve Sherhangi bir bit dizisinin nerede olduğudur. Ne kadar az soruya ihtiyacınız olursa o kadar iyidir.

Görev

Göreviniz girdi olarak pozitif bir tam sayı nve ikili Ruzunluk dizisi alan bir program veya işlev yazmaktır n. Sekans, bir tamsayı dizisi, bir dize veya seçtiğiniz başka bir makul tür olabilir. Programınız sıralamayı çıkarır R.

Programınız edilir değil dizisini erişmesine izin Rdoğrudan. Sadece o yapmak izin verilen şey Rfonksiyonuna girdi olarak vermek len_lcsbaşka bir ikili dizisi ile birlikte S. Fonksiyon len_lcs(R, S)en uzun ortak alt dizisinin uzunluğunu döndürür Rve S. Bu, hem Rve hem de (mutlaka bitişik olmayan) bir alt sekans olarak ortaya çıkan en uzun bit dizisi anlamına gelir S. Girdileri len_lcsfarklı uzunluklarda olabilir. Program bu işlevi Rve diğer dizileri birkaç kez çağırmalı ve daha sonra Rbu bilgilere dayanarak diziyi yeniden yapılandırmalıdır .

Misal

Girişleri n = 4ve R = "1010". İlk olarak, değerlendirmek olabilir len_lcs(R, "110")veren 3beri, "110"en uzun ortak sonradan olan "1010"ve "110". Sonra biliyoruz ki bir noktaya bir bit sokarak Relde edilir "110". Daha sonra, en uzun ortak alt diziler olduğu ve bu yüzden doğru olmadığı için len_lcs(R, "0110")geri dönen deneyebiliriz . Sonra deneriz , geri döner . Şimdi bunu biliyoruz , böylece bu diziyi doğru çıktı olarak geri verebiliriz. Bunun için 3 çağrı gerekiyordu .3"110""010""0110"len_lcs(R, "1010")4R == "1010"len_lcs

Kurallar ve puanlama

Gelen bu depo , sen adlı bir dosya bulacaksınız subsequence_data.txt75 ve 124 arası uzunluklarda 100 rastgele ikili dizileri Onlar 0 ile 1 arasında rasgele üç yüzer alarak ortalama alınarak üretildi içeren abir ve sonra saygısız a-biased sikke nkez. Puanınız, bu dizilere yapılan ortalama çağrı sayısıdırlen_lcs , daha düşük puan daha iyidir. Gönderiniz çağrı sayısını kaydetmelidir. Göndermeden önce programınızı dosya üzerinde çalıştırmanız dışında hiçbir zaman sınırı yoktur.

Gönderiniz belirleyici olacaktır. PRNG'lere izin verilir, ancak bugünün tarihini 200116(veya en yakın eşdeğerini) rastgele tohum olarak kullanmaları gerekir . Gönderinizi bu belirli test senaryolarına göre optimize etmenize izin verilmez. Bunun olduğundan şüpheleniyorsam, yeni bir parti oluşturacağım.

Bu kod golf değildir, bu nedenle okunabilir kod yazmanız önerilir. Rosetta Kodunda bir en uzun ortak alt sayfası ; bunu len_lcskendi dilinizde uygulamak için kullanabilirsiniz .


İyi fikir! Bunun herhangi bir uygulaması var mı?
Kusur

@flawr Hiçbir doğrudan uygulama bilmiyorum. Fikir , bilgisayar biliminin bir alt alanı olan ve karmaşık uygulamalar içeren sorgu karmaşıklığı teorisinden geldi .
Zgarb

Aynı zorluğa tekrar sahip olmak harika ama lcsyerine erişebileceğiniz yer olduğunu düşünüyorum len_lcs.
Kusur

@flawr Geri lcs(R, "01"*2*n)döndüğünden beri çok ilginç olmazdı R. ;) Ama eğer arama 1 yerine lcs(R, S)skoru artıracaksa işe yarayabilir len(S)ya da bunun gibi bir şey ...
Zgarb

1
Diğer cevapları görmek isterim = S
flawr

Yanıtlar:


10

Java, 99.04 98,46 97,66 lcs () çağrıları

Nasıl çalışır

Örnek: Yeniden yapılandırılacak çizgimiz 00101. Öncelikle, yalnızca sıfırlar dizesi ile (burada lcs'ı orijinal dize ile karşılaştırmak) karşılaştırarak kaç tane sıfır olduğunu öğreniyoruz 00000. Sonra, her pozisyonda geçmesi çevirmek 0a 1ve şimdi daha uzun bir ortak alt dize varsa kontrol edin. Cevabınız evet ise, kabul edin ve bir sonraki konuma geçin, hayır ise, akımı 1a 0konumuna çevirin ve bir sonraki konuma geçin:

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

optimizasyonları

Bu sadece "saf" bir uygulamadır, belki birden fazla pozisyonu aynı anda kontrol eden daha sofistike bir alogrithm bulmak mümkün olabilir. Ama emin gerçekten orada eğer değilim olduğunu daha iyi bir (örn Hamming koduna benzer eşlik bitlerinin hesaplamaya dayanan) sadece değerlendirmek her zaman gibi, uzunluk ortak altdizgenin.

Belirli bir basamak satırı için, bu algoritmanın tam olarak #ofDigitsUntilTheLastOccurenceOf1 + 1kontrol edilmesi gerekir. (Son hane bir ise, alt özet 1.)

DÜZENLEME: Küçük bir optimizasyon: Eğer son 2. basamağı kontrol ettiysek ve yine de a 1eklememiz gerekirse, en son konumda olması gerektiğinden emin oluruz ve ilgili kontrolü atlayabiliriz.

EDIT2: Yukarıdaki fikri sonunculara uygulayabildiğini fark ettim k.

Tabii bu ilk önce tüm satırları yeniden sıralayarak bu optimizasyon ile biraz daha düşük bir puan elde etmek mümkün olabilir, çünkü belki de en sonunda olanlarla daha fazla satır elde edebilirsiniz, ancak bu açıkça ve akım için optimizasyon olacaktır komik olmayan test senaryoları.

Çalışma süresi

Üst sınır O(#NumberOfBits).

Tam kod

İşte tam kod:

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
İzleyen 1'ler olduğunda daha az çağrı aldığınızdan, ilk tahminin size 1 pozisyondan ziyade 0 pozisyon için avlamaya geçtiğiniz 1 saniyeden 0 saniye olduğunu söylerse ortalama olarak daha iyi yapabileceğiniz gibi görünüyor. Bunu birkaç kez bile yapabilirsiniz.
histokrat

1
@histokrat Sanırım sonuncuyu kullandığında zaten duruyor 1, bu sadece sıfır kaldı.
Martin Ender
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.