Errate das Wort (aka Lingo)

13

Das Ziel dieser Herausforderung ist es, ein Programm zu schreiben, das in der Lage ist, ein Wort in möglichst wenigen Versuchen zu erraten. Es basiert auf dem Konzept der Lingo-TV-Show ( http://en.wikipedia.org/wiki/Lingo_(US_game_show) ).

Regeln

Bei einer Wortlänge, die als erstes Argument in der Befehlszeile übergeben wurde, stehen dem Player-Programm fünf Versuche zur Verfügung, das Wort zu erraten, indem auf die Standardausgabe eine Schätzung gefolgt von einem einzelnen \nZeichen geschrieben wird.

Nachdem eine Vermutung durchgeführt wurde, erhält das Programm eine Zeichenfolge in der Standardeingabe, gefolgt von einem einzelnen \nZeichen.

Die Zeichenfolge hat die gleiche Länge wie das zu erratende Wort und besteht aus einer Folge der folgenden Zeichen:

  • X: was bedeutet, dass der angegebene Buchstabe nicht in dem zu erratenden Wort enthalten ist
  • ?: was bedeutet, dass der angegebene Buchstabe im zu erratenden Wort vorhanden ist, aber an einer anderen Stelle
  • O: was bedeutet, dass der Brief an dieser Stelle richtig erraten wurde

Wenn das Wort zu erraten , ist zum Beispiel dents, und das Programm sendet das Wort dozes, wird es erhalten , OXX?Oweil dund srichtig ist, eist fehl am Platz, und ound zist nicht vorhanden.

Achten Sie darauf, dass ein Buchstabe, der mehr als das zu erratende Wort vorkommt, nicht mehr als ?und Omehr als die Anzahl der Vorkommen des Buchstabens im zu erratenden Wort markiert wird . Wenn das zu erratende Wort beispielsweise lautet coziesund das Programm sendet tosses, wird es empfangen, XOXXOOda nur eines gefunden werden kann s.

Wörter werden aus einer englischen Wortliste ausgewählt. Wenn das vom Programm gesendete Wort kein gültiges Wort der richtigen Länge ist, wird der Versuch als automatischer Fehler gewertet und es werden nur Xdie zurückgegeben.
Das Player-Programm sollte davon ausgehen, dass eine Datei mit dem Namen wordlist.txtund einem Wort pro Zeile im aktuellen Arbeitsverzeichnis vorhanden ist und bei Bedarf gelesen werden kann.
Vermutungen sollten nur aus alphabetischen Kleinbuchstaben bestehen ( [a-z]).
Für das Programm sind keine anderen Netzwerk- oder Dateivorgänge zulässig.

Das Spiel endet, wenn eine Zeichenfolge, die nur aus Obesteht, zurückgegeben wird oder nachdem das Programm 5 Versuche unternommen hat und das Wort nicht erraten konnte.

Wertung

Die Punktzahl eines Spiels ergibt sich aus der angegebenen Formel:

score = 100 * (6 - number_of_attempts)

Wenn das Wort also beim ersten Versuch richtig geraten wurde, werden 500 Punkte vergeben. Der letzte Versuch ist 100 Punkte wert.

Wenn Sie das Wort nicht erraten, erhalten Sie null Punkte.

Die Grube

Spielerprogramme werden bewertet, indem versucht wird, 100 zufällige Wörter für jede Wortlänge zwischen einschließlich 4 und 13 Zeichen zu erraten .
Die zufällige Wortauswahl erfolgt im Voraus, sodass alle Einträge die gleichen Wörter erraten müssen.

Das Gewinnerprogramm und die akzeptierte Antwort sind diejenigen, die die höchste Punktzahl erzielen.

Programme werden in einer virtuellen Ubuntu-Maschine ausgeführt, wobei der Code unter https://github.com/noirotm/lingo verwendet wird . Implementierungen in einer beliebigen Sprache werden akzeptiert, sofern angemessene Anweisungen zum Kompilieren und / oder Ausführen bereitgestellt werden.

Ich stelle ein paar Testimplementierungen in Ruby im Git-Repository zur Verfügung. Lassen Sie sich gerne von ihnen inspirieren.

Diese Frage wird regelmäßig mit Ranglisten für veröffentlichte Antworten aktualisiert, damit Herausforderer ihre Einträge verbessern können.

Die offizielle Abschlussbewertung findet am 1. Juli statt .

Aktualisieren

Einträge können nun das Vorhandensein von wordlistN.txtDateien annehmen , um das Lesen der Wortliste für die aktuelle Wortlänge für N zwischen einschließlich 4 und 13 zu beschleunigen.

Beispielsweise gibt es eine wordlist4.txtDatei, die alle vier Buchstabenwörter und wordlist10.txtalle zehn Buchstabenwörter usw. enthält.

Ergebnisse der ersten Runde

Zum Datum des 01.07.2014 wurden drei Einsendungen mit folgenden Ergebnissen eingereicht:

                        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)

Alle Einreichungen verliefen konsistent, mit einem klaren Sieger als C ++ - Einreichungen von @ edc65.

Alle Teilnehmer sind ziemlich genial. Ich konnte @ chinese-perl-goth noch nicht einmal schlagen.
Bei weiteren Einsendungen erfolgt eine erneute Bewertung. Aktuelle Einträge können auch verbessert werden, wenn Sie das Gefühl haben, es besser zu machen.

SirDarius
quelle
1
Nur um zu verdeutlichen: Wenn das Programm mehr als 6 Versuche unternimmt, das Wort zu erraten, erhält es dann negative Punkte oder nur Null? Mit anderen Worten, brauchen wir Logik, um das Programm nach 6 Versuchen zu beenden, um negative Punkte zu vermeiden? (Regeln sagen null Punkte, wenn das Programm das Wort nicht
errät
1
@ZoveGames nach fünf Versuchen sollte Ihr Programm beendet werden, aber die Game Engine wird es zwangsweise beenden, wenn sie dies ablehnt :)
SirDarius
1
@RichardA ja richtig, mach dir keine Sorgen um Python, es ist ein erstklassiger Bürger, also werde ich kein Problem damit haben, Python-Code auszuführen :)
SirDarius
1
@justhalf Vielen Dank dafür! Ich kann endlich weitermachen!
MisterBla
1
@ Justhalf gute Idee in der Tat, ich werde versuchen, das umzusetzen
SirDarius

Antworten:

5

C ++ 267700 Punkte

Eine Portierung von einer alten MasterMind-Engine.
Unterschiede zu MasterMind:

  • Weitere Slots
  • Weitere Symbole
  • Größerer Lösungsraum (aber nicht so viel, da nicht alle Symbolkombinationen zulässig sind)
  • Die Antwort ist sehr informativ, so dass wir nach jeder Vermutung mehr Informationen haben
  • Die Antwort ist langsamer zu generieren und das ist schade, weil mein Algorithmus viel dafür tun muss.

Die Grundidee ist, das Wort zu wählen, das den Lösungsraum minimiert. Der Algorithmus ist für die erste Vermutung (ich meine "Tage") sehr langsam, aber die beste erste Vermutung hängt nur vom Wort len ​​ab, so dass es in der Quelle fest codiert ist. Die anderen Vermutungen sind in Sekunden erledigt.

Der Code

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

Meine Punkte

Auswertung mit Jargon, 100 Runden:

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

Gesamt 265'100

Selbstbewertete Partituren

Hier sind meine Durchschnittspunkte für die gesamte Wortliste. Nicht vollständig zuverlässig, da sich einige Details des Algorithmus während der Tests geändert haben.

 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

Nach diesen Zahlen sollte meine durchschnittliche Punktzahl in der Nähe von 257'800 liegen

PIT SCORE

Als letztes habe ich Ruby installiert, und jetzt habe ich eine "offizielle" Punktzahl:

    4       5       6       7       8       9      10      11      12      13   TOTAL
10700   16300   22000   25700   27400   30300   32000   33800   34700   34800   267700
edc65
quelle
Meine Absicht war es, so etwas zu kreieren. Leider konnte ich nicht herausfinden, wie der Lösungsraum wirklich minimiert werden kann, also habe ich ihn angenähert. Und meins ist in Python, also ist es noch langsamer, haha. Ich habe auch die erste Vermutung festgeschrieben. Deine ist definitiv besser als meine für die kürzeren Saiten. Kannst du mit meiner Implementierung auch die gleichen Eingaben vergleichen? Wir haben auch ganz andere erste Vermutungen.
Nur die Hälfte des
@justhalf Ich habe einige Runden mit lingo.go ausprobiert. Ich habe nicht bei der Grube nachgesehen (ich habe Ruby nicht installiert). Unsere Ergebnisse liegen nahe beieinander, ich denke, es ist eine Frage des Glücks.
Edc65
Deiner ist meiner Meinung nach besser, da dein gemeldeter Durchschnitt besser ist als der von mir gemeldete. Obwohl Sie wesentlich mehr Zeit in Anspruch nehmen.
1.
Dies scheint der bisher stärkste Spieler zu sein. Ich werde das offizielle Ergebnis noch heute veröffentlichen. Bleib dran!
SirDarius
Hoppla, Korrektur für meinen Kommentar oben, ich habe vergessen, dass meine Einreichung in Java ist.
1.
5

Java, 249700 Punkte (schlägt in meinem Test Chinese Perl Goth)

Rangliste aktualisiert:

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

Hier ist die alte Rangliste mit pit.rb:

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

** Ranglisten **
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)

Im Vergleich zu @chineseperlgoth verliere ich in kürzeren Worten (<6 Zeichen), gewinne aber in längeren Worten (> = 6 Zeichen).

Die Idee ist ähnlich wie bei @chineseperlgoth. Meine Hauptidee ist nur, die Vermutung zu finden (kann jedes Wort der gleichen Länge sein, nicht unbedingt eine der verbleibenden Möglichkeiten), die die meisten Informationen für die nächste Vermutung liefert.

Derzeit spiele ich immer noch mit der Formel, aber für die obige Anzeigetafel wähle ich das Wort aus, das das Minimum ergibt für:

-num_confusion * Entropie

Die neueste Version verwendet unterschiedliche Bewertungen, um die nächstbeste Schätzung zu finden, wodurch die Anzahl der "einzelnen Möglichkeiten" nach der aktuellen Schätzung maximiert wird. Dies geschieht, indem alle Wörter in der verkürzten Wortliste (um Zeit zu sparen) auf alle möglichen Kandidaten überprüft werden und ermittelt wird, welche Vermutung wahrscheinlicher ist, um eine "einzelne Möglichkeit" zu ergeben (dh nach dieser Vermutung wird es nur eine mögliche Antwort geben) nächste Vermutung.

Also zum Beispiel diesen Lauf:

Neue Runde beginnen, Wort ist Boons
Got: Seora
Gesendet:? XOXX
Got: topsl
Gesendet: XOX? X
Got: Mönche
Gesendet: XO? XO
Got: Bewig
Gesendet: OXXXX
Got: Boons
Gesendet: OOOOO
Runde gewonnen mit 100 Punkten

Aus den ersten drei Vermutungen haben wir bereits irgendwo "* oo * s" mit einem "n" und wir müssen noch einen weiteren Buchstaben herausfinden. Das Schöne an diesem Algorithmus ist nun, dass er statt Wörter zu erraten, die dieser Form ähneln, das Wort errät, das überhaupt keine Beziehung zu früheren Vermutungen hat. Er versucht, mehr Buchstaben zu geben und hoffentlich den fehlenden Buchstaben zu enthüllen. In diesem Fall wird zufällig auch die Position für das fehlende "b" richtig ermittelt und mit der richtigen endgültigen Vermutung "boons" abgeschlossen.

Hier ist der Code:

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));
        }
    }
}
nur zur Hälfte
quelle
Super, dieser Eintrag ist sehr stark! Ich erinnere mich, wie ich in der TV-Show menschliche Spieler sah, die eine ähnliche Strategie anwendeten, als sie kein Wort aus den aktuellen Hinweisen erraten konnten.
SirDarius
3

Perl

Es gibt noch einiges an Verbesserungspotential, aber zumindest übertrifft es die enthaltenen Beispielspieler :)

Setzt Schreibzugriff auf das aktuelle Verzeichnis zum Zwischenspeichern von Wortlisten voraus (um die Ausführung zu beschleunigen); erstellt wordlist.lenN.storDateien mit Storable. Wenn dies ein Problem ist, entfernen Sie read_cached_wordlistden Code und ändern Sie ihn, um ihn nur zu verwenden read_wordlist.

Erläuterung

Zuerst erstelle ich ein Histogramm der Buchstabenhäufigkeiten in allen Wörtern der aktuellen Wortliste ( build_histogram). Dann muss ich meine nächste Vermutung treffen - was gemacht wird von find_best_word. Der Bewertungsalgorithmus addiert einfach die Histogrammwerte und überspringt die bereits gesehenen Buchstaben. Dies gibt mir ein Wort, das die häufigsten Buchstaben in der Wortliste enthält. Wenn es mehr als ein Wort mit einer bestimmten Punktzahl gibt, wähle ich eines nach dem Zufallsprinzip aus. Nachdem ich ein Wort gefunden habe, sende ich es an die Spiele-Engine, lese die Antwort und versuche, etwas Nützliches daraus zu machen :)

Ich halte eine Reihe von Bedingungen aufrecht, dh Buchstaben, die an einer bestimmten Stelle in einem Wort vorkommen können. Beim Start ist dies nur einfach (['a'..'z'] x $len), aber es wird basierend auf den in der Antwort gegebenen Hinweisen aktualisiert (siehe update_conds). Ich baue dann einen regulären Ausdruck aus diesen Bedingungen und filtere die Wortliste durch.

Bei Tests habe ich dann herausgefunden, dass die oben genannte Filterung nicht ?allzu gut funktioniert , daher der zweite Filter ( filter_wordlist_by_reply). Dies nutzt die Tatsache aus, dass ein als ?in dem Wort markierter Buchstabe an einer anderen Position auftritt, und filtert die Wortliste entsprechend.

Diese Schritte werden für jede Iteration der Hauptschleife wiederholt, es sei denn, die Lösung wurde gefunden (oder es ist nicht mehr möglich, von stdin zu lesen, was einen Fehler bedeutet).

Code

#!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
chinesischer Perl Goth
quelle
1
Meine Regeln haben ursprünglich das Schreiben auf die Festplatte verboten, aber ich mache eine Ausnahme, um das Zwischenspeichern der Wortliste zuzulassen, weil die große, die ich gefunden habe, die Prüfung der gesamten Sache ärgerlich langsam macht :)
SirDarius
Dieser Eintrag funktioniert besser als meine eigenen (noch nicht veröffentlichten) Versuche. Könnten Sie Ihren Algorithmus etwas erläutern?
SirDarius
Ich habe eine kurze Erklärung hinzugefügt; korrigiert die Code-Formatierung auch ein bisschen.
Chinese Perl Goth
@ SirDarius: Ich glaube nicht, dass es Verluste geben würde, wenn ein bestimmter Test eine Wortliste verwendet, die nur Einträge der richtigen Länge enthält. Während es für ein Programm nicht allzu schwierig sein sollte, Wörter in der Datei zu ignorieren, deren Länge von der angegebenen abweicht, würde die Existenz solcher Wörter das Testen zumindest verlangsamen. Ich frage mich auch, ob es sinnvoll wäre, Einreichungen die Angabe eines optionalen Programms zu gestatten, das bei gegebener Wortliste und N eine Wortliste, die auf die hilfreichste Art und Weise formatiert ist, an die Standardausgabe sendet ...
supercat
... und lassen Sie zu, dass das Hauptprogramm dies anstelle einer unformatierten Wortliste verwendet (wenn also eine Voranalyse erforderlich ist, muss diese nur einmal für jede Wortlänge und nicht einmal pro Spiel durchgeführt werden).
Supercat