คาดเดาคำ (อาคา Lingo)


13

เป้าหมายของความท้าทายนี้คือการเขียนโปรแกรมที่สามารถคาดเดาคำด้วยจำนวนครั้งที่น้อยที่สุด มันขึ้นอยู่กับแนวคิดของรายการทีวี Lingo ( http://en.wikipedia.org/wiki/Lingo_(US_game_show )

กฎระเบียบ

ได้รับความยาวของคำผ่านเป็นอาร์กิวเมนต์แรกในบรรทัดคำสั่งของตนพ้นโปรแกรมเล่นห้าพยายามที่จะคาดเดาคำโดยการเขียนเดาการส่งออกมาตรฐานของมันตามมาด้วยซิงเกิ้ล\nของตัวละคร

หลังจากทำการเดาแล้วโปรแกรมจะรับสตริงจากอินพุตมาตรฐานตามด้วย\nอักขระตัวเดียว

สตริงมีความยาวเท่ากับคำที่จะเดาและประกอบด้วยลำดับของอักขระต่อไปนี้:

  • X: ซึ่งหมายความว่าจดหมายที่ให้นั้นไม่ปรากฏในคำที่จะคาดเดา
  • ?: ซึ่งหมายความว่าจดหมายที่ให้นั้นมีอยู่ในคำที่จะคาดเดา แต่ในสถานที่อื่น
  • O: ซึ่งหมายความว่าจดหมายในตำแหน่งนี้ถูกเดาอย่างถูกต้อง

ตัวอย่างเช่นหากคำที่จะคาดเดาคือdentsและโปรแกรมส่งคำdozesนั้นจะได้รับOXX?Oเพราะdและsถูกต้องถูกeวางผิดที่และoและzไม่ได้อยู่

ระวังว่าถ้าตัวอักษรนั้นมีอยู่หลายครั้งในความพยายามที่คาดเดามากกว่าในคำที่จะคาดเดามันจะไม่ถูกทำเครื่องหมายเป็น?และOเวลามากกว่าจำนวนที่เกิดขึ้นของตัวอักษรในคำที่จะคาดเดา ตัวอย่างเช่นหากคำที่จะคาดเดาคือcoziesและโปรแกรมส่งtossesมันจะได้รับXOXXOOเพราะมีเพียงหนึ่งคำsในการค้นหา

เลือกคำจากรายการคำศัพท์ภาษาอังกฤษ หากคำที่ส่งมาจากโปรแกรมไม่ใช่คำที่ถูกต้องตามความยาวที่ถูกต้องความพยายามนั้นจะถือเป็นความล้มเหลวอัตโนมัติและXจะส่งคืนเฉพาะ
โปรแกรมเล่นควรสันนิษฐานว่าไฟล์ชื่อwordlist.txtและมีหนึ่งคำต่อบรรทัดอยู่ในไดเรกทอรีการทำงานปัจจุบันและสามารถอ่านได้ตามความจำเป็น
การคาดเดาควรประกอบด้วยอักขระตัวพิมพ์เล็กและตัวอักษร ( [a-z]) เท่านั้น
ไม่มีการดำเนินการเครือข่ายหรือไฟล์อื่น ๆ สำหรับโปรแกรม

เกมจะจบลงเมื่อมีการOส่งคืนสตริงที่ประกอบไปด้วยเท่านั้นหรือหลังจากโปรแกรมได้ทำไปแล้ว 5 ครั้งและไม่สามารถเดาคำศัพท์ได้

เกณฑ์การให้คะแนน

คะแนนของเกมจะได้รับจากสูตรที่กำหนด:

score = 100 * (6 - number_of_attempts)

ดังนั้นหากเดาคำถูกต้องในการลองครั้งแรกจะได้รับ 500 คะแนน ความพยายามครั้งสุดท้ายมีค่า 100 คะแนน

ความล้มเหลวในการเดาคำให้เป็นศูนย์คะแนน

หลุม

โปรแกรมของผู้เล่นจะถูกประเมินโดยพยายามให้พวกเขาเดาคำศัพท์สุ่ม100คำสำหรับความยาวแต่ละคำระหว่าง 4 ถึง 13 ตัวอักษรโดยรวม
การเลือกคำแบบสุ่มจะทำล่วงหน้าเพื่อให้ทุกรายการจะต้องเดาคำเดียวกัน

โปรแกรมที่ชนะและคำตอบที่ได้รับการยอมรับจะเป็นหนึ่งในคะแนนสูงสุด

โปรแกรมจะทำงานในเครื่องเสมือน Ubuntu โดยใช้รหัสที่https://github.com/noirotm/lingo ยอมรับการใช้งานในภาษาใด ๆ ตราบเท่าที่มีคำแนะนำที่เหมาะสมในการรวบรวมและ / หรือเรียกใช้พวกเขามีให้

ฉันให้ทดสอบการใช้งานไม่กี่ในทับทิมในพื้นที่เก็บข้อมูลคอมไพล์อย่าลังเลที่จะรับแรงบันดาลใจจากพวกเขา

คำถามนี้จะได้รับการอัปเดตเป็นระยะเพื่อจัดทำคำตอบที่เผยแพร่เพื่อให้ผู้ท้าชิงสามารถปรับปรุงรายการได้

การประเมินผลขั้นสุดท้ายอย่างเป็นทางการจะมีขึ้นใน1 กรกฎาคม

ปรับปรุง

รายการสามารถสมมติว่ามีwordlistN.txtไฟล์เพื่อเพิ่มความเร็วในการอ่านรายการคำสำหรับความยาวคำปัจจุบันสำหรับ N ระหว่าง 4 และ 13 รวม

ตัวอย่างเช่นมีwordlist4.txtไฟล์ที่มีทั้งสี่คำตัวอักษรและwordlist10.txtมีทั้งสิบคำตัวอักษรและอื่น ๆ

ผลการแข่งขันรอบแรก

ณ วันที่ 2014-07-01 มีการส่งสามรายการพร้อมผลลัพธ์ต่อไปนี้:

                        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)

รายการทั้งหมดดำเนินการอย่างสม่ำเสมอโดยมีผู้ชนะที่ชัดเจนเป็นรายการของ C ++ ของ @ edc65

ผู้เข้าแข่งขันทุกคนนั้นยอดเยี่ยมมาก ฉันไม่สามารถเอาชนะ @ chinese-perl-goth ได้
หากมีการส่งรายการเพิ่มเติมการประเมินผลอื่นจะเกิดขึ้น รายการปัจจุบันสามารถปรับปรุงได้ถ้าคุณรู้สึกว่าคุณสามารถทำได้ดีกว่า


1
เพียงเพื่อชี้แจง: หากโปรแกรมใช้เวลามากกว่า 6 พยายามที่จะเดาคำว่ามันจะได้รับคะแนนเชิงลบหรือเป็นศูนย์? กล่าวอีกนัยหนึ่งเราต้องใช้ตรรกะในการออกจากโปรแกรมหลังจาก 6 พยายามหลีกเลี่ยงจุดลบหรือไม่? (กฎบอกว่าเป็นศูนย์คะแนนถ้าโปรแกรมไม่สามารถเดาคำศัพท์ได้)
DankMemes

1
@ZoveGames หลังจากห้าพยายามโปรแกรมของคุณควรออก แต่เกมเครื่องยนต์กวาดต้อนจะยุติมันถ้ามันปฏิเสธที่จะทำเช่นนั้น :)
SirDarius

1
@RichardA ใช่ขวาไม่ต้องกังวลเกี่ยวกับงูหลามก็เป็นพลเมืองชั้นแรกดังนั้นฉันจะไม่มีปัญหาใช้รหัสหลามบาง :)
SirDarius

1
@ justhalf ขอบคุณมากสำหรับสิ่งนั้น! ในที่สุดฉันก็สามารถทำต่อไปได้!
MisterBla

1
@ เพียงความคิดที่ดีจริง ๆ ฉันจะพยายามที่จะใช้
SirDarius

คำตอบ:


5

C ++ 26,7700 คะแนน

การย้ายจากเอ็นจิ้น 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

คะแนน PIT

ในที่สุดฉันก็ติดตั้ง Ruby ดังนั้นตอนนี้ฉันมีคะแนน 'เป็นทางการ':

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

ความตั้งใจของฉันคือการสร้างบางสิ่งเช่นนี้ อนิจจาฉันไม่สามารถหาวิธีลดพื้นที่โซลูชันได้อย่างแท้จริงดังนั้นฉันจึงประมาณมัน และของฉันอยู่ใน Python ดังนั้นมันจึงช้าลงฮ่าฮ่า ฉันยัง hardcoded เดาแรก ของคุณดีกว่าของฉันแน่นอนสำหรับสตริงที่สั้นกว่า คุณสามารถทดสอบด้วยการนำของฉันไปใช้กับชุดข้อมูลเดียวกันเพื่อเปรียบเทียบได้หรือไม่ นอกจากนี้เรายังมีชุดการเดาแรกที่แตกต่างกัน
justhalf

@ justhalf ฉันลองรอบกับ lingo.go ฉันไม่ได้ตรวจสอบกับหลุม (ฉันไม่ได้ติดตั้งทับทิม) คะแนนของเราใกล้เคียงกันฉันคิดว่าเป็นเรื่องของโชค
edc65

ของคุณดีกว่าฉันคิดว่าเนื่องจากค่าเฉลี่ยของคุณรายงานดีกว่าคะแนนที่ฉันรายงาน แม้ว่าคุณจะดูเหมือนจะใช้เวลามากขึ้น
justhalf

ดูเหมือนว่าจะเป็นผู้เล่นที่แข็งแกร่งที่สุดจนถึงตอนนี้ ฉันจะเรียกใช้ผลอย่างเป็นทางการในวันนี้ติดตามความคืบหน้า!
SirDarius

โอ๊ะโอการแก้ไขสำหรับความคิดเห็นของฉันด้านบนฉันลืมว่าการส่งของฉันอยู่ใน Java
justhalf

5

Java, 24,9700 คะแนน (ชนะ 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 200 400 400 500 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 เป็นเพียงความคิดหลักของฉันเกี่ยวกับการค้นหาการเดา (อาจเป็นคำใด ๆ ที่มีความยาวเท่ากันไม่จำเป็นต้องเป็นหนึ่งในความเป็นไปได้ที่เหลืออยู่) ซึ่งให้ข้อมูลมากที่สุดสำหรับการคาดเดาครั้งต่อไป

ขณะนี้ฉันยังคงเล่นกับสูตรอยู่ แต่สำหรับกระดานคะแนนด้านบนฉันเลือกคำที่จะให้ผลขั้นต่ำสำหรับ:

-num_confusion * เอนโทรปี

เวอร์ชันล่าสุดใช้การให้คะแนนที่แตกต่างกันเพื่อค้นหาการทายที่ดีที่สุดต่อไปซึ่งเป็นการเพิ่มจำนวน "ความเป็นไปได้ครั้งเดียว" สูงสุดหลังจากการเดาปัจจุบัน สิ่งนี้ทำได้โดยการลองทุกคำใน wordlist ที่ถูกตัด (เพื่อประหยัดเวลา) กับผู้สมัครที่เป็นไปได้ทั้งหมดและดูว่าการเดาแบบไหนที่เป็นไปได้มากกว่าในการสร้าง "ความเป็นไปได้ครั้งเดียว" (นั่นคือหลังจากการเดานี้ เดาต่อไป

ตัวอย่างเช่นการวิ่งครั้งนี้:

เริ่มต้นรอบใหม่คำว่า boons
เตรียมพร้อม: seora
ส่งแล้ว:? XOXX
เตรียมพร้อม: topsl
ส่งแล้ว: XOX? X
เตรียมพร้อม: พระภิกษุ
ส่งแล้ว: XO? XO
เตรียมพร้อม: bewig
ส่งแล้ว: OXXXX
เตรียมพร้อม: boons
ส่งแล้ว: OOOOO
รอบชนะด้วยคะแนน 100

จากการคาดเดาสามครั้งแรกเราได้รับ "* oo * s" ด้วย "n" ที่ไหนสักแห่งแล้วและเรายังต้องหาตัวอักษรอีกหนึ่งตัว ตอนนี้ความสวยงามของอัลกอริทึมนี้คือแทนที่จะคาดเดาคำที่คล้ายกับรูปแบบนั้นแทนที่จะคาดเดาคำที่ไม่เกี่ยวข้องกับการเดาก่อนหน้าเลยพยายามให้ตัวอักษรมากขึ้นหวังว่าจะเปิดเผยตัวอักษรที่หายไป ในกรณีนี้จะเกิดขึ้นเพื่อให้ได้ตำแหน่ง "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));
        }
    }
}

เยี่ยมมากรายการนี้แข็งแกร่งมาก! ฉันจำได้ว่าเห็นผู้เล่นคนในรายการทีวีโดยใช้กลยุทธ์ที่คล้ายกันเมื่อพวกเขาไม่สามารถเดาคำจากเบาะแสปัจจุบันได้
SirDarius

3

Perl

ยังคงมีห้องพักสำหรับการปรับปรุง แต่อย่างน้อยก็เป็นตัวอย่างของผู้เล่น :)

สมมติว่าสิทธิ์การเขียนในไดเรกทอรีปัจจุบันสำหรับการแคชรายการคำศัพท์ (เพื่อให้เร็วขึ้นเล็กน้อย); จะสร้างไฟล์โดยใช้wordlist.lenN.stor Storableถ้าเรื่องนี้เป็นปัญหาลบและเปลี่ยนรหัสเพื่อใช้เพียงread_cached_wordlistread_wordlist

คำอธิบาย

ก่อนอื่นฉันสร้างฮิสโตแกรมของความถี่จดหมายในทุกคำในรายการคำปัจจุบัน ( build_histogram) แล้วผมก็ต้องเลือกเดาต่อไปของฉัน - find_best_wordซึ่งจะกระทำโดย อัลกอริทึมการให้คะแนนเป็นเพียงการเพิ่มค่าฮิสโตแกรมร่วมกันโดยข้ามตัวอักษรที่เห็นอยู่แล้ว นี่ทำให้ฉันมีคำที่มีตัวอักษรที่พบบ่อยที่สุดในรายการคำ หากมีมากกว่าหนึ่งคำที่มีคะแนนที่กำหนดฉันเลือกหนึ่งคำแบบสุ่ม เมื่อพบคำฉันส่งมันไปที่เอ็นจิ้นเกมอ่านคำตอบแล้วลองทำสิ่งที่มีประโยชน์กับมัน :)

ฉันคงชุดเงื่อนไขไว้นั่นคือตัวอักษรที่สามารถเกิดขึ้นได้ในตำแหน่งที่กำหนดในคำ ในการเริ่มต้นสิ่งนี้เป็นเรื่องง่าย(['a'..'z'] x $len)แต่ได้รับการอัปเดตโดยอ้างอิงจากคำแนะนำที่ให้ไว้ในการตอบกลับ (ดูupdate_conds) ฉันสร้าง regex จากเงื่อนไขเหล่านั้นแล้วกรองคำศัพท์ผ่านมัน

ในระหว่างการทดสอบฉันพบว่าตัวกรองดังกล่าวไม่สามารถจัดการ?ได้ดีเกินไปดังนั้นตัวกรองที่สอง ( 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
กฎของฉันเขียนห้ามเดิมในดิสก์ แต่ฉันจะทำให้มันมีข้อยกเว้นที่จะอนุญาตให้แคชของรายการคำเพราะขนาดใหญ่ทำให้ผมพบว่าสิ่งที่ทั้ง annoyingly ช้าที่จะทดสอบ :)
SirDarius

รายการนี้ใช้งานได้ดีกว่าความพยายามของฉัน (ยังไม่เผยแพร่) คุณช่วยอธิบายอัลกอริทึมของคุณหน่อยได้ไหม?
SirDarius

ฉันได้เพิ่มคำอธิบายสั้น ๆ ; แก้ไขรหัสการจัดรูปแบบเล็กน้อยเช่นกัน
ชาวจีนชาวเยอรมัน

@SirDarius: ฉันไม่คิดว่าจะมีการสูญเสียใด ๆ หากการทดสอบใด ๆ ที่ใช้รายการคำที่มีเพียงรายการที่มีความยาวที่เหมาะสม ในขณะที่โปรแกรมไม่ควรมองข้ามคำภายในไฟล์ที่มีความยาวมากกว่าที่ระบุ แต่การมีอยู่ของคำดังกล่าวจะทำให้การทดสอบช้าลงอย่างน้อย นอกจากนี้ฉันสงสัยว่าจะมีค่าในการอนุญาตให้ส่งเพื่อระบุโปรแกรมเสริมซึ่งกำหนดรายชื่อคำและ N จะส่งรายการมาตรฐานในรูปแบบรายการคำในรูปแบบที่เป็นประโยชน์มากที่สุด ...
supercat

... และอนุญาตให้โปรแกรมหลักใช้สิ่งนั้นแทนที่จะเป็นรายการคำศัพท์ดิบ (ดังนั้นหากต้องการการวิเคราะห์ล่วงหน้าบางรายการจะต้องทำเพียงครั้งเดียวสำหรับแต่ละความยาวคำแทนที่จะเป็นหนึ่งครั้งต่อเกม)
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.