最優秀得点板


16

私はこの(現在は無効になっている)質問への回答を見ることに興味がありましたが、修正/改善されたことはありませんでした。

6面のBoggleダイス(この質問から盗まれた構成)が与えられたら、処理時間の2分で、どのボード構成が最高のスコアを可能にするかを決定します。(つまり、どの面でどのサイコロが最大のスコアリングワードプールを可能にするか?)


目的

  • コードは2分(120秒)以内で実行する必要があります。その時点で、自動的に実行を停止し、結果を出力する必要があります。

  • 最終的なチャレンジスコアは、プログラムの5回の実行の平均Boggleスコアになります。

    • 同点の場合、勝者はより多くの単語を見つけたアルゴリズムになります。
    • 引き分けがある場合、勝者はより多くのアルゴリズムを見つけた方になります 長い(8+)単語をます。

ルール/制約

  • これはコードの挑戦です。コード長は関係ありません。

  • を参照してください 単語リストこのリンクISPELL "english.0"リストを使用-SCOWLリストにはかなり一般的な単語がありません)。

    • このリストは、任意の方法でコード内で参照/インポート/読み取りできます。
    • 正規表現に一致する単語のみ ^([a-pr-z]|qu){3,16}$がカウントされます。(小文字、3〜16文字、quのみを単位として使用する必要があります。)
  • 単語は、隣接する文字(水平、垂直、および斜め)をリンクして、1つの単語で1つのダイを複数回使用することなく、正しい順序で単語を綴ることによって形成されます。

    • 単語は3文字以上でなければなりません。短い単語はポイントを獲得しません。
    • サイコロではなく、文字が重複してもかまいません。
    • ボードの片側からもう一方の側にエッジ/クロスオーバーする単語は許可されていません。
  • 最終的なBoggle(チャレンジでない)スコアは、検出されたすべての単語のポイント値の合計です。

    • 各単語に割り当てられるポイント値は、単語の長さに基づいています。(下記参照)
    • 通常のBoggleルールは、他のプレイヤーが見つけた単語を控除/割引します。ここでは、他のプレイヤーが関与しておらず、見つかったすべての単語が合計スコアにカウントされるものとします。
    • ただし、同じグリッドで複数回見つかった単語は1回だけカウントする必要があります。
  • 機能/プログラムは最適な配置を見つける必要があります。事前に定義されたリストを単純にハードコーディングすることはできません。

  • 出力は、理想的なゲームボードの4x4グリッド、そのボードで見つかったすべての単語のリスト、およびそれらの単語に一致するBoggleスコアでなければなりません。


ダイ構成

A  A  E  E  G  N
E  L  R  T  T  Y
A  O  O  T  T  W
A  B  B  J  O  O
E  H  R  T  V  W
C  I  M  O  T  U
D  I  S  T  T  Y
E  I  O  S  S  T
D  E  L  R  V  Y
A  C  H  O  P  S
H  I  M  N  Qu U
E  E  I  N  S  U
E  E  G  H  N  W
A  F  F  K  P  S
H  L  N  N  R  Z
D  E  I  L  R  X

標準ボトルスコア表

Word length => Points
<= 2 - 0 pts
   3 - 1  
   4 - 1  
   5 - 2  
   6 - 3  
   7 - 5
>= 8 - 11 pts
*Words using the "Qu" die will count the full 2 letters for their word, not just the 1 die.

出力例

A  L  O  J  
V  U  T  S  
L  C  H  E  
G  K  R  X

CUT
THE
LUCK
HEX
....

140 points

さらに説明が必要な場合は、お問い合わせください!


2
目標を標準化するために辞書を用意することを希望します。また、これは新しいアイデアではないことに注意してください。単純なGoogle検索で明らかになります:)私が見た最高スコアは45271414合計単語数)です。ai.stanford.edu
mellamokb

4
プログラムは今世紀を終了するために必要ですか?
ピーターテイラー

1
@GlitchMr英語では、Qは通常Uでのみ使用されます。Boggleは、2つの文字を1つのユニットと同じダイに配置することでこれを説明します。
ガフィ

1
単語リストの仕様は不明です。english.0にリストされている単語のみ小文字で数えていますか?(標準の単語ゲームのルールでは、略語/頭文字と固有名詞は除外されています)
ピーターテイラー

1
私は正規表現を考えていました^([a-pr-z]|qu){3,16}$(これは、quを含む3文字の単語を誤って除外しますが、何もありません)。
ピーターテイラー

回答:


9

C、平均500+ 1500 1750ポイント

これは、バージョン2に対する比較的小さな改善です(以前のバージョンに関する注意については、以下を参照してください)。2つの部分があります。まず、プールからボードをランダムに選択する代わりに、プログラムはプール内のすべてのボードを反復処理し、プールの最上部に戻って繰り返す前に各ボードを順番に使用します。(この反復が行われている間にプールが変更されているため、連続して2回以上選択されるボードがありますが、これは重大な問題ではありません。) 、また、プールの内容を改善せずにプログラムが長すぎる場合、検索が「停止」したと判断し、プールを空にして、新しい検索からやり直します。2分が経過するまでこれを続けます。

最初は、1500ポイントの範囲を超えるために、何らかのヒューリスティック検索を使用すると考えていました。4527ポイントのボードに関する@mellamokbのコメントは、改善の余地が十分にあると私を推測させました。ただし、比較的小さな単語リストを使用しています。4527点のボードは、最も包括的な単語リストであるYAWLを使用して得点しました。これは、公式の米国スクラブルの単語リストよりもさらに大きいものです。これを念頭に置いて、私は私のプログラムが見つけたボードを再検討し、1700程度以上のボードの限られたセットがあるように見えることに気付きました。そのため、たとえば、1726スコアのボードを発見した複数のランがありましたが、発見されたのは常にまったく同じボードでした(回転と反射を無視)。

別のテストとして、辞書としてYAWLを使用してプログラムを実行し、約12回実行した後に4527ポイントのボードを見つけました。これを考えると、私のプログラムはすでに検索スペースの上限にあると仮定しているので、私が計画していた書き直しは非常にわずかな利益で余分な複雑さをもたらすでしょう。

english.0ワードリストを使用して、私のプログラムが見つけた5つの最高得点のボードのリストを以下に示します。

1735 :  D C L P  E I A E  R N T R  S E G S
1738 :  B E L S  R A D G  T I N E  S E R S
1747 :  D C L P  E I A E  N T R D  G S E R
1766 :  M P L S  S A I E  N T R N  D E S G
1772:   G R E P  T N A L  E S I T  D R E S

私の信念は、531個の単語を含む1772個の「grepボード」(これを呼んでいます)は、このワードリストで最高得点のボードであるということです。私のプログラムの2分間の実行の50%以上がこの掲示板で終わります。また、プログラムを何も改善することなく一晩実行したままにしました。したがって、より高いスコアのボードがある場合、プログラムの検索手法を無効にする何らかの側面が必要になる可能性があります。たとえば、レイアウトのあらゆる小さな変更によって合計スコアが大幅に低下するボードは、私のプログラムで発見されない可能性があります。私の考えでは、このようなボードが存在する可能性は非常に低いです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#define WORDLISTFILE "./english.0"

#define XSIZE 4
#define YSIZE 4
#define BOARDSIZE (XSIZE * YSIZE)
#define DIEFACES 6
#define WORDBUFSIZE 256
#define MAXPOOLSIZE 32
#define STALLPOINT 64
#define RUNTIME 120

/* Generate a random int from 0 to N-1.
 */
#define random(N)  ((int)(((double)(N) * rand()) / (RAND_MAX + 1.0)))

static char const dice[BOARDSIZE][DIEFACES] = {
    "aaeegn", "elrtty", "aoottw", "abbjoo",
    "ehrtvw", "cimotu", "distty", "eiosst",
    "delrvy", "achops", "himnqu", "eeinsu",
    "eeghnw", "affkps", "hlnnrz", "deilrx"
};

/* The dictionary is represented in memory as a tree. The tree is
 * represented by its arcs; the nodes are implicit. All of the arcs
 * emanating from a single node are stored as a linked list in
 * alphabetical order.
 */
typedef struct {
    int letter:8;   /* the letter this arc is labelled with */
    int arc:24;     /* the node this arc points to (i.e. its first arc) */
    int next:24;    /* the next sibling arc emanating from this node */
    int final:1;    /* true if this arc is the end of a valid word */
} treearc;

/* Each of the slots that make up the playing board is represented
 * by the die it contains.
 */
typedef struct {
    unsigned char die;      /* which die is in this slot */
    unsigned char face;     /* which face of the die is showing */
} slot;

/* The following information defines a game.
 */
typedef struct {
    slot board[BOARDSIZE];  /* the contents of the board */
    int score;              /* how many points the board is worth */
} game;

/* The wordlist is stored as a binary search tree.
 */
typedef struct {
    int item: 24;   /* the identifier of a word in the list */
    int left: 16;   /* the branch with smaller identifiers */
    int right: 16;  /* the branch with larger identifiers */
} listnode;

/* The dictionary.
 */
static treearc *dictionary;
static int heapalloc;
static int heapsize;

/* Every slot's immediate neighbors.
 */
static int neighbors[BOARDSIZE][9];

/* The wordlist, used while scoring a board.
 */
static listnode *wordlist;
static int listalloc;
static int listsize;
static int xcursor;

/* The game that is currently being examined.
 */
static game G;

/* The highest-scoring game seen so far.
 */
static game bestgame;

/* Variables to time the program and display stats.
 */
static time_t start;
static int boardcount;
static int allscores;

/* The pool contains the N highest-scoring games seen so far.
 */
static game pool[MAXPOOLSIZE];
static int poolsize;
static int cutoffscore;
static int stallcounter;

/* Some buffers shared by recursive functions.
 */
static char wordbuf[WORDBUFSIZE];
static char gridbuf[BOARDSIZE];

/*
 * The dictionary is stored as a tree. It is created during
 * initialization and remains unmodified afterwards. When moving
 * through the tree, the program tracks the arc that points to the
 * current node. (The first arc in the heap is a dummy that points to
 * the root node, which otherwise would have no arc.)
 */

static void initdictionary(void)
{
    heapalloc = 256;
    dictionary = malloc(256 * sizeof *dictionary);
    heapsize = 1;
    dictionary->arc = 0;
    dictionary->letter = 0;
    dictionary->next = 0;
    dictionary->final = 0;
}

static int addarc(int arc, char ch)
{
    int prev, a;

    prev = arc;
    a = dictionary[arc].arc;
    for (;;) {
        if (dictionary[a].letter == ch)
            return a;
        if (!dictionary[a].letter || dictionary[a].letter > ch)
            break;
        prev = a;
        a = dictionary[a].next;
    }
    if (heapsize >= heapalloc) {
        heapalloc *= 2;
        dictionary = realloc(dictionary, heapalloc * sizeof *dictionary);
    }
    a = heapsize++;
    dictionary[a].letter = ch;
    dictionary[a].final = 0;
    dictionary[a].arc = 0;
    if (prev == arc) {
        dictionary[a].next = dictionary[prev].arc;
        dictionary[prev].arc = a;
    } else {
        dictionary[a].next = dictionary[prev].next;
        dictionary[prev].next = a;
    }
    return a;
}

static int validateword(char *word)
{
    int i;

    for (i = 0 ; word[i] != '\0' && word[i] != '\n' ; ++i)
        if (word[i] < 'a' || word[i] > 'z')
            return 0;
    if (word[i] == '\n')
        word[i] = '\0';
    if (i < 3)
        return 0;
    for ( ; *word ; ++word, --i) {
        if (*word == 'q') {
            if (word[1] != 'u')
                return 0;
            memmove(word + 1, word + 2, --i);
        }
    }
    return 1;
}

static void createdictionary(char const *filename)
{
    FILE *fp;
    int arc, i;

    initdictionary();
    fp = fopen(filename, "r");
    while (fgets(wordbuf, sizeof wordbuf, fp)) {
        if (!validateword(wordbuf))
            continue;
        arc = 0;
        for (i = 0 ; wordbuf[i] ; ++i)
            arc = addarc(arc, wordbuf[i]);
        dictionary[arc].final = 1;
    }
    fclose(fp);
}

/*
 * The wordlist is stored as a binary search tree. It is only added
 * to, searched, and erased. Instead of storing the actual word, it
 * only retains the word's final arc in the dictionary. Thus, the
 * dictionary needs to be walked in order to print out the wordlist.
 */

static void initwordlist(void)
{
    listalloc = 16;
    wordlist = malloc(listalloc * sizeof *wordlist);
    listsize = 0;
}

static int iswordinlist(int word)
{
    int node, n;

    n = 0;
    for (;;) {
        node = n;
        if (wordlist[node].item == word)
            return 1;
        if (wordlist[node].item > word)
            n = wordlist[node].left;
        else
            n = wordlist[node].right;
        if (!n)
            return 0;
    }
}

static int insertword(int word)
{
    int node, n;

    if (!listsize) {
        wordlist->item = word;
        wordlist->left = 0;
        wordlist->right = 0;
        ++listsize;
        return 1;
    }

    n = 0;
    for (;;) {
        node = n;
        if (wordlist[node].item == word)
            return 0;
        if (wordlist[node].item > word)
            n = wordlist[node].left;
        else
            n = wordlist[node].right;
        if (!n)
            break;
    }

    if (listsize >= listalloc) {
        listalloc *= 2;
        wordlist = realloc(wordlist, listalloc * sizeof *wordlist);
    }
    n = listsize++;
    wordlist[n].item = word;
    wordlist[n].left = 0;
    wordlist[n].right = 0;
    if (wordlist[node].item > word)
        wordlist[node].left = n;
    else
        wordlist[node].right = n;
    return 1;
}

static void clearwordlist(void)
{
    listsize = 0;
    G.score = 0;
}


static void scoreword(char const *word)
{
    int const scoring[] = { 0, 0, 0, 1, 1, 2, 3, 5 };
    int n, u;

    for (n = u = 0 ; word[n] ; ++n)
        if (word[n] == 'q')
            ++u;
    n += u;
    G.score += n > 7 ? 11 : scoring[n];
}

static void addwordtolist(char const *word, int id)
{
    if (insertword(id))
        scoreword(word);
}

static void _printwords(int arc, int len)
{
    int a;

    while (arc) {
        a = len + 1;
        wordbuf[len] = dictionary[arc].letter;
        if (wordbuf[len] == 'q')
            wordbuf[a++] = 'u';
        if (dictionary[arc].final) {
            if (iswordinlist(arc)) {
                wordbuf[a] = '\0';
                if (xcursor == 4) {
                    printf("%s\n", wordbuf);
                    xcursor = 0;
                } else {
                    printf("%-16s", wordbuf);
                    ++xcursor;
                }
            }
        }
        _printwords(dictionary[arc].arc, a);
        arc = dictionary[arc].next;
    }
}

static void printwordlist(void)
{
    xcursor = 0;
    _printwords(1, 0);
    if (xcursor)
        putchar('\n');
}

/*
 * The board is stored as an array of oriented dice. To score a game,
 * the program looks at each slot on the board in turn, and tries to
 * find a path along the dictionary tree that matches the letters on
 * adjacent dice.
 */

static void initneighbors(void)
{
    int i, j, n;

    for (i = 0 ; i < BOARDSIZE ; ++i) {
        n = 0;
        for (j = 0 ; j < BOARDSIZE ; ++j)
            if (i != j && abs(i / XSIZE - j / XSIZE) <= 1
                       && abs(i % XSIZE - j % XSIZE) <= 1)
                neighbors[i][n++] = j;
        neighbors[i][n] = -1;
    }
}

static void printboard(void)
{
    int i;

    for (i = 0 ; i < BOARDSIZE ; ++i) {
        printf(" %c", toupper(dice[G.board[i].die][G.board[i].face]));
        if (i % XSIZE == XSIZE - 1)
            putchar('\n');
    }
}

static void _findwords(int pos, int arc, int len)
{
    int ch, i, p;

    for (;;) {
        ch = dictionary[arc].letter;
        if (ch == gridbuf[pos])
            break;
        if (ch > gridbuf[pos] || !dictionary[arc].next)
            return;
        arc = dictionary[arc].next;
    }
    wordbuf[len++] = ch;
    if (dictionary[arc].final) {
        wordbuf[len] = '\0';
        addwordtolist(wordbuf, arc);
    }
    gridbuf[pos] = '.';
    for (i = 0 ; (p = neighbors[pos][i]) >= 0 ; ++i)
        if (gridbuf[p] != '.')
            _findwords(p, dictionary[arc].arc, len);
    gridbuf[pos] = ch;
}

static void findwordsingrid(void)
{
    int i;

    clearwordlist();
    for (i = 0 ; i < BOARDSIZE ; ++i)
        gridbuf[i] = dice[G.board[i].die][G.board[i].face];
    for (i = 0 ; i < BOARDSIZE ; ++i)
        _findwords(i, 1, 0);
}

static void shuffleboard(void)
{
    int die[BOARDSIZE];
    int i, n;

    for (i = 0 ; i < BOARDSIZE ; ++i)
        die[i] = i;
    for (i = BOARDSIZE ; i-- ; ) {
        n = random(i);
        G.board[i].die = die[n];
        G.board[i].face = random(DIEFACES);
        die[n] = die[i];
    }
}

/*
 * The pool contains the N highest-scoring games found so far. (This
 * would typically be done using a priority queue, but it represents
 * far too little of the runtime. Brute force is just as good and
 * simpler.) Note that the pool will only ever contain one board with
 * a particular score: This is a cheap way to discourage the pool from
 * filling up with almost-identical high-scoring boards.
 */

static void addgametopool(void)
{
    int i;

    if (G.score < cutoffscore)
        return;
    for (i = 0 ; i < poolsize ; ++i) {
        if (G.score == pool[i].score) {
            pool[i] = G;
            return;
        }
        if (G.score > pool[i].score)
            break;
    }
    if (poolsize < MAXPOOLSIZE)
        ++poolsize;
    if (i < poolsize) {
        memmove(pool + i + 1, pool + i, (poolsize - i - 1) * sizeof *pool);
        pool[i] = G;
    }
    cutoffscore = pool[poolsize - 1].score;
    stallcounter = 0;
}

static void selectpoolmember(int n)
{
    G = pool[n];
}

static void emptypool(void)
{
    poolsize = 0;
    cutoffscore = 0;
    stallcounter = 0;
}

/*
 * The program examines as many boards as it can in the given time,
 * and retains the one with the highest score. If the program is out
 * of time, then it reports the best-seen game and immediately exits.
 */

static void report(void)
{
    findwordsingrid();
    printboard();
    printwordlist();
    printf("score = %d\n", G.score);
    fprintf(stderr, "// score: %d points (%d words)\n", G.score, listsize);
    fprintf(stderr, "// %d boards examined\n", boardcount);
    fprintf(stderr, "// avg score: %.1f\n", (double)allscores / boardcount);
    fprintf(stderr, "// runtime: %ld s\n", time(0) - start);
}

static void scoreboard(void)
{
    findwordsingrid();
    ++boardcount;
    allscores += G.score;
    addgametopool();
    if (bestgame.score < G.score) {
        bestgame = G;
        fprintf(stderr, "// %ld s: board %d scoring %d\n",
                time(0) - start, boardcount, G.score);
    }

    if (time(0) - start >= RUNTIME) {
        G = bestgame;
        report();
        exit(0);
    }
}

static void restartpool(void)
{
    emptypool();
    while (poolsize < MAXPOOLSIZE) {
        shuffleboard();
        scoreboard();
    }
}

/*
 * Making small modifications to a board.
 */

static void turndie(void)
{
    int i, j;

    i = random(BOARDSIZE);
    j = random(DIEFACES - 1) + 1;
    G.board[i].face = (G.board[i].face + j) % DIEFACES;
}

static void swapdice(void)
{
    slot t;
    int p, q;

    p = random(BOARDSIZE);
    q = random(BOARDSIZE - 1);
    if (q >= p)
        ++q;
    t = G.board[p];
    G.board[p] = G.board[q];
    G.board[q] = t;
}

/*
 *
 */

int main(void)
{
    int i;

    start = time(0);
    srand((unsigned int)start);

    createdictionary(WORDLISTFILE);
    initwordlist();
    initneighbors();

    restartpool();
    for (;;) {
        for (i = 0 ; i < poolsize ; ++i) {
            selectpoolmember(i);
            turndie();
            scoreboard();
            selectpoolmember(i);
            swapdice();
            scoreboard();
        }
        ++stallcounter;
        if (stallcounter >= STALLPOINT) {
            fprintf(stderr, "// stalled; restarting search\n");
            restartpool();
        }
    }

    return 0;
}

バージョン2の注意事項(6月9日)

最初のバージョンのコードを出発点として使用する1つの方法を次に示します。このバージョンへの変更は100行未満で構成されていますが、平均ゲームスコアは3倍になりました。

このバージョンでは、プログラムは、プログラムがこれまでに生成した最高スコアのN個のボードで構成される候補の「プール」を維持します。新しいボードが生成されるたびに、プールに追加され、プール内の最もスコアの低いボードが削除されます(スコアが既に存在するものよりも低い場合、追加されたばかりのボードである可能性が非常に高い)。プールは、最初はランダムに生成されたボードで満たされ、その後、プログラムの実行中は一定のサイズを維持します。

プログラムのメインループは、プールからランダムなボードを選択して変更し、この新しいボードのスコアを決定してから、プールに入れることで構成されます(十分なスコアがある場合)。この方法で、プログラムは高得点のボードを継続的に改良しています。主な活動は段階的で段階的な改善を行うことですが、プールのサイズにより、プログラムがマルチステップの改善を見つけて、ボードのスコアを一時的に悪化させてから改善することもできます。

通常、このプログラムは、適切なローカル最大値をかなり迅速に検出します。その後、おそらく、より良い最大値は遠すぎて検出できません。そのため、プログラムを10秒より長く実行する意味はほとんどありません。これは、たとえばプログラムがこの状況を検出し、新しい候補プールで新しい検索を開始することで改善される場合があります。ただし、これはほんのわずかな増加にすぎません。適切なヒューリスティック検索手法は、探索のより良い手段となるでしょう。

(補足:このバージョンは毎秒約5,000ボードを生成していることがわかりました。最初のバージョンは通常毎秒20,000ボードを処理していたので、最初は心配でした。言い換えれば、それは完全にボードごとに多くの単語を見つけるプログラムによるものでした。これに照らして、私はワードリストを管理するためにコードを変更することを検討しましたが、このプログラムは割り当てられた120秒のうち10最適化は非常に時期尚早です。)

バージョン1(6月2日)に関する注意

これは非常に簡単なソリューションです。ランダムボードを生成し、10秒後に最高スコアのボードを出力します。(問題の仕様で許可されている余分な110秒は通常、待つだけの価値のある最終的な解決策を改善しないため、デフォルトで10秒に設定しました。)それは非常に愚かなことです。ただし、よりインテリジェントな検索を行うための適切な出発点となるすべてのインフラストラクチャがあり、締め切り前にそれを利用したい人がいる場合は、そうすることをお勧めします。

プログラムは、辞書をツリー構造に読み込むことから始まります。(フォームは可能な限り最適化されていませんが、これらの目的には十分すぎるほどです。)他の基本的な初期化の後、ボードの生成とスコアリングを開始します。プログラムは私のマシンで毎秒約2万枚のボードを調べ、約2万枚のボードの後、ランダムなアプローチが枯渇し始めます。

実際に評価されるボードは常に1つだけなので、スコアリングデータはグローバル変数に格納されます。これにより、引数として再帰関数に渡す必要がある定数データの量を最小限に抑えることができます。(これにより、一部の人にじんましんができると確信しています。彼らに謝罪します。)単語リストはバイナリ検索ツリーとして保存されます。見つかったすべての単語は、単語リストで検索する必要があるため、重複する単語は2回カウントされません。ただし、ワードリストは評価プロセス中にのみ必要であるため、スコアが見つかった後は破棄されます。したがって、プログラムの最後に、選択したボードにもう一度スコアを付けて、ワードリストを印刷できるようにする必要があります。

おもしろい事実:で生成されたランダムに生成されたBoggleボードの平均スコアは、english.061.7ポイントです。


明らかに、私自身の効率を改善する必要があります。:
ガフィ

私の遺伝的アプローチでは、約200-kのボードを生成する約700-800ポイントを獲得しているので、次世代を生み出す方法で明らかにあなたは私よりもはるかに優れた何かをしているのです。
ピーターテイラー

これまでにマスターワードリストにのみ実装された自分のツリー構造は、動作し、ボードの検証を可能にしますが、システムメモリを使い果たします(プロセスを強制するのにかなりの時間がかかる点まで積極的に遅れます)早期に終了する)。これは間違いなく自分のせいですが、私はそれに取り組んでいます!編集:すでに修正されています!;-)
ガフィ

@PeterTaylor遺伝的アルゴリズムを試すことを考えましたが、2つのボードを組み合わせるためのもっともらしいメカニズムを考えることができませんでした。お元気ですか?ボード上のスロットごとに親をランダムに選択していますか?
パンボックス

ボードの状態をサイコロの並びとサイコロに表示される面に分割しました。順列交叉にはcs.colostate.edu/~genitor/1995/permutations.pdfの「order crossover 1」を使用し、顔交叉には明白です。しかし、私は実装する時間を見つける必要がある全く異なるアプローチのアイデアを持っています。
ピーターテイラー

3

VBA(現在は平均80-110ポイント、未完)

これが私の作業プロセスですが、可能な限り最高のものではありません。何度もテストを実行した後、ボードで見つかった私の最高の最高スコアは約120です。まだいくつかのより良い一般的なクリーンアップが必要であり、多くの場所でより多くの効率が得られると確信しています。

  • 2012.05.09:
    • 元の投稿
  • 2012.05.10-2012.05.18:
    • スコアリングアルゴリズムの改善
    • 経路探索ロジックの改善
  • 2012.06.07-2012.06.12
    • ワード制限を8から6に減らしました。より小さなワードでより多くのボードを使用できます。平均スコアがわずかに改善したようです。(実行ごとに10〜15枚程度のボードをチェック、1対2)
    • ブレッドボックスの提案に従って、単語リストを格納するツリー構造を作成しました。これにより、ボード上の単語のバックエンドチェックが大幅に高速化されます。
    • 最大単語サイズ(速度とスコア)を変更してみましたが、5または6のどちらがより良い選択肢であるかはまだ決めていません。6は合計100〜120のボードをチェックし、5つは500〜1000になります(どちらもこれまでに提供された他の例よりもはるかに低いです)。
    • 問題:連続して何度も実行した後、プロセスの速度が低下し始めるため、管理するメモリがまだ残っています。

これはおそらくあなたの中には恐ろしいように見えますが、私が言ったように、WIPです。私は建設的な批判に対して非常にオープンです!非常に長いボディでごめんなさい...


ダイスクラスモジュール

Option Explicit

Private Sides() As String

Sub NewDie(NewLetters As String)
    Sides = Split(NewLetters, ",")
End Sub

Property Get Side(i As Integer)
    Side = Sides(i)
End Property

ツリークラスモジュール

Option Explicit

Private zzroot As TreeNode


Sub AddtoTree(ByVal TreeWord As Variant)
Dim i As Integer
Dim TempNode As TreeNode

    Set TempNode = TraverseTree(TreeWord, zzroot)
    SetNode TreeWord, TempNode

End Sub

Private Function SetNode(ByVal Value As Variant, parent As TreeNode) As TreeNode
Dim ValChar As String
    If Len(Value) > 0 Then
        ValChar = Left(Value, 1)
        Select Case Asc(ValChar) - 96
            Case 1:
                Set parent.Node01 = AddNode(ValChar, parent.Node01)
                Set SetNode = parent.Node01
            Case 2:
                Set parent.Node02 = AddNode(ValChar, parent.Node02)
                Set SetNode = parent.Node02
            ' ... - Reduced to limit size of answer.
            Case 26:
                Set parent.Node26 = AddNode(ValChar, parent.Node26)
                Set SetNode = parent.Node26
            Case Else:
                Set SetNode = Nothing
        End Select

        Set SetNode = SetNode(Right(Value, Len(Value) - 1), SetNode)
    Else
        Set parent.Node27 = AddNode(True, parent.Node27)
        Set SetNode = parent.Node27
    End If
End Function

Function AddNode(ByVal Value As Variant, NewNode As TreeNode) As TreeNode
    If NewNode Is Nothing Then
        Set AddNode = New TreeNode
        AddNode.Value = Value
    Else
        Set AddNode = NewNode
    End If
End Function
Function TraverseTree(TreeWord As Variant, parent As TreeNode) As TreeNode
Dim Node As TreeNode
Dim ValChar As String
    If Len(TreeWord) > 0 Then
        ValChar = Left(TreeWord, 1)

        Select Case Asc(ValChar) - 96
            Case 1:
                Set Node = parent.Node01
            Case 2:
                Set Node = parent.Node02
            ' ... - Reduced to limit size of answer.
            Case 26:
                Set Node = parent.Node26
            Case Else:
                Set Node = Nothing
        End Select

        If Not Node Is Nothing Then
            Set TraverseTree = TraverseTree(Right(TreeWord, Len(TreeWord) - 1), Node)
            If Not TraverseTree Is Nothing Then
                Set TraverseTree = parent
            End If
        Else
            Set TraverseTree = parent
        End If
    Else
        If parent.Node27.Value Then
            Set TraverseTree = parent
        Else
            Set TraverseTree = Nothing
        End If
    End If
End Function

Function WordScore(TreeWord As Variant, Step As Integer, Optional parent As TreeNode = Nothing) As Integer
Dim Node As TreeNode
Dim ValChar As String
    If parent Is Nothing Then Set parent = zzroot
    If Len(TreeWord) > 0 Then
        ValChar = Left(TreeWord, 1)

        Select Case Asc(ValChar) - 96
            Case 1:
                Set Node = parent.Node01
            Case 2:
                Set Node = parent.Node02
            ' ... - Reduced to limit size of answer.
            Case 26:
                Set Node = parent.Node26
            Case Else:
                Set Node = Nothing
        End Select

        If Not Node Is Nothing Then
            WordScore = WordScore(Right(TreeWord, Len(TreeWord) - 1), Step + 1, Node)
        End If
    Else
        If parent.Node27 Is Nothing Then
            WordScore = 0
        Else
            WordScore = Step
        End If
    End If
End Function

Function ValidWord(TreeWord As Variant, Optional parent As TreeNode = Nothing) As Integer
Dim Node As TreeNode
Dim ValChar As String
    If parent Is Nothing Then Set parent = zzroot
    If Len(TreeWord) > 0 Then
        ValChar = Left(TreeWord, 1)

        Select Case Asc(ValChar) - 96
            Case 1:
                Set Node = parent.Node01
            Case 2:
                Set Node = parent.Node02
            ' ... - Reduced to limit size of answer.
            Case 26:
                Set Node = parent.Node26
            Case Else:
                Set Node = Nothing
        End Select

        If Not Node Is Nothing Then
            ValidWord = ValidWord(Right(TreeWord, Len(TreeWord) - 1), Node)
        Else
            ValidWord = False
        End If
    Else
        If parent.Node27 Is Nothing Then
            ValidWord = False
        Else
            ValidWord = True
        End If
    End If
End Function

Private Sub Class_Initialize()
    Set zzroot = New TreeNode
End Sub

Private Sub Class_Terminate()
    Set zzroot = Nothing
End Sub

TreeNodeクラスモジュール

Option Explicit

Public Value As Variant
Public Node01 As TreeNode
Public Node02 As TreeNode
' ... - Reduced to limit size of answer.
Public Node26 As TreeNode
Public Node27 As TreeNode

メインモジュール

Option Explicit

Const conAllSides As String = ";a,a,e,e,g,n;e,l,r,t,t,y;a,o,o,t,t,w;a,b,b,j,o,o;e,h,r,t,v,w;c,i,m,o,t,u;d,i,s,t,t,y;e,i,o,s,s,t;d,e,l,r,v,y;a,c,h,o,p,s;h,i,m,n,qu,u;e,e,i,n,s,u;e,e,g,h,n,w;a,f,f,k,p,s;h,l,n,n,r,z;d,e,i,l,r,x;"
Dim strBoard As String, strBoardTemp As String, strWords As String, strWordsTemp As String
Dim CheckWordSub As String
Dim iScore As Integer, iScoreTemp As Integer
Dim Board(1 To 4, 1 To 4) As Integer
Dim AllDice(1 To 16) As Dice
Dim AllWordsTree As Tree
Dim AllWords As Scripting.Dictionary
Dim CurWords As Scripting.Dictionary
Dim FullWords As Scripting.Dictionary
Dim JunkWords As Scripting.Dictionary
Dim WordPrefixes As Scripting.Dictionary
Dim StartTime As Date, StopTime As Date
Const MAX_LENGTH As Integer = 5
Dim Points(3 To 8) As Integer

Sub Boggle()
Dim DiceSetup() As String
Dim i As Integer, j As Integer, k As Integer

    StartTime = Now()

    strBoard = vbNullString
    strWords = vbNullString
    iScore = 0

    ReadWordsFileTree

    DiceSetup = Split(conAllSides, ";")

    For i = 1 To 16
        Set AllDice(i) = New Dice
        AllDice(i).NewDie "," & DiceSetup(i)
    Next i

    Do While WithinTimeLimit

        Shuffle

        strBoardTemp = vbNullString
        strWordsTemp = vbNullString
        iScoreTemp = 0

        FindWords

        If iScoreTemp > iScore Or iScore = 0 Then
            iScore = iScoreTemp
            k = 1
            For i = 1 To 4
                For j = 1 To 4
                    strBoardTemp = strBoardTemp & AllDice(k).Side(Board(j, i)) & "  "
                    k = k + 1
                Next j
                strBoardTemp = strBoardTemp & vbNewLine
            Next i
            strBoard = strBoardTemp
            strWords = strWordsTemp

        End If

    Loop

    Debug.Print strBoard
    Debug.Print strWords
    Debug.Print iScore & " points"

    Set AllWordsTree = Nothing
    Set AllWords = Nothing
    Set CurWords = Nothing
    Set FullWords = Nothing
    Set JunkWords = Nothing
    Set WordPrefixes = Nothing

End Sub

Sub ShuffleBoard()
Dim i As Integer

    For i = 1 To 16
        If Not WithinTimeLimit Then Exit Sub
        Board(Int((i - 1) / 4) + 1, 4 - (i Mod 4)) = Int(6 * Rnd() + 1)
    Next i

End Sub

Sub Shuffle()
Dim n As Long
Dim Temp As Variant
Dim j As Long

    Randomize
    ShuffleBoard
    For n = 1 To 16
        If Not WithinTimeLimit Then Exit Sub
        j = CLng(((16 - n) * Rnd) + n)
        If n <> j Then
            Set Temp = AllDice(n)
            Set AllDice(n) = AllDice(j)
            Set AllDice(j) = Temp
        End If
    Next n

    Set FullWords = New Scripting.Dictionary
    Set CurWords = New Scripting.Dictionary
    Set JunkWords = New Scripting.Dictionary

End Sub

Sub ReadWordsFileTree()
Dim FSO As New FileSystemObject
Dim FS
Dim strTemp As Variant
Dim iLength As Integer
Dim StartTime As Date

    StartTime = Now()
    Set AllWordsTree = New Tree
    Set FS = FSO.OpenTextFile("P:\Personal\english.txt")

    Points(3) = 1
    Points(4) = 1
    Points(5) = 2
    Points(6) = 3
    Points(7) = 5
    Points(8) = 11

    Do Until FS.AtEndOfStream
        strTemp = FS.ReadLine
        If strTemp = LCase(strTemp) Then
            iLength = Len(strTemp)
            iLength = IIf(iLength > 8, 8, iLength)
            If InStr(strTemp, "'") < 1 And iLength > 2 Then
                AllWordsTree.AddtoTree strTemp
            End If
        End If
    Loop
    FS.Close

End Sub

Function GetScoreTree() As Integer
Dim TempScore As Integer

    If Not WithinTimeLimit Then Exit Function

    GetScoreTree = 0

    TempScore = AllWordsTree.WordScore(CheckWordSub, 0)
    Select Case TempScore
        Case Is < 3:
            GetScoreTree = 0
        Case Is > 8:
            GetScoreTree = 11
        Case Else:
            GetScoreTree = Points(TempScore)
    End Select

End Function

Sub SubWords(CheckWord As String)
Dim CheckWordScore As Integer
Dim k As Integer, l As Integer

    For l = 0 To Len(CheckWord) - 3
        For k = 1 To Len(CheckWord) - l
            If Not WithinTimeLimit Then Exit Sub

            CheckWordSub = Mid(CheckWord, k, Len(CheckWord) - ((k + l) - 1))

            If Len(CheckWordSub) >= 3 And Not CurWords.Exists(CheckWordSub) Then
                CheckWordScore = GetScoreTree

                If CheckWordScore > 0 Then
                    CurWords.Add CheckWordSub, CheckWordSub
                    iScoreTemp = iScoreTemp + CheckWordScore
                    strWordsTemp = strWordsTemp & CheckWordSub & vbNewLine
                End If

                If Left(CheckWordSub, 1) = "q" Then
                    k = k + 1
                End If
            End If

        Next k
    Next l

End Sub

Sub FindWords()
Dim CheckWord As String
Dim strBoardLine(1 To 16) As String
Dim Used(1 To 16) As Boolean
Dim i As Integer, j As Integer, k As Integer, l As Integer, m As Integer, n As Integer
Dim StartSquare As Integer
Dim FullCheck As Variant

    n = 1
    For l = 1 To 4
        For m = 1 To 4
            If Not WithinTimeLimit Then Exit Sub
            strBoardLine(n) = AllDice(n).Side(Board(m, l))
            n = n + 1
        Next m
    Next l

    For i = 1 To 16
        For k = 1 To 16

            If Not WithinTimeLimit Then Exit Sub
            If k Mod 2 = 0 Then
                For j = 1 To 16
                    Used(j) = False
                Next j

                Used(i) = True
                MakeWords strBoardLine, Used, i, k / 2, strBoardLine(i)
            End If

        Next k
    Next i

    For Each FullCheck In FullWords.Items
        SubWords CStr(FullCheck)
    Next FullCheck

End Sub

Function MakeWords(BoardLine() As String, Used() As Boolean, _
    Start As Integer, _
    Direction As Integer, CurString As String) As String
Dim i As Integer, j As Integer, k As Integer, l As Integer

    j = 0

    Select Case Direction
        Case 1:
            k = Start - 5
        Case 2:
            k = Start - 4
        Case 3:
            k = Start - 3
        Case 4:
            k = Start - 1
        Case 5:
            k = Start + 1
        Case 6:
            k = Start + 3
        Case 7:
            k = Start + 4
        Case 8:
            k = Start + 5
    End Select

    If k >= 1 And k <= 16 Then
        If Not WithinTimeLimit Then Exit Function

        If Not Used(k) Then
            If ValidSquare(Start, k) Then
                If Not (JunkWords.Exists(CurString & BoardLine(k))) And Not FullWords.Exists(CurString & BoardLine(k)) Then
                    Used(k) = True
                    For l = 1 To MAX_LENGTH
                        If Not WithinTimeLimit Then Exit Function
                        MakeWords = CurString & BoardLine(k)
                        If Not (JunkWords.Exists(MakeWords)) Then
                            JunkWords.Add MakeWords, MakeWords
                        End If
                        If Len(MakeWords) = MAX_LENGTH And Not FullWords.Exists(MakeWords) Then
                            FullWords.Add MakeWords, MakeWords
                        ElseIf Len(MakeWords) < MAX_LENGTH Then
                            MakeWords BoardLine, Used, k, l, MakeWords
                        End If
                    Next l
                    Used(k) = False
                End If
            End If
        End If
    End If

    If Len(MakeWords) = MAX_LENGTH And Not FullWords.Exists(MakeWords) Then
        FullWords.Add MakeWords, MakeWords
        Debug.Print "FULL - " & MakeWords
    End If

End Function

Function ValidSquare(StartSquare As Integer, EndSquare As Integer) As Boolean
Dim sx As Integer, sy As Integer, ex As Integer, ey As Integer

    If Not WithinTimeLimit Then Exit Function

    sx = (StartSquare - 1) Mod 4 + 1
    ex = (EndSquare - 1) Mod 4 + 1

    sy = Int((StartSquare - 1) / 4 + 1)
    ey = Int((EndSquare - 1) / 4 + 1)

    ValidSquare = (sx - 1 <= ex And sx + 1 >= ex) And (sy - 1 <= ey And sy + 1 >= ey) And StartSquare <> EndSquare

End Function

Function WithinTimeLimit() As Boolean
    StopTime = Now()
    WithinTimeLimit = (Round(CDbl(((StopTime - StartTime) - Int(StopTime - StartTime)) * 86400), 0) < 120)
End Function

2
私はコードに目を通していませんが、50ポイントはとてつもなく低いです。スコアが1000以上のランダムに生成されたボードをプレイしました(SOOWPODSを使用-提供されている単語リストはそれほど広範囲ではないかもしれません)。あなたはサインエラーをチェックしたいかもしれません!
ピーターテイラー

@PeterTaylor提案をありがとう。私はそのスコアが非常に低いことを知っており、問題の一部が明らかな単語を見逃しているという事実にあることを知っています
...-ガフィ

@PeterTaylorまた、記録のために、私は最終製品を待つのではなく、進行状況を継続的に投稿しています。それが起こるまで、質問をいくぶん生き続けたいと思います。
ガフィ

また、これは最速のマシンでは実行されていないことにも注意してください。
ガフィ

1
@Gaffi 10秒でスコアを計算しますか?それは長すぎる9.999秒です。あなたのコードを再考する必要があります。ワードリストをツリーに変換することを拒否する場合は、少なくとも次の操作を行います。すべての2文字のプレフィックスのリスト(ハッシュテーブルなど)を作成します。その後、ボード上のパスをたどり始めたときに、最初の2文字がリストにない場合、可能なパスのサブツリー全体をスキップします。繰り返しますが、完全なツリーを構築するのが最適ですが、2文字のプレフィックスリストが役立ち、作成するのに非常に安価です。
パンボックス

2

サーチスペースのサイズのクイックルック。

   16! => 20922789888000 Dice Permutations
(6^16) =>  2821109907456 Face Permutations
 59025489844657012604928000 Boggle Grids 

各ダイの繰り返しを除外することを減らします。

              16! => 20922789888000 Dice Permutations
(4^4)*(5^6)*(6^5) => 31104000000 Unique Face Permutations
   650782456676352000000000 Boggle Grids 

@breadboxは、辞書をハッシュテーブルO(1)チェックとして保存します。

編集

Best Board(これまでに目撃したことがあります)

L  E  A  N
S  E  T  M
T  S  B  D
I  E  G  O

Score: 830
Words: 229
SLEETIEST  MANTELETS
MANTEELS  MANTELET  MATELESS
MANTEEL  MANTELS  TESTEES  BETISES  OBTESTS  OBESEST
SLEETS  SLEEST  TESTIS  TESTES  TSETSE  MANTES  MANTEL  TESTAE  TESTEE
STEELS  STELES  BETELS  BESETS  BESITS  BETISE  BODGES  BESEES  EISELS
GESTES  GEISTS  OBTEST
LEANT  LEATS  LEETS  LEESE  LESES  LESTS  LESBO  ANTES  NATES  SLEET  SETAE
SEATS  STIES  STEEL  STETS  STEAN  STEAM  STELE  SELES  TAELS  TEELS  TESTS
TESTE  TELES  TETES  MATES  TESTA  TEATS  SEELS  SITES  BEETS  BETEL  BETES
BESET  BESTS  BESIT  BEATS  BODGE  BESEE  DOGES  EISEL  GESTS  GESTE  GESSE
GEITS  GEIST  OBESE
LEAN  LEAT  LEAM  LEET  LEES  LETS  LEST  LESS  EATS  EELS  ELSE  ETNA  ESES
ESTS  ESSE  ANTE  ANTS  ATES  AMBO  NATS  SLEE  SEEL  SETA  SETS  SESE  SEAN
SEAT  SEAM  SELE  STIE  STET  SEES  TAEL  TAES  TEEL  TEES  TEST  TEAM  TELE
TELS  TETS  TETE  MATE  MATS  MAES  TIES  TEAT  TEGS  SELS  SEGO  SITS  SITE
BEET  BEES  BETA  BETE  BETS  BEST  BEAN  BEAT  BEAM  BELS  BOGS  BEGO  BEGS
DOGE  DOGS  DOBS  GOBS  GEST  GEIT  GETS  OBES
LEA  LEE  LET  LES  EAN  EAT  EEL  ELS  ETA  EST  ESS  ANT  ATE  NAT  NAE  NAM
SEE  SET  SEA  SEL  TAN  TAE  TAM  TEE  TES  TEA  TEL  TET  MNA  MAN  MAT  MAE
TIE  TIS  TEG  SEG  SEI  SIT  BEE  BET  BEL  BOD  BOG  BEG  DOG  DOB  ITS  EGO
GOD  GOB  GET  OBS  OBE
EA  EE  EL  ET  ES  AN  AT  AE  AM  NA  ST  TA  TE  MA
TI  SI  BE  BO  DO  IT  IS  GO  OD  OB

そんなに多くのRAMを搭載したマシンを手に入れて、話しましょう。
パンボックス

正方形の対称性を考慮して、ダイスの順列を8で除算する必要があります。また、(4 ^ 4)(5 ^ 6)(6 ^ 5)をどのように取得しますか?私は(4 ^ 3)(5 ^ 7)(6 ^ 6)にして、合計の検索スペースを2 ^ 79強にします。
ピーターテイラー

@ピーター・テイラー:そのとおりです。ユニークな顔をするとき、私は1対多を削除したに違いありません。83のユニークな顔があることに同意できると思います(ダイ全体の繰り返しを除く)。繰り返しのない任意の16を選択します。'83 x 82 x 81 x 80 x 79 x 78 x 77 x 76 x 75 x 74 x 73 x 72 x 71 x 70 x 69 x 68 'およそ:1.082 x(10 ^ 30)==>〜2 ^ 100これまでになく、その数は膨大です。
アダムスパイト

2
@AdamSpeight私はもともと、辞書をハッシュテーブルとして保存することについてのあなたのコメントは単なる冗談だと思っていたので、基本的には無視しました。謝罪いたします。適切な応答は次のとおりです。実際、ハッシュテーブルはこの問題に対するお粗末なデータ構造です。「Xは有効な単語ですか?」という質問にのみ答えることができるので、単語を見つけるためにすべての可能な文字列を作成する必要があります。DAWGを使用すると、「Xは有効な単語の接頭辞ですか?」これにより、検索スペースをその合計サイズのごく小さな部分に切り詰めることができます。
ブレッドボックス

Hashtableは、完全な単語になることのない単語の断片(dicttree.ceiling(fragment).startsWith(fragment))を間引くことができないため、ひどいです。所定のボッグルボードには何百万もの潜在的な単語がありますが、2〜3文字をつなげた後、その大部分を捨てることができます。ツリートラバーサルはハッシュテーブルルックアップよりも遅くなりますが、ツリーを使用すると、バックトラッキングによって99%以上の作業を回避できます。
ジムW

1

私のエントリが終わって、ここでボードの検索につきDream.In.Code〜30msの上(2コアマシン上で、より速く、よりコアを持つべきです)


まだそれに探しますが、そのページの最初のリンクがありません:でしhttp://。;-)
ガフィ

非常に素晴らしい。私は学習経験として自分でそれを盗もうとするつもりです。.NETto VBAは難しくありませ
ガフィ

ISPELLリスト(SOWPODSではありません)を実行するときに、平均スコアを含めるように回答を更新しますか?それは挑戦の一部であり、私はあなたの結果がブレッドボックスと比較されるのを見ることに興味があります。
ガフィ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.