Đoán từ (còn gọi là Lingo)


13

Mục tiêu của thử thách này là viết một chương trình có thể đoán một từ trong số lần thử nhỏ nhất có thể. Nó dựa trên khái niệm của chương trình truyền hình Lingo ( http://en.wikipedia.org/wiki/Lingo_(US_game_show) ).

Quy tắc

Với một độ dài từ được truyền làm đối số đầu tiên trên dòng lệnh của nó, chương trình người chơi sẽ xử lý năm lần thử đoán từ bằng cách viết một phỏng đoán trên đầu ra tiêu chuẩn của nó theo sau là một \nký tự.

Sau khi đoán, chương trình nhận được một chuỗi trên đầu vào tiêu chuẩn của nó, cũng theo sau là một \nký tự.

Chuỗi có cùng độ dài với từ để đoán và bao gồm một chuỗi các ký tự sau:

  • X: có nghĩa là chữ cái đã cho không có trong từ để đoán
  • ?: có nghĩa là chữ cái đã cho có mặt trong từ để đoán, nhưng tại một vị trí khác
  • O: có nghĩa là chữ cái ở vị trí này đã được đoán chính xác

Ví dụ: nếu từ để đoán là dentsvà chương trình gửi từ đó dozes, nó sẽ nhận được OXX?Odslà chính xác, ebị đặt sai vị trí ozkhông có mặt.

Hãy cẩn thận rằng nếu một bức thư là lần hiện diện ở những nỗ lực đoán hơn trong từ đoán, nó sẽ không được đánh dấu là ?Olần nhiều hơn số lần xuất hiện của chữ trong từ để đoán. Ví dụ: nếu từ để đoán là coziesvà chương trình gửi tosses, nó sẽ nhận được XOXXOOvì chỉ có một từ sđể định vị.

Từ được chọn từ một danh sách từ tiếng Anh. Nếu từ được gửi bởi chương trình không phải là một từ hợp lệ có độ dài chính xác, thì lần thử đó được coi là một lỗi tự động và chỉ Xđược trả về.
Chương trình trình phát nên giả sử rằng một tệp có tên wordlist.txtvà chứa một từ trên mỗi dòng có trong thư mục làm việc hiện tại và có thể được đọc khi cần thiết.
Đoán chỉ nên bao gồm các ký tự chữ thường ( [a-z]).
Không có hoạt động mạng hoặc tập tin khác được phép cho chương trình.

Trò chơi kết thúc khi một chuỗi chỉ bao gồm Ođược trả về hoặc sau khi chương trình đã thực hiện 5 lần thử và không thể đoán từ.

Chấm điểm

Điểm của trò chơi được tính theo công thức đã cho:

score = 100 * (6 - number_of_attempts)

Vì vậy, nếu từ được đoán chính xác trong lần thử đầu tiên, 500 điểm sẽ được đưa ra. Lần thử cuối cùng có giá trị 100 điểm.

Không đoán được từ cho điểm không.

Cái hố

Các chương trình người chơi sẽ được đánh giá bằng cách cố gắng để họ đoán 100 từ ngẫu nhiên cho mỗi độ dài từ 4 đến 13 ký tự.
Lựa chọn từ ngẫu nhiên sẽ được thực hiện trước vì vậy tất cả các mục sẽ phải đoán cùng một từ.

Chương trình chiến thắng, và câu trả lời được chấp nhận, sẽ là người đạt được điểm số cao nhất.

Các chương trình sẽ được chạy trong một máy ảo Ubuntu, sử dụng mã tại https://github.com/noirotm/lingo . Việc triển khai bằng bất kỳ ngôn ngữ nào cũng được chấp nhận miễn là các hướng dẫn hợp lý để biên dịch và / hoặc chạy chúng được cung cấp.

Tôi đang cung cấp một vài triển khai thử nghiệm trong ruby ​​trong kho git, vui lòng lấy cảm hứng từ chúng.

Câu hỏi này sẽ được cập nhật định kỳ với bảng xếp hạng cho các câu trả lời được công bố để những người thách thức có thể cải thiện các mục của họ.

Đánh giá chính thức cuối cùng sẽ diễn ra vào ngày 1 tháng Bảy .

Cập nhật

Các mục nhập bây giờ có thể giả sử sự hiện diện của wordlistN.txtcác tệp để tăng tốc độ đọc danh sách từ cho độ dài từ hiện tại cho N trong khoảng từ 4 đến 13.

Ví dụ, có một wordlist4.txttệp chứa tất cả bốn từ chữ cái và wordlist10.txtchứa tất cả mười từ chữ cái, v.v.

Kết quả vòng một

Vào ngày 2014 / 07-01, ba mục đã được gửi, với kết quả như sau:

                        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ất cả các mục được thực hiện một cách nhất quán, với một người chiến thắng rõ ràng, là mục của C ++ của @ edc65.

Tất cả các thí sinh đều khá tuyệt vời. Cho đến nay tôi đã không thể đánh bại @ Trung Quốc-perl-goth.
Nếu nhiều mục được gửi, đánh giá khác sẽ diễn ra. Các mục hiện tại cũng có thể được cải thiện nếu bạn cảm thấy mình có thể làm tốt hơn.


1
Chỉ cần làm rõ: nếu chương trình mất hơn 6 lần cố gắng đoán từ, nó có nhận được điểm âm hay chỉ bằng không? Nói cách khác, chúng ta có cần logic để thoát khỏi chương trình sau 6 lần cố gắng tránh các điểm tiêu cực không? (Quy tắc nói không có điểm nếu chương trình không đoán được từ)
DankMeme

1
@ZoveGames sau năm lần thử, chương trình của bạn sẽ thoát, nhưng công cụ trò chơi sẽ buộc chấm dứt nếu nó từ chối làm như vậy :)
SirDarius

1
@RichardA đúng rồi, đừng lo lắng về Python, nó là một công dân hạng nhất, vì vậy tôi sẽ không gặp vấn đề gì khi chạy một số mã trăn :)
SirDarius

1
@justhalf Cảm ơn rất nhiều vì điều đó! Cuối cùng tôi cũng có thể tiếp tục!
MisterBla

1
@justhalf ý tưởng tốt thực sự, tôi sẽ cố gắng thực hiện điều đó
SirDarius

Câu trả lời:


5

C ++ 267700 điểm

Một porting từ một động cơ MasterMind cũ.
Sự khác biệt từ MasterMind:

  • Nhiều khe hơn
  • Thêm biểu tượng
  • Không gian giải pháp lớn hơn (nhưng không quá nhiều, vì không cho phép kết hợp tất cả các biểu tượng)
  • Phản hồi có nhiều thông tin, vì vậy chúng tôi có thêm thông tin sau mỗi lần đoán
  • Phản hồi chậm hơn để tạo ra và đó là một điều đáng tiếc vì thuật toán của tôi phải làm điều đó rất nhiều.

Ý tưởng cơ bản là chọn từ làm giảm thiểu không gian giải pháp. Thuật toán thực sự chậm đối với lần đoán đầu tiên (ý tôi là 'ngày'), nhưng lần đoán đầu tiên tốt nhất chỉ phụ thuộc vào từ len, do đó, nó được mã hóa cứng trong nguồn. Các dự đoán khác được thực hiện trong vài giây.

Mật mã

(Biên dịch với 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;
}

Điểm của tôi

Đánh giá với biệt ngữ, 100 vòng:

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

Tổng số 265'100

Điểm tự đánh giá

Dưới đây là điểm trung bình của tôi, được ghi trên toàn bộ danh sách từ. Không hoàn toàn đáng tin cậy vì một số chi tiết của thuật toán đã thay đổi trong các thử nghiệm.

 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

Theo những con số này, điểm trung bình của tôi phải ở mức gần 256'800

CỬA HÀNG PIT

Cuối cùng tôi đã cài đặt Ruby, vì vậy bây giờ tôi có điểm 'chính thức':

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

Ý định của tôi là tạo ra một cái gì đó như thế này. Than ôi tôi không thể tìm thấy làm thế nào để thực sự giảm thiểu không gian giải pháp, vì vậy tôi đã xấp xỉ nó. Và của tôi là trong Python, vì vậy nó thậm chí còn chậm hơn, haha. Tôi cũng đã mã hóa lần đoán đầu tiên. Của bạn chắc chắn là tốt hơn của tôi cho các chuỗi ngắn hơn. Bạn có thể kiểm tra với việc triển khai của tôi cũng trên cùng một bộ đầu vào để so sánh không? Ngoài ra, chúng tôi có khá nhiều dự đoán đầu tiên.
vừa rồi

@justhalf Mình đã thử vài vòng với lingo.go. Tôi đã không kiểm tra với hố (Tôi chưa cài đặt Ruby). Điểm số của chúng tôi rất gần, đó là vấn đề may mắn tôi nghĩ.
edc65

Bạn nghĩ tốt hơn, vì trung bình báo cáo của bạn tốt hơn điểm số mà tôi đã báo cáo. Mặc dù bạn dường như mất nhiều thời gian hơn.
vừa rồi

Đây dường như là người chơi mạnh nhất cho đến nay. Tôi sẽ chạy kết quả chính thức sau ngày hôm nay, hãy theo dõi!
SirDarius

Rất tiếc, chỉnh sửa cho nhận xét của tôi ở trên, tôi quên rằng bài đăng của tôi là bằng Java.
cần

5

Java, 249700 điểm (vượt qua Perl Goth của Trung Quốc trong bài kiểm tra của tôi)

Cập nhật danh sách xếp hạng:

                        4 5 6 7 8 9 10 11 12 13 Tổng cộng
perl Trung Quốc_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

Đây là danh sách xếp hạng bằng cách sử dụng pit.rb:

                        4 5 6 7 8 9 10 11 12 13 Tổng cộng
người chơi ruby-example.rb 200 400 400 500 1800 1400 1700 1600 3200 4400 15600
người chơi ruby-example2.rb 2700 3200 2500 4300 7300 6300 8200 10400 13300 15000 73200
người chơi ruby-example3.rb 4500 7400 9900 13700 15400 19000 19600 22300 24600 27300 163700
perl Trung Quốc_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

** Xếp hạng **
1: java Lingo (245000)
2: perl Trung Quốc_perl_goth.pl (231100)
3: người chơi ruby-example3.rb (163700)
4: người chơi ruby-example2.rb (73200)
5: người chơi ruby-example.rb (15600)

So với @chineseperlgoth, tôi thua bằng những từ ngắn hơn (<6 ký tự) nhưng tôi thắng bằng những từ dài hơn (> = 6 ký tự).

Ý tưởng tương tự như @chineseperlgoth, chỉ là ý tưởng chính của tôi là tìm ra dự đoán (có thể là bất kỳ từ nào có cùng độ dài, không nhất thiết là một trong những khả năng còn lại) cung cấp nhiều thông tin nhất cho lần đoán tiếp theo.

Hiện tại tôi vẫn đang chơi với công thức, nhưng đối với bảng điểm ở trên, tôi chọn từ sẽ mang lại mức tối thiểu cho:

-num_confusion * entropy

Phiên bản mới nhất sử dụng cách tính điểm khác nhau để tìm ra dự đoán tốt nhất tiếp theo, đó là tối đa hóa số lượng "khả năng duy nhất" sau lần đoán hiện tại. Điều này được thực hiện bằng cách thử tất cả các từ trong danh sách từ được cắt tỉa (để tiết kiệm thời gian) cho tất cả các ứng cử viên có thể và xem dự đoán nào có thể xảy ra hơn để tạo ra "khả năng duy nhất" (nghĩa là, sau lần đoán này sẽ chỉ có một câu trả lời khả dĩ) cho đoán tiếp theo.

Vì vậy, ví dụ này chạy:

Bắt đầu vòng mới, từ là boong
Đã: seora
Đã gửi :? XOXX
Có: ngọn
Đã gửi: XOX? X
Có: các nhà sư
Đã gửi: XO? XO
Có: bewig
Đã gửi: OXXXX
Có: bồ câu
Đã gửi: OOOOO
Vòng thắng với số điểm 100

Từ ba lần đoán đầu tiên, chúng tôi đã có "* oo * s" với một "n" ở đâu đó và chúng tôi vẫn cần phải tìm ra thêm một chữ cái nữa. Bây giờ, cái hay của thuật toán này là thay vì đoán các từ giống với dạng đó, thay vào đó, nó đoán từ không liên quan đến các lần đoán trước đó, cố gắng đưa ra nhiều chữ cái hơn, hy vọng tiết lộ chữ cái bị thiếu. Trong trường hợp này, điều đó cũng xảy ra để có được vị trí cho "b" bị thiếu một cách chính xác và kết luận với dự đoán chính xác cuối cùng là "những con lợn rừng".

Đây là mã:

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

Tuyệt vời, mục này là mạnh mẽ nghiêm trọng! Tôi nhớ đã thấy những người chơi trong chương trình TV sử dụng một chiến lược tương tự khi họ không thể đoán được từ nào trong các manh mối hiện tại.
SirDarius

3

Perl

Vẫn còn một số chỗ cần cải thiện nhưng ít nhất nó cũng đánh bại những người chơi ví dụ đi kèm :)

Giả sử truy cập ghi vào thư mục hiện tại để lưu danh sách từ (để làm cho nó chạy nhanh hơn một chút); sẽ tạo wordlist.lenN.stortập tin bằng cách sử dụng Storable. Nếu đây là một vấn đề, loại bỏ read_cached_wordlistvà thay đổi mã để sử dụng chỉ read_wordlist.

Giải trình

Đầu tiên, tôi xây dựng một biểu đồ tần số chữ cái trong tất cả các từ trong danh sách từ hiện tại ( build_histogram). Sau đó, tôi cần chọn dự đoán tiếp theo của mình - được thực hiện bởi find_best_word. Thuật toán chấm điểm chỉ là thêm các giá trị biểu đồ lại với nhau, bỏ qua các chữ cái đã thấy. Điều này cho tôi một từ có chứa các chữ cái thường xuyên nhất trong danh sách từ. Nếu có nhiều hơn một từ với số điểm nhất định, tôi chọn ngẫu nhiên một từ. Tìm được một từ, tôi gửi nó cho công cụ trò chơi, đọc câu trả lời sau đó thử và làm một cái gì đó hữu ích với nó :)

Tôi duy trì một tập hợp các điều kiện, nghĩa là các chữ cái có thể xảy ra tại một vị trí nhất định trong một từ. Khi bắt đầu, điều này chỉ đơn giản (['a'..'z'] x $len), nhưng nó được cập nhật dựa trên các gợi ý được đưa ra trong trả lời (xem update_conds). Tôi xây dựng một regex từ những điều kiện đó sau đó và lọc danh sách từ thông qua nó.

Trong các thử nghiệm, tôi đã phát hiện ra rằng bộ lọc nói trên không xử lý ?quá tốt, do đó bộ lọc thứ hai ( filter_wordlist_by_reply). Điều này lợi dụng thực tế là một chữ cái được đánh dấu là ?xuất hiện trong từ ở một vị trí khác và lọc danh sách từ cho phù hợp.

Các bước này được lặp lại cho mỗi lần lặp của vòng lặp chính, trừ khi tìm thấy giải pháp (hoặc không thể đọc từ stdin nữa, điều đó có nghĩa là thất bại).

#!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
Các quy tắc của tôi ban đầu cấm ghi vào đĩa, nhưng tôi biến nó thành một ngoại lệ để cho phép lưu vào danh sách từ, bởi vì quy tắc lớn mà tôi tìm thấy khiến toàn bộ điều này trở nên khó kiểm tra :)
SirDarius

Mục này hoạt động tốt hơn so với các nỗ lực của tôi (chưa được công bố). Bạn có thể giải thích thuật toán của bạn một chút?
SirDarius

Tôi đã thêm một lời giải thích ngắn; sửa lỗi định dạng mã một chút là tốt.
tiếng Trung Quốc perl goth

@SirDarius: Tôi không nghĩ sẽ có bất kỳ tổn thất nào nếu bất kỳ bài kiểm tra cụ thể nào sử dụng danh sách từ chỉ chứa các mục có độ dài phù hợp. Mặc dù chương trình không nên quá khó để bỏ qua các từ trong tệp có độ dài khác với chỉ định, sự tồn tại của các từ đó sẽ làm chậm quá trình kiểm tra. Ngoài ra, tôi tự hỏi liệu sẽ có giá trị trong việc cho phép đệ trình chỉ định một chương trình tùy chọn, được đưa ra một danh sách từ và N, sẽ gửi cho đầu ra tiêu chuẩn một danh sách từ được định dạng theo bất cứ cách nào hữu ích nhất ...
supercat

... và cho phép chương trình chính sử dụng danh sách đó thay vì danh sách từ thô (vì vậy nếu cần phân tích trước, nó sẽ chỉ phải được thực hiện một lần cho mỗi độ dài từ, thay vì một lần cho mỗi trò chơi).
supercat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.