Kelimeyi tahmin et (aka Lingo)


13

Bu zorluğun amacı, mümkün olan en az sayıda denemede bir kelimeyi tahmin edebilen bir program yazmaktır. Lingo TV şovunun konseptine dayanmaktadır ( http://en.wikipedia.org/wiki/Lingo_(US_game_show ).

kurallar

Komut satırında ilk argüman olarak geçen bir kelime uzunluğu göz önüne alındığında, oyuncu programı , standart çıktısında bir tahmin ve ardından tek bir karakter yazarak kelimeyi tahmin etmek için beş deneme dener \n.

Bir tahmin yapıldıktan sonra, program standart girdisinde bir dize alır ve ardından tek bir \nkarakter alır.

Dize, tahmin edilecek kelimeyle aynı uzunluğa sahiptir ve aşağıdaki karakterlerden oluşan bir diziden oluşur:

  • X: yani verilen harf tahmin etmek için kelimede mevcut değil
  • ?: bu, verilen harfin tahmin etmek için kelimede bulunduğu, ancak başka bir yerde olduğu anlamına gelir
  • O: bu, bu konumdaki harfin doğru bir şekilde tahmin edildiği anlamına gelir

Tahmine kelimedir Örneğin, dentsve program haber gönderir dozes, bu alacak OXX?Oçünkü dve sdoğru olduğundan eyanlış edilir ve ove zmevcut değildir.

Bir harf tahmin etmek kelimesinde daha tahmin girişim daha mevcut kez ise, bu olacağını dikkatli olun değil olarak işaretlenmesi ?ve Otahmin etmek kelimenin harfi tekrarlarını sayısından daha fazla kez. Örneğin, tahmin edilecek kelime coziesve program gönderirse tosses, alınacak XOXXOOtek bir tane olduğu siçin bu kodu alır.

Kelimeler bir ingilizce kelime listesinden seçilir. Program tarafından gönderilen kelime doğru uzunlukta geçerli bir kelime değilse, girişim otomatik bir hata olarak kabul edilir ve yalnızca X'döndürülür.
Oynatıcı programı wordlist.txt, geçerli çalışma dizininde her satırda bir sözcük adı verilen ve içeren bir dosyanın bulunduğunu ve gerektiğinde okunabileceğini varsaymalıdır .
Tahminler yalnızca alfabetik küçük harfli karakterlerden ( [a-z]) oluşmalıdır .
Program için başka ağ veya dosya işlemlerine izin verilmez.

Oyun yalnızca oluşan bir dize Odöndürüldüğünde veya program 5 deneme yaptıktan ve kelimeyi tahmin edemedikten sonra sona erer .

puanlama

Bir oyunun puanı verilen formülle verilir:

score = 100 * (6 - number_of_attempts)

Dolayısıyla, kelime ilk denemede doğru tahmin edilirse, 500 puan verilir. Son deneme 100 puan değerindedir.

Kelimeyi tahmin etmemek sıfır puan verir.

Çukur

Oyuncu programları, her bir kelime uzunluğu için 4 ila 13 karakter arasında 100 rastgele kelime tahmin etmeye çalışılarak değerlendirilecektir .
Rastgele kelime seçimi önceden yapılacaktır, böylece tüm girişler aynı kelimeleri tahmin etmek zorunda kalacaktır.

Kazanan program ve kabul edilen cevap, en yüksek puana ulaşan program olacaktır.

Programlar, https://github.com/noirotm/lingo adresindeki kod kullanılarak bir Ubuntu sanal makinesinde çalıştırılacaktır . Herhangi bir dilde uygulamalar, bunları derlemek ve / veya çalıştırmak için makul talimatlar sağlandığı sürece kabul edilir.

Git deposunda ruby'de birkaç test uygulaması sağlıyorum, onlardan ilham almaktan çekinmeyin.

Bu soru, meydan okuyucuların girişlerini iyileştirebilmeleri için düzenli olarak yayınlanan cevaplar sıralamasıyla güncellenecektir.

Resmi nihai değerlendirme 1 Temmuz'da yapılacak .

Güncelleme

Girişler artık wordlistN.txt4 ile 13 arasında N için geçerli kelime uzunluğu için kelime listesini okuma hızını artıracak dosyaların varlığını varsayabilir .

Örneğin, wordlist4.txtdört harfli kelimelerin wordlist10.txttümünü içeren ve on harfli kelimelerin tümünü içeren bir dosya vardır .

İlk tur sonuçları

2014-07-01 tarihinde, aşağıdaki sonuçlarla birlikte üç kayıt gönderildi:

                        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)

Tüm kayıtlar, @ edc65'in C ++ 's girişi olmak üzere, tutarlı bir şekilde kazanan bir performans sergiledi.

Tüm yarışmacılar oldukça harika. Şimdiye kadar @ chinese-perl-goth'u bile yenemedim.
Daha fazla giriş gönderilirse, başka bir değerlendirme yapılacaktır. Daha iyi yapabileceğinizi düşünüyorsanız, mevcut girişler de geliştirilebilir.


1
Açıklığa kavuşturmak için: program kelimeyi tahmin etmek için 6'dan fazla deneme yaparsa, negatif puan mı yoksa sadece sıfır mı? Başka bir deyişle, 6 olumsuz noktadan kaçınmaya çalıştıktan sonra programdan çıkmak için mantığa ihtiyacımız var mı? (Program kelimeyi tahmin
edemezse

1
@ZoveGames beş denemeden sonra programınız çıkmalı, ancak oyun motoru bunu yapmayı reddederse zorla sonlandıracaktır :)
SirDarius

1
Evet, doğru, Python için endişelenme, birinci sınıf bir vatandaş, bu yüzden bazı python kodlarını çalıştırırken sorun
yaşamayacağım

1
@justhalf Bunun için çok teşekkürler! Sonunda devam edebilirim!
MisterBla

1
@justhalf gerçekten iyi bir fikir, bunu uygulamaya çalışacağım
SirDarius

Yanıtlar:


5

C ++ 267700 Puanlar

Eski bir MasterMind motorundan bir taşıma.
MasterMind'in Farkları:

  • Daha fazla alan
  • Daha fazla sembol
  • Daha büyük çözüm alanı (ancak çok fazla değil, çünkü tüm sembol kombinasyonuna izin verilmiyor)
  • Yanıt çok bilgilendiricidir, bu yüzden her tahminden sonra daha fazla bilgiye sahibiz
  • Yanıt üretmek daha yavaştır ve bu çok yazık çünkü algoritmam bunu çok yapmak zorunda.

Temel fikir, çözüm alanını en aza indiren kelimeyi seçmektir. Algoritma ilk tahmin için gerçekten yavaştır (yani 'günler'), ancak en iyi ilk tahmin sadece len kelimesine bağlıdır, bu yüzden kaynakta kodlanmıştır. Diğer tahminler saniyeler içinde yapılır.

Kod

(G ++ -O3 ile derleyin)

#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;
}

Puanlarım

Lingo ile değerlendirme, 100 mermi:

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

Toplam 265'100

Kendi değerlendirdiği puanlar

İşte tüm kelime listesinde ortalama puanlarım. Testler sırasında algoritmanın bazı detayları değiştiği için tamamen yeniden yüklenemez.

 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

Bu rakamlara göre, ortalama puanım 257.800 civarında olmalı

PIT PUANI

Sonunda Ruby'yi kurdum, şimdi bir 'resmi' puanım var:

    4       5       6       7       8       9      10      11      12      13   TOTAL
10700   16300   22000   25700   27400   30300   32000   33800   34700   34800   267700

Niyetim böyle bir şey yaratmaktı. Ne yazık ki çözüm alanını gerçekten en aza nasıl bulacağımı bulamadım, bu yüzden ona yaklaştım. Ve benimki Python'da, bu yüzden daha yavaş, haha. Ayrıca ilk tahminin kodunu da yazdım. Sizinki kısa dizeler için kesinlikle benimkinden daha iyi. Uygulamamı karşılaştırmak için aynı girdi seti üzerinde de test edebilir misiniz? Ayrıca oldukça farklı ilk tahminlerimiz var.
justhalf

@ justhalf lingo.go ile bazı turlar denedim. Çukura bakmadım (Ruby yüklü değil). Puanlarımız yakın, bu bir şans meselesi.
edc65

Sizinki daha iyi olduğunu düşünüyorum, çünkü rapor ettiğiniz ortalama rapor ettiğim puandan daha iyi. Çok daha fazla zaman almanıza rağmen.
justhalf

Bu şimdiye kadarki en güçlü oyuncu gibi görünüyor. Resmi sonucu bugün daha sonra yayınlayacağım, bizi izlemeye devam edin!
SirDarius

Hata! Yukarıdaki yorumum için düzeltme, gönderimin Java'da olduğunu unuttum.
justhalf

5

Java, 249700 puan (testimde Çin Perl Goth'u atıyor)

Güncellenmiş sıralama listesi:

                        4 5 6 7 8 9 10 11 12 13 Toplam
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

İşte eski rütbe listesi pit.rb:

                        4 5 6 7 8 9 10 11 12 13 Toplam
ruby player-example.rb 2004004005001800 1400 1700 1600 3200 4400 15600
ruby player-example2.rb 2700 3200 2500 4300 7300 6300 8200 10400 13300 15000 73200
ruby player-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

** Sıralama **
1: java Lingo (245000)
2: perl chinese_perl_goth.pl (231100)
3: Ruby Player-example3.rb (163700)
4: Ruby Player-example2.rb (73200)
5: Ruby Player-example.rb (15600)

@Chineseperlgoth ile karşılaştırıldığında, daha kısa kelimelerle kaybederim (<6 karakter) ama daha uzun kelimelerle kazanırım (> = 6 karakter).

Fikir @chineseperlgoth'a benziyor, sadece ana fikrim, bir sonraki tahmin için en fazla bilgiyi veren tahminin bulunmasıyla ilgili (aynı uzunluktaki herhangi bir kelime olabilir, geri kalan olasılıklardan biri olmayabilir).

Şu anda hala formülle oynuyorum, ancak yukarıdaki skorbord için aşağıdakiler için minimum verimi sağlayacak kelimeyi seçiyorum:

-num_confusion * entropi

En son sürüm bir sonraki en iyi tahmin bulmak için farklı puanlama kullanır, mevcut tahmin sonra "tek olasılık" sayısını maksimuma çıkarır. Bu, tüm olası adaylar üzerinde budanmış kelime listesindeki (zaman kazanmak için) tüm kelimeleri deneyerek yapılır ve hangi tahminin "tek olasılık" üretmek için daha olası olduğunu görün (yani, bu tahminden sonra sadece bir olası cevap olacaktır) sonraki tahmin.

Örneğin, bu çalışma:

Yeni raund başlayarak, kelime nimettir
Anladım: seora
Gönderildi:? XOXX
Var: topsl
Gönderildi: XOX? X
Var: keşişler
Gönderildi: XO? XO
Anladım: Bewig
Gönderildi: OXXXX
Var: Boons
Gönderildi: OOOOO
100 ile kazanılan raund

İlk üç tahminden, bir yerde bir "n" ile "* oo * s" zaten var ve hala bir mektup daha bulmamız gerekiyor. Şimdi bu algoritmanın güzelliği, bu forma benzeyen kelimeleri tahmin etmek yerine, önceki tahminlerle hiçbir ilgisi olmayan kelimeyi tahmin etmek, daha fazla harf vermeye çalışmak, umarım kayıp mektubu ortaya çıkarmaktır. Bu durumda, eksik "b" pozisyonunu da doğru bir şekilde almak ve doğru son tahminde "boons" ile sonuçlanır.

İşte kod:

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

Harika, bu giriş gerçekten güçlü! Mevcut ipuçlarından bir kelime tahmin edemedikleri zaman, TV şovunda insan oyuncuları benzer bir strateji kullanarak gördüğümü hatırlıyorum.
SirDarius

3

Perl

Hala iyileştirilmesi gereken bir yer var ama en azından dahil edilen örnek oyuncuları yeniyor :)

Kelime listelerini önbelleğe almak için geçerli dizine yazma erişimini varsayar (biraz daha hızlı çalışmasını sağlamak için); yaratacak wordlist.lenN.storkullanarak dosyaları Storable. Bu bir sorunsa, read_cached_wordlistyalnızca kullanılacak kodu kaldırın ve değiştirin read_wordlist.

açıklama

İlk olarak, geçerli kelime listesindeki ( build_histogram) tüm kelimelerde harf frekanslarının bir histogramını oluşturuyorum . Sonra bir sonraki tahminimi seçmem gerekiyor - ki bu tarafından yapılır find_best_word. Puanlama algoritması histogram değerlerini bir araya getiriyor ve daha önce görülen harfleri atlıyor. Bu bana kelime listesindeki en sık kullanılan harfleri içeren bir kelime verir. Belirli bir puanı olan birden fazla kelime varsa, rastgele bir kelime seçerim. Bir kelime bulduktan sonra, onu oyun motoruna gönderiyorum, yanıtı okudum ve sonra onunla faydalı bir şey yapmayı deneyin :)

Bir dizi koşulu, yani bir kelimede belirli bir konumda oluşabilecek harfleri koruyorum. Başlangıçta, bu sadece basittir (['a'..'z'] x $len), ancak yanıtta verilen ipuçlarına dayanarak güncellenir (bkz. update_conds). O zaman bu koşulların dışında bir regex oluşturmak ve üzerinden kelime listesini filtre.

Testler sırasında, yukarıda belirtilen filtrenin ?çok iyi işlemediğini , dolayısıyla ikinci filtrenin ( filter_wordlist_by_reply) işe yaramadığını öğrendim . Bu ?, kelimede farklı bir konumda olduğu gibi işaretlenmiş bir harfin olması ve sözcük listesini buna göre filtrelemesi gerçeğinden yararlanır .

Çözüm bulunmadıkça (veya artık stdin'den okumak mümkün değil, bu da bir hata anlamına gelir), bu adımlar ana döngünün her yinelemesi için tekrarlanır.

kod

#!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

1
Kurallarım başlangıçta diske yazmayı yasakladı, ancak kelime listesinin önbelleğe alınmasına izin vermek için bir istisna yapıyorum, çünkü bulduğum büyük olan her şeyi test etmek için can sıkıcı bir şekilde yavaşlıyor :)
SirDarius

Bu giriş kendi (henüz yayınlanmamış) girişimlerimden daha iyi çalışıyor. Algoritmanızı biraz açıklayabilir misiniz?
SirDarius

Kısa bir açıklama ekledim; kod biçimlendirmesini de biraz düzeltti.
Çin perl goth

@SirDarius: Herhangi bir test sadece uygun uzunluktaki girişleri içeren bir kelime listesi kullanıyorsa herhangi bir kayıp olacağını düşünmüyorum. Bir programın uzunluğu belirtilenden farklı olan dosya içindeki kelimeleri yoksayması aşırı zor olmamakla birlikte, bu tür kelimelerin varlığı en azından testi yavaşlatır. Ayrıca, gönderilerin bir kelime listesi ve N verildiğinde, standart çıktıya en moda ne olursa olsun biçimlendirilmiş bir kelime listesi gönderecek isteğe bağlı bir program belirtmesine izin vermenin değeri olup olmadığını merak ediyorum ...
supercat

... ve ana programın ham kelime listesi yerine bunu kullanmasına izin verin (bu yüzden bazı ön analizler gerekiyorsa, oyun başına bir kez değil, her kelime uzunluğu için sadece bir kez yapılması gerekir).
supercat
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.