Tebak kata (alias Lingo)

13

Tujuan dari tantangan ini adalah untuk menulis sebuah program yang dapat menebak kata dalam jumlah upaya sekecil mungkin. Ini didasarkan pada konsep acara TV Lingo ( http://en.wikipedia.org/wiki/Lingo_(US_game_show) ).

Aturan

Mengingat panjang kata yang dilewati sebagai argumen pertama pada baris perintahnya, program pemain melakukan lima upaya untuk menebak kata dengan menulis tebakan pada output standarnya diikuti oleh satu \nkarakter.

Setelah tebakan dibuat, program menerima string pada input standarnya, juga diikuti oleh satu \nkarakter.

String memiliki panjang yang sama dengan kata untuk menebak dan terdiri dari urutan karakter berikut:

  • X: yang berarti bahwa huruf yang diberikan tidak ada dalam kata untuk menebak
  • ?: yang berarti bahwa huruf yang diberikan hadir dalam kata untuk menebak, tetapi di lokasi lain
  • O: yang berarti bahwa surat di lokasi ini telah ditebak dengan benar

Misalnya, jika kata untuk menebak adalah dents, dan program mengirimkan kata dozes, itu akan menerima OXX?Okarena ddan sbenar, esalah tempat, dan odan ztidak ada.

Berhati-hatilah bahwa jika sebuah huruf muncul lebih sering dalam upaya menebak daripada dalam kata untuk menebak, itu tidak akan ditandai sebagai ?dan Olebih dari jumlah kemunculan huruf dalam kata untuk menebak. Misalnya, jika kata untuk menebak adalah coziesdan program mengirim tosses, ia akan menerimaXOXXOO karena hanya ada satu suntuk menemukan.

Kata-kata dipilih dari daftar kata bahasa Inggris. Jika kata yang dikirim oleh program bukan kata yang valid dengan panjang yang benar, upaya dianggap sebagai kegagalan otomatis dan hanya Xkata yang dikembalikan.
Program pemain harus mengasumsikan bahwa file bernama wordlist.txtdan mengandung satu kata per baris ada di direktori kerja saat ini, dan dapat dibaca seperlunya.
Tebakan seharusnya hanya terdiri dari huruf kecil alfabet ( [a-z]).
Tidak ada operasi jaringan atau file lain yang diizinkan untuk program ini.

Permainan berakhir ketika string yang hanya terdiri dari Odikembalikan atau setelah program melakukan 5 upaya dan tidak dapat menebak kata.

Mencetak gol

Skor permainan diberikan oleh rumus yang diberikan:

score = 100 * (6 - number_of_attempts)

Jadi, jika kata tersebut ditebak dengan benar pada percobaan pertama, diberikan 500 poin. Percobaan terakhir bernilai 100 poin.

Gagal menebak kata memberikan poin nol.

Lubang

Program pemain akan dievaluasi dengan mencoba membuat mereka menebak 100 kata acak untuk setiap panjang kata antara 4 dan 13 karakter secara inklusif.
Pemilihan kata acak akan dilakukan terlebih dahulu sehingga semua entri harus menebak kata yang sama.

Program yang menang, dan jawaban yang diterima, akan menjadi orang yang mencapai skor tertinggi.

Program akan dijalankan di mesin virtual Ubuntu, menggunakan kode di https://github.com/noirotm/lingo . Implementasi dalam bahasa apa pun diterima selama instruksi yang masuk akal untuk mengkompilasi dan / atau menjalankannya disediakan.

Saya menyediakan beberapa implementasi tes di ruby ​​di repositori git, jangan ragu untuk mengambil inspirasi dari mereka.

Pertanyaan ini akan diperbarui secara berkala dengan peringkat untuk jawaban yang dipublikasikan sehingga penantang dapat meningkatkan entri mereka.

Evaluasi akhir resmi akan berlangsung pada tanggal 1 Juli .

Memperbarui

Entri sekarang dapat mengasumsikan keberadaan wordlistN.txtfile untuk mempercepat membaca daftar kata untuk panjang kata saat ini untuk N antara 4 dan 13 secara inklusif.

Misalnya, ada wordlist4.txtfile yang berisi keempat kata huruf, dan wordlist10.txtberisi semua sepuluh kata huruf, dan sebagainya.

Hasil babak pertama

Pada tanggal 2014-07-01-01, tiga entri telah dikirimkan, dengan hasil sebagai berikut:

                        4       5       6       7       8       9       10      11      12      13      Total
./chinese-perl-goth.pl  8100    12400   15700   19100   22100   25800   27900   30600   31300   33600   226600
java Lingo              10600   14600   19500   22200   25500   28100   29000   31600   32700   33500   247300
./edc65                 10900   15800   22300   24300   27200   29600   31300   33900   33400   33900   262600

** Rankings **
1: ./edc65 (262600)
2: java Lingo (247300)
3: ./chinese-perl-goth.pl (226600)

Semua entri dilakukan secara konsisten, dengan pemenang yang jelas, menjadi entri C ++ dari @ edc65.

Semua kontestan sangat mengagumkan. Sejauh ini saya belum bisa mengalahkan @ chinese-perl-goth.
Jika lebih banyak entri dikirimkan, evaluasi lain akan dilakukan. Entri saat ini juga dapat ditingkatkan jika Anda merasa dapat melakukan yang lebih baik.

SirDarius
sumber
1
Hanya untuk memperjelas: jika program membutuhkan lebih dari 6 upaya untuk menebak kata, apakah mendapat poin negatif atau hanya nol? Dengan kata lain, apakah kita perlu logika untuk keluar dari program setelah 6 mencoba menghindari poin negatif? (Aturan mengatakan nol poin jika program gagal menebak kata)
DankMemes
1
@ZoveGames setelah lima upaya, program Anda harus keluar, tetapi mesin game akan menghentikannya secara paksa jika ia menolak melakukannya :)
SirDarius
1
@ Richard ya, jangan khawatir tentang Python, ini adalah warga negara kelas satu, jadi saya tidak akan kesulitan menjalankan beberapa kode python :)
SirDarius
1
@ justhalf Terima kasih banyak untuk itu! Saya akhirnya bisa melanjutkan!
MisterBla
1
@hanya benar-benar ide yang bagus, saya akan mencoba menerapkannya
SirDarius

Jawaban:

5

C ++ 267700 Poin

Porting dari mesin MasterMind lama.
Perbedaan dari MasterMind:

  • Lebih banyak slot
  • Lebih banyak simbol
  • Ruang solusi lebih besar (tetapi tidak terlalu banyak, karena tidak semua kombinasi simbol diperbolehkan)
  • Responsnya sangat informatif, jadi kami memiliki lebih banyak informasi setelah setiap tebakan
  • Responsnya lebih lambat untuk dihasilkan dan itu sangat disayangkan karena algoritma saya harus sering melakukannya.

Ide dasarnya adalah memilih kata yang meminimalkan ruang solusi. Algoritma ini sangat lambat untuk tebakan pertama (maksud saya 'hari'), tetapi tebakan pertama yang terbaik hanya bergantung pada kata len, jadi hardcoded pada sumbernya. Tebakan-tebakan lainnya dilakukan dalam hitungan detik.

Kode

(Kompilasi dengan g ++ -O3)

#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <ctime>
#include <cstdlib>

using namespace std;

class LRTimer
{
private:
    time_t start;
public:
    void startTimer(void)
    {
        time(&start);
    }

    double stopTimer(void)
    {
        return difftime(time(NULL),start);
    } 

};

#define MAX_WORD_LEN 15
#define BIT_QM 0x8000

LRTimer timer;
int size, valid, wordLen;

string firstGuess[] = { "", "a", "as", "iao", "ares", 
    "raise", "sailer", "saltier", "costlier", "clarities", 
    "anthelices", "petulancies", "incarcerates", "allergenicity" };

class Pattern
{
public:
    char letters[MAX_WORD_LEN];
    char flag;
    int mask;

    Pattern() 
        : letters(), mask(), flag()
    {
    }

    Pattern(string word) 
        : letters(), mask(), flag()
    {
        init(word);
    }

    void init(string word)
    {
        const char *wdata = word.data();
        for(int i = 0; i < wordLen; i++) {
            letters[i] = wdata[i];
            mask |= 1 << (wdata[i]-'a');
        }
    }

    string dump()
    {
        return string(letters);
    }

    int check(Pattern &secret)
    {
        if ((mask & secret.mask) == 0)
            return 0;

        char g[MAX_WORD_LEN], s[MAX_WORD_LEN];
        int r = 0, q = 0, i, j, k=99;
        for (i = 0; i < wordLen; i++)
        {
            g[i] = (letters[i] ^ secret.letters[i]);
            if (g[i])
            {
                r += r;
                k = 0;
                g[i] ^= s[i] = secret.letters[i];
            }
            else
            {
                r += r + 1;
                s[i] = 0;
            }
        }
        for (; k < wordLen; k++)
        {
            q += q;
            if (g[k]) 
            {
                for (j = 0; j < wordLen; j++)
                    if (g[k] == s[j])
                    {
                        q |= BIT_QM;
                        s[j] = 0;
                        break;
                    }
            }
        }
        return r|q;
    }

    int count(int ck, int limit);

    int propcheck(int limit);

    void filter(int ck);
};

string dumpScore(int ck)
{
    string result(wordLen, 'X');
    for (int i = wordLen; i--;)
    {
        result[i] = ck & 1 ? 'O' : ck & BIT_QM ? '?' : 'X';
        ck >>= 1;
    }
    return result;
}

int parseScore(string ck)
{
    int result = 0;
    for (int i = 0; i < wordLen; i++)
    {
        result += result + (
            ck[i] == 'O' ? 1 : ck[i] == '?' ? BIT_QM: 0
        );
    }
    return result;
}

Pattern space[100000];

void Pattern::filter(int ck)
{
    int limit = valid, i = limit;
//  cerr << "Filter IN Valid " << setbase(10) << valid << " This " << dump() << "\n"; 

    while (i--)
    {
        int cck = check(space[i]);
//      cerr << setbase(10) << setw(8) << i << ' ' << space[i].dump() 
//          << setbase(16) << setw(8) << cck << " (" << Pattern::dumpScore(cck) << ") ";

        if ( ck != cck )
        {
//          cerr << " FAIL\r" ;
            --limit;
            if (i != limit) 
            {
                Pattern t = space[i];
                space[i] = space[limit];
                space[limit] = t;
            }
        }
        else
        {
//          cerr << " PASS\n" ;
        }
    }
    valid = limit;
//  cerr << "\nFilter EX Valid " << setbase(10) << valid << "\n"; 
};

int Pattern::count(int ck, int limit)
{
    int i, num=0;
    for (i = 0; i < valid; ++i)
    {
        if (ck == check(space[i]))
            if (++num >= limit) return num;
    }
    return num;
}

int Pattern::propcheck(int limit)
{
    int k, mv, nv;

    for (k = mv = 0; k < valid; ++k)
    {
        int ck = check(space[k]);
        nv = count(ck, limit);
        if (nv >= limit)
        {
            return 99999;
        }
        if (nv > mv) mv = nv;
    }
    return mv;
}

int proposal(bool last)
{
    int i, minnv = 999999, mv, result;

    for (i = 0; i < valid; i++) 
    {
        Pattern& guess = space[i];
//      cerr << '\r' << setw(6) << i << ' ' << guess.dump();
        if ((mv = guess.propcheck(minnv)) < minnv)
        {
//          cerr << setw(6) << mv << ' ' << setw(7) << setiosflags(ios::fixed) << setprecision(0) << timer.stopTimer() << " s\n";
            minnv = mv;
            result = i;
        }
    }   
    if (last) 
        return result;
    minnv *= 0.75;
    for (; i<size; i++) 
    {
        Pattern& guess = space[i];
//      cerr << '\r' << setw(6) << i << ' ' << guess.dump();
        if ((mv = guess.propcheck(minnv)) < minnv)
        {
//          cerr << setw(6) << mv << ' ' << setw(7) << setiosflags(ios::fixed) << setprecision(0) << timer.stopTimer() << " s\n";
            minnv = mv;
            result = i;
        }
    }   
    return result;
}

void setup(string wordfile)
{
    int i = 0; 
    string word;
    ifstream infile(wordfile.data());
    while(infile >> word)
    {
        if (word.length() == wordLen) {
            space[i++].init(word);
        }
    }
    infile.close(); 
    size = valid = i;
}

int main(int argc, char* argv[])
{
    if (argc < 2) 
    {
        cerr << "Specify word length";
        return 1;
    }

    wordLen = atoi(argv[1]);

    timer.startTimer();
    setup("wordlist.txt");
    //cerr << "Words " << size 
    //  << setiosflags(ios::fixed) << setprecision(2)
    //  << " " << timer.stopTimer() << " s\n";

    valid = size;
    Pattern Guess = firstGuess[wordLen];
    for (int t = 0; t < 5; t++)
    {
        cout << Guess.dump() << '\n' << flush;
        string score;
        cin >> score;
        int ck = parseScore(score);
        //cerr << "\nV" << setw(8) << valid << " #" 
        //  << setw(3) << t << " : " << Guess.dump()
        //  << " : " << score << "\n";
        if (ck == ~(-1 << wordLen))
        {
            break;
        }
        Guess.filter(ck); 
        Guess = space[proposal(t == 3)];
    }
    // cerr << "\n";

    double time = timer.stopTimer();
    //cerr << setiosflags(ios::fixed) << setprecision(2)
    //   << timer.stopTimer() << " s\n";

    return 0;
}

Skor saya

Evaluasi dengan istilah, 100 putaran:

4   9000
5   17700
6   22000
7   25900
8   28600
9   29700
10  31000
11  32800
12  33500
13  34900

Total 265'100

Skor yang dievaluasi sendiri

Inilah poin rata-rata saya, yang dicetak di seluruh daftar kata. Tidak sepenuhnya dapat diandalkan karena beberapa detail algoritma telah berubah selama pengujian.

 4 # words  6728 PT AVG   100.98 87170.41 s
 5 # words 14847 PT AVG   164.44 42295.38 s
 6 # words 28127 PT AVG   212.27 46550.00 s 
 7 # words 39694 PT AVG   246.16 61505.54 s
 8 # words 49004 PT AVG   273.23 63567.45 s
 9 # words 50655 PT AVG   289.00 45438.70 s
10 # words 43420 PT AVG   302.13 2952.23 s
11 # words 35612 PT AVG   323.62 3835.00 s
12 # words 27669 PT AVG   330.19 5882.98 s
13 # words 19971 PT AVG   339.60 2712.98 s

Menurut angka-angka ini, skor rata-rata saya harus dekat 257'800

PIT SCORE

Akhirnya saya menginstal Ruby, jadi sekarang saya memiliki skor 'resmi':

    4       5       6       7       8       9      10      11      12      13   TOTAL
10700   16300   22000   25700   27400   30300   32000   33800   34700   34800   267700
edc65
sumber
Niat saya adalah untuk menciptakan sesuatu seperti ini. Sayangnya saya tidak dapat menemukan cara untuk benar-benar meminimalkan ruang solusi, jadi saya memperkirakannya. Dan milik saya menggunakan Python, jadi lebih lambat lagi, haha. Saya juga mencatat dengan keras tebakan pertama. Hormat saya jelas lebih baik dari saya untuk string yang lebih pendek. Bisakah Anda menguji dengan implementasi saya juga pada set input yang sama untuk dibandingkan? Kami juga memiliki set tebakan pertama yang sangat berbeda.
justhalf
@ justhalf Saya mencoba beberapa putaran dengan lingo.go. Saya tidak memeriksa lubang (Saya tidak menginstal Ruby). Skor kami hampir sama, saya pikir itu soal keberuntungan.
edc65
Anda lebih baik menurut saya, karena rata-rata yang Anda laporkan lebih baik daripada skor yang saya laporkan. Meskipun Anda tampaknya mengambil lebih banyak waktu.
justhalf
Ini tampaknya menjadi pemain terkuat sejauh ini. Saya akan menjalankan hasil resmi hari ini, tetap disini!
SirDarius
Ups, koreksi untuk komentar saya di atas, saya lupa bahwa kiriman saya ada di Jawa.
justhalf
5

Java, 249700 poin (mengalahkan Perl Goth Cina dalam tes saya)

Daftar peringkat yang diperbarui:

                        4 5 6 7 8 9 10 11 12 13 Total
perl chinese_perl_goth.pl 6700 12300 16900 19200 23000 26100 28500 29600 32100 33900 228300
java Lingo 9400 14700 18900 21000 26300 28700 30300 32400 33800 34200 249700

Berikut adalah daftar peringkat lama menggunakan pit.rb:

                        4 5 6 7 8 9 10 11 12 13 Total
pemain ruby-example.rb 200 400 400 500 1800 1400 1700 1600 3200 4400 15600
pemain ruby-example2.rb 2700 3200 2500 4300 7300 6300 8200 10400 13300 15000 73200
pemain ruby-example3.rb 4500 7400 9900 13700 15400 19000 19600 22300 24600 27300 163700
perl chinese_perl_goth.pl 6400 14600 16500 21000 22500 26000 27200 30600 32500 33800 231100
java Lingo 4800 13100 16500 21400 27200 29200 30600 32400 33700 36100 245000

** Peringkat **
1: java Lingo (245000)
2: perl chinese_perl_goth.pl (231100)
3: ruby ​​player-example3.rb (163700)
4: pemutar ruby-example2.rb (73200)
5: ruby ​​player-example.rb (15600)

Dibandingkan dengan @chineseperlgoth, saya kalah dalam kata-kata yang lebih pendek (<6 karakter) tetapi saya menang dalam kata-kata yang lebih panjang (> = 6 karakter).

Idenya mirip dengan @chineseperlgoth, hanya saja ide utama saya adalah tentang menemukan tebakan (bisa berupa kata dengan panjang yang sama, belum tentu salah satu dari kemungkinan yang tersisa) yang memberikan informasi paling banyak untuk tebakan berikutnya.

Saat ini saya masih bermain dengan formula, tetapi untuk papan skor di atas, saya memilih kata yang akan menghasilkan minimum untuk:

-num_confusion * entropi

Versi terbaru menggunakan skor berbeda untuk menemukan tebakan terbaik berikutnya, yang memaksimalkan jumlah "satu kemungkinan" setelah tebakan saat ini. Ini dilakukan dengan mencoba semua kata dalam daftar kata yang telah dipangkas (untuk menghemat waktu) pada semua kandidat yang mungkin, dan melihat tebakan mana yang lebih memungkinkan untuk menghasilkan "kemungkinan tunggal" (yaitu, setelah tebakan ini hanya akan ada satu kemungkinan jawaban) untuk tebakan selanjutnya.

Jadi misalnya menjalankan ini:

Mulai babak baru, kata adalah anugerah
Punya: seora
Terkirim:? XOXX
Punya: topsl
Terkirim: XOX? X
Punya: biarawan
Terkirim: XO? XO
Punya: bewig
Terkirim: OXXXX
Punya: boons
Terkirim: OOOOO
Babak menang dengan skor 100

Dari tiga tebakan pertama, kita sudah mendapatkan "* oo * s" dengan "n" di suatu tempat dan kita masih perlu mencari satu surat lagi. Sekarang keindahan dari algoritma ini adalah bahwa alih-alih menebak kata-kata yang mirip dengan bentuk itu, alih-alih menebak kata yang tidak ada hubungannya dengan tebakan sebelumnya sama sekali, mencoba memberikan lebih banyak huruf, semoga mengungkapkan huruf yang hilang. Dalam hal ini kebetulan juga mendapatkan posisi untuk "b" yang hilang dengan benar, dan diakhiri dengan tebakan akhir yang tepat "boons".

Ini kodenya:

import java.util.*;
import java.io.*;

class Lingo{
    public static String[] guessBestList = new String[]{
                                "",
                                "a",
                                "sa",
                                "tea",
                                "orae",
                                "seora", // 5
                                "ariose",
                                "erasion",
                                "serotina",
                                "tensorial",
                                "psalterion", // 10
                                "ulcerations",
                                "culteranismo",
                                "persecutional"};
    public static HashMap<Integer, ArrayList<String>> wordlist = new HashMap<Integer, ArrayList<String>>();

    public static void main(String[] args){
        readWordlist("wordlist.txt");
        Scanner scanner = new Scanner(System.in);
        int wordlen = Integer.parseInt(args[0]);
        int roundNum = 5;
        ArrayList<String> candidates = new ArrayList<String>();
        candidates.addAll(wordlist.get(wordlen));
        String guess = "";
        while(roundNum-- > 0){
            guess = guessBest(candidates, roundNum==4, roundNum==0);
            System.out.println(guess);
            String response = scanner.nextLine();
            if(isAllO(response)){
                break;
            }
            updateCandidates(candidates, guess, response);
            //print(candidates);
        }
    }

    public static void print(ArrayList<String> candidates){
        for(String str: candidates){
            System.err.println(str);
        }
        System.err.println();
    }

    public static void readWordlist(String path){
        try{
            BufferedReader reader = new BufferedReader(new FileReader(path));
            while(reader.ready()){
                String word = reader.readLine();
                if(!wordlist.containsKey(word.length())){
                    wordlist.put(word.length(), new ArrayList<String>());
                }
                wordlist.get(word.length()).add(word);
            }
        } catch (Exception e){
            System.exit(1);
        }
    }

    public static boolean isAllO(String response){
        for(int i=0; i<response.length(); i++){
            if(response.charAt(i) != 'O') return false;
        }
        return true;
    }

    public static String getResponse(String word, String guess){
        char[] wordChar = word.toCharArray();
        char[] result = new char[word.length()];
        Arrays.fill(result, 'X');
        for(int i=0; i<guess.length(); i++){
            if(guess.charAt(i) == wordChar[i]){
                result[i] = 'O';
                wordChar[i] = '_';
            }
        }
        for(int i=0; i<guess.length(); i++){
            if(result[i] == 'O') continue;
            for(int j=0; j<wordChar.length; j++){
                if(result[j] == 'O') continue;
                if(wordChar[j] == guess.charAt(i)){
                    result[i] = '?';
                    wordChar[j] = '_';
                    break;
                }
            }
        }
        return String.valueOf(result);
    }

    public static void updateCandidates(ArrayList<String> candidates, String guess, String response){
        for(int i=candidates.size()-1; i>=0; i--){
            String candidate = candidates.get(i);
            if(!response.equals(getResponse(candidate, guess))){
                candidates.remove(i);
            }
        }
    }

    public static int countMatchingCandidates(ArrayList<String> candidates, String guess, String response){
        int result = 0;
        for(String candidate: candidates){
            if(response.equals(getResponse(candidate, guess))){
                result++;
            }
        }
        return result;
    }

    public static String[] getSample(ArrayList<String> words, int size){
        String[] result = new String[size];
        int[] indices = new int[words.size()];
        for(int i=0; i<words.size(); i++){
            indices[i] = i;
        }
        Random rand = new Random(System.currentTimeMillis());
        for(int i=0; i<size; i++){
            int take = rand.nextInt(indices.length-i);
            result[i] = words.get(indices[take]);
            indices[take] = indices[indices.length-i-1];
        }
        return result;
    }

    public static String guessBest(ArrayList<String> candidates, boolean firstGuess, boolean lastGuess){
        if(candidates.size() == 1){
            return candidates.get(0);
        }
        String minGuess = candidates.get(0);
        int wordlen = minGuess.length();
        if(firstGuess && guessBestList[wordlen].length()==wordlen){
            return guessBestList[wordlen];
        }
        int minMatches = Integer.MAX_VALUE;
        String[] words;
        if(lastGuess){
            words = candidates.toArray(new String[0]);
        } else if (candidates.size()>10){
            words = bestWords(wordlist.get(wordlen), candidates, 25);
        } else {
            words = wordlist.get(wordlen).toArray(new String[0]);
        }
        for(String guess: words){
            double sumMatches = 0;
            for(String word: candidates){
                int matches = countMatchingCandidates(candidates, guess, getResponse(word, guess));
                if(matches == 0) matches = candidates.size();
                sumMatches += (matches-1)*(matches-1);
            }
            if(sumMatches < minMatches){
                minGuess = guess;
                minMatches = sumMatches;
            }
        }
        return minGuess;
    }

    public static String[] bestWords(ArrayList<String> words, ArrayList<String> candidates, int size){
        int[] charCount = new int[123];
        for(String candidate: candidates){
            for(int i=0; i<candidate.length(); i++){
                charCount[(int)candidate.charAt(i)]++;
            }
        }
        String[] tmp = (String[])words.toArray(new String[0]);
        Arrays.sort(tmp, new WordComparator(charCount));
        String[] result = new String[size+Math.min(size, candidates.size())];
        String[] sampled = getSample(candidates, Math.min(size, candidates.size()));
        for(int i=0; i<size; i++){
            result[i] = tmp[tmp.length-i-1];
            if(i < sampled.length){
                result[size+i] = sampled[i];
            }
        }
        return result;
    }

    static class WordComparator implements Comparator<String>{
        int[] charCount = null;

        public WordComparator(int[] charCount){
            this.charCount = charCount;
        }

        public Integer count(String word){
            int result = 0;
            int[] multiplier = new int[charCount.length];
            Arrays.fill(multiplier, 1);
            for(char chr: word.toCharArray()){
                result += multiplier[(int)chr]*this.charCount[(int)chr];
                multiplier[(int)chr] = 0;
            }
            return Integer.valueOf(result);
        }

        public int compare(String s1, String s2){
            return count(s1).compareTo(count(s2));
        }
    }
}
justhalf
sumber
Luar biasa, entri ini sangat kuat! Saya ingat melihat pemain manusia di acara TV menggunakan strategi yang sama ketika mereka tidak dapat menebak sepatah kata pun dari petunjuk saat ini.
SirDarius
3

Perl

Masih ada beberapa ruang untuk perbaikan tetapi setidaknya itu mengalahkan contoh pemain yang disertakan :)

Mengasumsikan akses tulis ke direktori saat ini untuk menyimpan daftar kata (untuk membuatnya berjalan sedikit lebih cepat); akan membuat wordlist.lenN.storfile menggunakan Storable. Jika ini merupakan masalah, hapus read_cached_wordlistdan ubah kode hanya untuk digunakan read_wordlist.

Penjelasan

Pertama, saya membuat histogram frekuensi huruf di semua kata dalam daftar kata saat ini ( build_histogram). Maka saya perlu memilih tebakan saya berikutnya - yang dilakukan oleh find_best_word. Algoritma penilaian hanya menambahkan nilai histogram bersamaan, melompati huruf yang sudah terlihat. Ini memberi saya kata yang berisi surat paling sering di daftar kata. Jika ada lebih dari satu kata dengan skor yang diberikan, saya memilih satu secara acak. Setelah menemukan sebuah kata, saya mengirimkannya ke mesin permainan, membaca jawabannya lalu mencoba dan melakukan sesuatu yang berguna dengannya :)

Saya memelihara serangkaian kondisi, yaitu, huruf yang dapat terjadi pada posisi tertentu dalam sebuah kata. Pada awalnya, ini hanya sederhana (['a'..'z'] x $len), tetapi diperbarui berdasarkan petunjuk yang diberikan dalam balasan (lihat update_conds). Saya membangun regex dari kondisi itu dan memfilter daftar kata melalui itu.

Selama pengujian saya menemukan bahwa penyaringan tersebut tidak menangani ?dengan baik, maka filter kedua ( filter_wordlist_by_reply). Ini mengambil keuntungan dari fakta bahwa huruf yang ditandai ?terjadi pada kata pada posisi yang berbeda, dan menyaring daftar kata yang sesuai.

Langkah-langkah ini diulang untuk setiap iterasi dari loop utama, kecuali jika solusinya ditemukan (atau tidak mungkin membaca dari stdin lagi, yang berarti kegagalan).

Kode

#!perl
use strict;
use warnings;
use v5.10;
use Storable;

$|=1;

sub read_wordlist ($) {
    my ($len) = @_;
    open my $w, '<', 'wordlist.txt' or die $!;
    my @wordlist = grep { chomp; length $_ == $len } <$w>;
    close $w;
    \@wordlist
}

sub read_cached_wordlist ($) {
    my ($len) = @_;
    my $stor = "./wordlist.len$len.stor";
    if (-e $stor) {
        retrieve $stor
    } else {
        my $wl = read_wordlist $len;
        store $wl, $stor;
        $wl
    }
}

sub build_histogram ($) {
    my ($wl) = @_;
    my %histo = ();
    for my $word (@$wl) {
        $histo{$_}++ for ($word =~ /./g);
    }
    \%histo
}

sub score_word ($$) {
    my ($word, $histo) = @_;
    my $score = 0;
    my %seen = ();
    for my $l ($word =~ /./g) {
        if (not exists $seen{$l}) {
            $score += $histo->{$l};
            $seen{$l} = 1;
        }
    }
    $score
}

sub find_best_word ($$) {
    my ($wl, $histo) = @_;
    my @found = (sort { $b->[0] <=> $a->[0] } 
                 map [ score_word($_, $histo), $_ ], @$wl);
    return undef unless @found;
    my $maxscore = $found[0]->[0];
    my @max;
    for (@found) {
        last if $_->[0] < $maxscore;
        push @max, $_->[1];
    }
    $max[rand @max]
}

sub build_conds ($) {
    my ($len) = @_;
    my @c;
    push @c, ['a'..'z'] for 1..$len;
    \@c
}

sub get_regex ($) {
    my ($cond) = @_;
    local $" = '';
    my $r = join "", map { "[@$_]" } @$cond;
    qr/^$r$/
}

sub remove_cond ($$$) {
    my ($conds, $pos, $ch) = @_;
    return if (scalar @{$conds->[$pos]} == 1);
    return unless grep { $_ eq $ch } @{$conds->[$pos]};
    $conds->[$pos] = [ grep { $_ ne $ch } @{$conds->[$pos]} ]
}

sub add_cond ($$$) {
    my ($conds, $pos, $ch) = @_;
    return if (scalar @{$conds->[$pos]} == 1);
    return if grep { $_ eq $ch } @{$conds->[$pos]};
    push @{$conds->[$pos]}, $ch
}

sub update_conds ($$$$) {
    my ($word, $reply, $conds, $len) = @_;
    my %Xes;
    %Xes = ();
    for my $pos (reverse 0..$len-1) {
        my $r = substr $reply, $pos, 1;
        my $ch = substr $word, $pos, 1;

        if ($r eq 'O') {
            $conds->[$pos] = [$ch]
        }

        elsif ($r eq '?') {
            for my $a (0..$len-1) {
                if ($a == $pos) {
                    remove_cond $conds, $a, $ch
                } else {
                    unless (exists $Xes{$a} and $Xes{$a} eq $ch) {
                        add_cond($conds, $a, $ch);
                    }
                }
            }
        }

        elsif ($r eq 'X') {
            $Xes{$pos} = $ch;
            for my $a (0..$len-1) {
                remove_cond $conds, $a, $ch
            }
        }
    }
}

sub uniq ($) {
    my ($data) = @_;
    my %seen; 
    [ grep { !$seen{$_}++ } @$data ]
}

sub filter_wordlist_by_reply ($$$) {
    my ($wl, $word, $reply) = @_;
    return $wl unless $reply =~ /\?/;
    my $newwl = [];
    my $len = length $reply;
    for my $pos (0..$len-1) {
        my $r = substr $reply, $pos, 1;
        my $ch = substr $word, $pos, 1;
        next unless $r eq '?';
        for my $a (0..$len-1) {
            if ($a != $pos) {
                if ('O' ne substr $reply, $a, 1) {
                    push @$newwl, grep { $ch eq substr $_, $a, 1 } @$wl
                }
            }
        }
    }
    uniq $newwl
}

my $len = $ARGV[0] or die "no length";
my $wl = read_cached_wordlist $len;
my $conds = build_conds $len;

my $c=0;
do {
    my $histo = build_histogram $wl;
    my $word = find_best_word $wl, $histo;
    die "no candidates" unless defined $word;
    say $word;
    my $reply = <STDIN>; 
    chomp $reply;
    exit 1 unless length $reply;
    exit 0 if $reply =~ /^O+$/;
    update_conds $word, $reply, $conds, $len;
    $wl = filter_wordlist_by_reply $wl, $word, $reply;
    $wl = [ grep { $_ =~ get_regex $conds } @$wl ]
} while 1
perl Cina goth
sumber
1
Aturan saya awalnya melarang penulisan ke disk, tapi saya membuatnya menjadi pengecualian untuk memungkinkan caching daftar kata, karena yang besar yang saya temukan membuat semuanya sangat lambat untuk diuji :)
SirDarius
Entri ini berfungsi lebih baik daripada upaya saya (belum dipublikasikan). Bisakah Anda menjelaskan sedikit algoritme Anda?
SirDarius
Saya telah menambahkan penjelasan singkat; memperbaiki sedikit pemformatan kode juga.
Chinese perl goth
@ SirDarius: Saya rasa tidak akan ada kerugian jika tes tertentu menggunakan daftar kata yang hanya berisi entri dengan panjang yang sesuai. Meskipun seharusnya tidak terlalu sulit bagi suatu program untuk mengabaikan kata-kata di dalam file yang panjangnya tidak ditentukan, keberadaan kata-kata seperti itu setidaknya akan memperlambat pengujian. Juga, saya bertanya-tanya apakah akan ada nilai dalam memungkinkan pengiriman untuk menentukan program opsional yang, diberikan daftar kata dan N, akan mengirim ke output standar daftar kata diformat dengan cara apa pun yang paling membantu ...
supercat
... dan izinkan program utama untuk menggunakannya daripada daftar kata mentah (jadi jika beberapa pra-analisis diperlukan, itu hanya harus dilakukan satu kali untuk setiap panjang kata, bukan satu kali per game).
supercat