言葉を推測する(別名Lingo)


13

この課題の目標は、可能な限り少ない試行回数で単語を推測できるプログラムを作成することです。Lingo TVショー(http://en.wikipedia.org/wiki/Lingo_(US_game_show))のコンセプトに基づいています。

ルール

コマンドラインの最初の引数として渡された単語の長さを考えると、プレーヤープログラムは、標準出力に推測を書き込み、その後に単一の文字を書き込むことによって、単語を推測する5回の試行を破棄し\nます。

推測が行われた後、プログラムは標準入力で文字列を受け取り、その後に1 \n文字が続きます。

文字列は推測する単語と同じ長さで、次の文字のシーケンスで構成されます。

  • X:これは、与えられた文字が推測する単語に存在しないことを意味します
  • ?:これは、与えられた文字が推測する単語に存在するが、別の場所にあることを意味します
  • O:これは、この場所の文字が正しく推測されたことを意味します

推測する単語がある場合たとえば、dentsと、プログラムは言葉を送りdozes、それが届きますOXX?Oので、dおよびs正しい、e紛失され、oかつz存在しません。

手紙は推測する単語よりも推測する試みの中で複数回存在する場合、それがされますように注意してくださいではないとしてマークされる?O推測する単語の文字の出現箇所の数よりも多くの倍。たとえば、推測する単語がcoziesで、プログラムがを送信する場合、検索するのは1つだけなのでtosses、受信します。XOXXOOs

単語は英語の単語リストから選択されます。プログラムによって送信された単語が正しい長さの有効な単語でない場合、試行は自動失敗と見なされ、のみXが返されます。
プレーヤープログラムは、wordlist.txt1行に1ワードという名前のファイルが現在の作業ディレクトリに存在し、必要に応じて読み取ることができると想定する必要があります。
推測はアルファベットの小文字([a-z])のみで構成する必要があります。
他のネットワークまたはファイル操作はプログラムに対して許可されていません。

のみで構成される文字列Oが返されるか、プログラムが5回試行して単語を推測できなかったときに、ゲームは終了します。

得点

ゲームのスコアは、次の式で与えられます。

score = 100 * (6 - number_of_attempts)

そのため、最初の試行で単語が正しく推測された場合、500ポイントが与えられます。最後の試行は100ポイントの価値があります。

単語の推測に失敗すると、ゼロポイントが付与されます。

ピット

プレーヤープログラムは、4〜13文字の単語長ごとに100のランダムな単語を推測させることで評価されます。
ランダムな単語選択は事前に行われるため、すべてのエントリは同じ単語を推測する必要があります。

受賞したプログラムと受け入れられた回答が、最高のスコアに到達します。

https://github.com/noirotm/lingoのコードを使用して、プログラムはUbuntu仮想マシンで実行されます。コンパイルおよび/または実行するための合理的な指示が提供されている限り、どの言語での実装も受け入れられます。

私はgitリポジトリのrubyでいくつかのテスト実装を提供していますが、それらから自由にインスピレーションを受けてください。

この質問は公開された回答のランキングで定期的に更新されるため、チャレンジャーはエントリを改善できます。

公式の最終評価は7月1日に行われます。

更新

エントリは、wordlistN.txtファイルの存在を想定して、4〜13のNの現在の単語長の単語リストの読み取りを高速化できます。

たとえば、wordlist4.txt4文字すべての単語を含むファイル、wordlist10.txt10文字すべての単語を含むファイルなどがあります。

最初のラウンドの結果

2014年7月1日の時点で、3つのエントリが提出され、次の結果が得られました。

                        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)

すべてのエントリは、@ edc65のC ++のエントリであり、明確な勝者で一貫して実行されました。

すべての出場者はとても素晴らしいです。これまで@ chinese-perl-gothに勝つことさえできませんでした。
さらにエントリが送信されると、別の評価が行われます。改善できると思われる場合は、現在のエントリを改善することもできます。


1
明確にするために、プログラムが6回以上単語を推測しようとすると、負のポイントが得られるのか、それともゼロになるのか?言い換えれば、マイナスポイントを回避しようとする6回の試行後にプログラムを終了するロジックが必要ですか?(プログラムは、単語を推測するために失敗した場合にルールが零点を言う)
DankMemes

1
@ZoveGamesは5回試行した後、プログラムを終了する必要がありますが、ゲームエンジンはそうしないと強制的に終了します:)
SirDarius

1
@RichardAはい、Pythonを心配しないでください、それは一流の市民ですので、Pythonコードを実行しても問題はありません:)
SirDarius

1
@justhalfそのためにどうもありがとう!やっと続行できます!
MisterBla

1
確かに@justhalf良いアイデア、私はそれを実装しようとするでしょう
SirDarius

回答:


5

C ++ 267700ポイント

古いMasterMindエンジンからの移植。
MasterMindとの違い:

  • より多くのスロット
  • その他の記号
  • より大きなソリューションスペース(ただし、すべてのシンボルの組み合わせが許可されているわけではないため、それほど多くはありません)
  • 応答は非常に有益であるため、推測ごとに詳細情報が得られます
  • 応答は生成が遅くなりますが、それは残念です。私のアルゴリズムが多くのことをしなければならないからです。

基本的な考え方は、ソリューションスペースを最小化する単語を選択することです。アルゴリズムは最初の推測では本当に遅いですが(「日」を意味します)、最良の最初の推測は単語lenのみに依存するため、ソースにハードコーディングされています。他の推測は数秒で行われます。

コード

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

私のスコア

専門用語による評価、100ラウンド:

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

合計265'100

自己評価スコア

ワードリスト全体で採点された私の平均ポイントを以下に示します。テスト中にアルゴリズムの詳細が変更されたため、完全に信頼できるわけではありません。

 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

これらの数値によると、私の平均スコアは257'800に近いはずです

ピットスコア

ついにRubyをインストールしたので、「公式」スコアが得られました。

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

私の意図は、このようなものを作成することでした。残念ながら、ソリューション空間を本当に最小化する方法を見つけることができなかったので、近似しました。そして、私のものはPythonにあるので、さらに遅いです(笑)。最初の推測もハードコーディングしました。あなたのものは、短い弦のために私のものよりも間違いなく優れています。比較するために、同じ入力セットでも実装でテストできますか?また、かなり異なる一連の最初の推測があります。
ちょうど半分14年

@justhalf lingo.goでいくつかのラウンドを試しました。ピットで確認しませんでした(Rubyがインストールされていません)。私たちのスコアは近いです、それは私が思うに運の問題です。
edc65 14年

あなたの報告された平均は私が報告したスコアよりも良いので、あなたの方が良いと思います。かなり時間がかかるようですが。
ちょうど半分14

これはこれまでで最強のプレーヤーのようです。今日は公式結果を発表する予定です。お楽しみに!
SirDarius 14

おっと、上記のコメントを修正しましたが、提出物がJavaであることを忘れていました。
ちょうど半分

5

Java、249700ポイント(私のテストでは、中国語のPerl Gothに勝ちます)

更新されたランクリスト:

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

以下が古いランクリストpit.rbです:

                        4 5 6 7 8 9 10 11 12 13合計
ruby player-example.rb 200400400500 1800 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

**ランキング**
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と比較すると、短い単語(<6文字)では負けますが、長い単語(> = 6文字)では勝ちます。

このアイデアは@chineseperlgothに似ています。私の主なアイデアは、次の推測に最も多くの情報を提供する推測(同じ長さの単語で、必ずしも残りの可能性の1つではない)を見つけることです。

現在、私はまだ数式で遊んでいますが、上記のスコアボードでは、次の最小値が得られる単語を選択します。

-num_confusion *エントロピー

最新バージョンでは、異なるスコアリングを使用して、次に最適な推測を見つけます。これは、現在の推測の後の「単一の可能性」の数を最大化します。これは、可能性のあるすべての候補に対して剪定された単語リストのすべての単語を試して(時間を節約する)、どの推測が「単一の可能性」を生み出す可能性が高いかを確認します次の推測。

したがって、たとえば、この実行:

新ラウンドの開始、言葉は恩恵
わかった:seora
送信済み:?XOXX
わかった:topsl
送信済み:XOX?X
得た:僧ks
送信済み:XO?XO
手に入れました:
送信済み:OXXXX
得た:ブーン
送信済み:OOOOO
ラウンドはスコア100で勝ちました

最初の3つの推測から、どこかに "n"が付いた "* oo * s"がすでに得られているため、さらに1文字を理解する必要があります。このアルゴリズムの利点は、その形式に似た単語を推測する代わりに、以前の推測とはまったく関係のない単語を推測し、より多くの文字を与えようとして、行方不明の文字を明らかにすることです。この場合、欠落している「b」の位置も正しく取得され、正しい最終推測「boons」で終了します。

コードは次のとおりです。

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

素晴らしい、このエントリは非常に強力です!テレビ番組で、現在の手がかりから単語を推測できなかったときに、同様の戦略を使用して人間のプレーヤーを見たのを覚えています。
サーダリウス

3

Perl

まだ改善の余地はありますが、少なくとも同梱されているサンプルプレーヤーには勝っています:)

ワードリストをキャッシュするために現在のディレクトリへの書き込みアクセスを想定しています(少し速く実行するため)。を使用してwordlist.lenN.storファイルを作成しますStorable。これが問題になる場合は、read_cached_wordlist単に使用するようにコードを削除して変更しますread_wordlist

説明

まず、現在の単語リスト(build_histogram)内のすべての単語の文字頻度のヒストグラムを作成します。次に、次の推測を選択する必要がありますfind_best_word。スコアリングアルゴリズムは、すでに見た文字をスキップして、ヒストグラム値を加算するだけです。これにより、単語リストで最も頻繁に使用される文字を含む単語が表示されます。特定のスコアを持つ単語が複数ある場合、ランダムに選択します。単語を見つけたので、それをゲームエンジンに送信し、返信を読んで、それで何か役に立つことを試してみてください:)

一連の条件、つまり、単語内の特定の位置に出現する可能性のある文字を維持します。開始時、これは単純な(['a'..'z'] x $len)ものですが、返信で与えられたヒントに基づいて更新されupdate_condsます(を参照)。それらの条件から正規表現を構築し、それを通して単語リストをフィルタリングします。

テスト中に、前述のフィルタリングは?sをうまく処理できないため、2番目のフィルター(filter_wordlist_by_reply)であることがわかりました。これは?、異なる位置の単語に出現するようにマークされた文字が利用されるという事実を利用し、それに応じて単語リストをフィルタリングします。

これらの手順は、解決策が見つからない限り(または、標準入力から読み取ることができなくなる、つまり失敗を意味しない限り)、メインループの反復ごとに繰り返されます。

コード

#!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
私のディスクに元々禁じ書き込みルール、しかし、私が見つけた大きなものは、テストにうるさく遅い全体を作るので、私は、それ単語リストをキャッシュできるようにする例外作る:)
SirDarius

このエントリは、私自身の(未公開の)試みよりもうまく機能します。アルゴリズムについて少し説明してもらえますか?
サーダリウス

簡単な説明を追加しました。コードの書式設定も少し修正しました。
中国perlゴス

@SirDarius:特定のテストで適切な長さのエントリのみを含む単語リストを使用しても損失はないと思います。指定した長さ以外のファイル内の単語をプログラムが無視することはそれほど難しくないはずですが、そのような単語の存在はテストの速度を遅くします。また、単語リストとNを指定すると、最も役立つ方法でフォーマットされた単語リストを標準出力に送信するオプションのプログラムをサブミッションで指定できるようになることに価値があるのでしょうか...
supercat

...そして、メインプログラムが生の単語リストではなくそれを使用できるようにします(したがって、事前分析が必要な場合は、ゲームごとにではなく、単語の長さごとに1回だけ行う必要があります)。
supercat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.