猜单词(又名Lingo)


13

这项挑战的目标是编写一个能够以尽可能少的尝试次数猜出一个单词的程序。它基于Lingo电视节目(http://en.wikipedia.org/wiki/Lingo_(US_game_show))的概念。

规则

给定单词长度作为其命令行上的第一个参数传递,播放器程序通过在其标准输出上写一个猜测,然后跟一个字符来进行五次猜测该词的尝试\n

进行猜测之后,程序会在其标准输入上接收一个字符串,并在其后跟随一个\n字符。

该字符串与要猜测的单词具有相同的长度,并且由以下字符序列组成:

  • X:表示要猜测的单词中不存在给定字母
  • ?:表示给定字母出现在要猜测的单词中,但在另一个位置
  • O:表示此位置的字母已正确猜到

例如,如果要猜测的单词是dents,并且程序发送该单词dozes,它将接收到,OXX?O因为ds是正确的,e放错了位置o并且z不存在。

请注意,如果在猜测尝试中出现的字母次数比在猜测单词中出现的次数多,它将不会被标记为?O在猜测单词中出现字母的次数以及次数要多。例如,如果要猜测的单词是cozies且程序发送tosses,则它将接收,XOXXOO因为只有一个s要定位。

单词是从英语单词列表中选择的。如果程序发送的单词不是正确长度的有效单词,则该尝试将被视为自动失败,并且仅X返回。
播放器程序应假定wordlist.txt当前工作目录中存在一个名为且每行包含一个单词的文件,并且可以根据需要读取。
猜测只能由字母小写字母([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文件,以加快读取N的当前单词长度(包括4和13)的单词列表的速度。

例如,有一个wordlist4.txt文件包含所有四个字母词,并wordlist10.txt包含所有十个字母词,依此类推。

第一轮结果

在2014年7月1日,已提交了三个条目,结果如下:

                        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 2014年

1
@ZoveGames经过五次尝试后,您的程序应该退出,但如果游戏引擎拒绝这样做,则游戏引擎将强制终止它:)
SirDarius 2014年

1
@RichardA是的,不用担心Python,它是一等公民,所以我运行一些python代码不会有问题:)
SirDarius 2014年

1
@justhalf非常感谢!我终于可以继续了!
MisterBla 2014年

1
@justhalf确实是个好主意,我会尝试实现这一点
SirDarius 2014年

Answers:


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

我的意图是创建类似这样的东西。las,我找不到真正最小化解决方案空间的方法,因此我近似了它。我的是Python语言,所以它的速度更慢,哈哈。我还对第一个猜测进行了硬编码。对于较短的弦,您的绝对比我的好。您是否也可以在同一组输入上测试我的实现以进行比较?此外,我们还有许多不同的第一手猜测。
Justhalf 2014年

@justhalf我在lingo.go尝试了一些回合。我没有检查坑(我没有安装Ruby)。我们的分数很接近,我认为这是运气的问题。
edc65 2014年

我认为您的效果更好,因为您报告的平均值比我报告的分数更好。尽管您似乎要花费更多的时间。
justhalf 2014年

到目前为止,这似乎是最强的选手。我将在今天晚些时候发布官方结果,敬请期待!
SirDarius 2014年

糟糕,更正了我上面的评论,我忘记了我的提交使用Java。
justhalf 2014年

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

**排名**
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,只是我的主要想法是找到猜测(可以是相同长度的任何单词,不一定是剩余的可能性之一),它可以为下一次猜测提供最多的信息。

目前,我仍在使用公式,但是对于上面的记分板,我选择的单词将产生以下最小值:

-num_confusion *熵

最新版本使用不同的评分来找到下一个最佳猜测,这是在当前猜测之后最大化“单一可能性”的数量。通过对所有可能的候选词尝试修剪后的词表中的所有词(以节省时间),并查看哪个猜测更有可能产生“单一可能性”(也就是说,在此猜测之后将只有一个可能的答案)来完成此操作下一个猜测。

因此,例如此运行:

开始新的一轮,话是福音
得到了:seora
发送:?XOXX
得到了:topsl
发送:XOX?X
得到:和尚
发送:XO?XO
得了:bewig
发送:OXXXX
得到了:恩赐
发送:OOOOO
回合以100分获胜

从前三个猜测中,我们已经在某个地方带有“ n”的“ * oo * s”,我们仍然需要找出另外一个字母。现在,该算法的优点在于,它不用猜测与该形式相似的单词,而是猜测与先前的猜测完全无关的单词,从而尝试给出更多字母,并希望揭示出丢失的字母。在这种情况下,它碰巧也正确地获得了丢失的“ b”的位置,并以正确的最终猜测“恩赐”结束。

这是代码:

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

太棒了,此条目非常强大!我记得当电视节目中的人类玩家无法从当前线索中猜出一个单词时,他们会使用类似的策略。
SirDarius 2014年

3

佩尔

仍有改进的空间,但至少可以击败示例球员:)

假定对当前目录有写访问权限以缓存单词列表(以使其运行更快);将wordlist.lenN.stor使用创建文件Storable。如果这是一个问题,请删除read_cached_wordlist并更改代码以使用just read_wordlist

说明

首先,我建立了当前单词表(build_histogram)中所有单词的字母频率直方图。然后,我需要选择下一个猜测-由来完成find_best_word。评分算法只是将直方图的值加在一起,跳过已经看到的字母。这给了我一个单词,其中包含单词表中最频繁出现的字母。如果给定分数有多个单词,我会随机选择一个。找到一个单词后,我将其发送到游戏引擎,阅读回复,然后尝试对它进行一些有用的操作:)

我维护一组条件,即单词中给定位置可能出现的字母。开始时,这很简单(['a'..'z'] x $len),但是会根据回复中给出的提示进行更新(请参阅参考资料update_conds)。然后,我根据这些条件构建一个正则表达式,并通过它过滤单词列表。

在测试期间,我发现前面提到的过滤效果?不太好,因此使用了第二个过滤器(filter_wordlist_by_reply)。这利用了以下事实:标记为的字母?出现在单词中的其他位置,并相应地过滤单词列表。

除非找到解决方案(否则无法再从stdin读取,这意味着失败),则对主循环的每次迭代都重复这些步骤。

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

该条目比我自己的尝试(尚未发布)要好。您能解释一下您的算法吗?
SirDarius 2014年

我添加了简短的解释;修复了代码格式化的问题。
Chinese perl goth

@SirDarius:我认为,如果任何特定的测试使用仅包含适当长度条目的单词列表,就不会有任何损失。尽管程序忽略文件中长度不是指定长度的单词并不太困难,但是这些单词的存在至少会减慢测试速度。另外,我不知道允许提交者指定一个可选程序是否有价值,给定单词列表和N,该程序将以最有用的方式格式化的单词列表发送到标准输出...
supercat

...并允许主程序使用该列表而不是原始单词列表(因此,如果需要进行一些预分析,则每个单词长度只需要执行一次,而不是每个游戏一次)。
supercat 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.