114

有什么好的算法来确定刽子手游戏中单词的“难度”,以便游戏可以选择匹配指定难度级别的单词?

难度似乎与所需猜测的数量、字母使用的相对频率(例如,具有许多不常见字母的单词可能更难猜测)以及单词的长度有关。

还有一些主观因素需要(尝试)补偿,例如一个单词在玩家词汇表中的可能性,并且可以被识别,允许从仅基于字母频率的猜测策略转变为基于列表的猜测已知的匹配词。

我现在的尝试低于红宝石。关于如何改进分类的任何建议?

def classify_word(w)
  n = w.chars.to_a.uniq.length # Num. unique chars in w
  if n < 5 and w.length > 4
    return WordDifficulty::Easy
  end
  if n > w.length / 2
    return WordDifficulty::Hard
  else
    return WordDifficulty::Medium
  end
end

我正在写一个我想让我的孩子们玩的刽子手游戏;我太老了,不能尝试“家庭作业”,这可能就是为什么这个问题会收到如此多的反对票......单词是从大型单词数据库中随机抽取的,其中包括许多晦涩的单词,并被难度级别过滤为词而定。

4

12 回答 12

92

一、简介

这里有一个系统地解决这个问题的方法:如果你有一个能很好地扮演刽子手的算法,那么你可以把每个单词的难度作为你的程序在猜测那个单词时猜错的次数。

2. 除了刽子手策略

在其他一些答案和评论中隐含了一个想法,即求解器的最佳策略是根据英语中字母的频率或某些语料库中单词的频率做出决定。这是一个诱人的想法,但它并不完全正确。如果求解器准确地模拟了 setter 选择的单词的分布,则求解器会做得最好,并且人类 setter 很可能会根据它们的稀有性或避免常用字母来选择单词。例如,虽然E是英语中最常用的字母,但如果 setter 总是从单词JUGFULRHYTHMSYZYGY和中进行选择ZYTHUM,那么完美的求解器不会从猜测开始E

对 setter 进行建模的最佳方法取决于上下文,但我想某种贝叶斯归纳推理在求解器与同一个 setter 或一组类似 setter 进行许多游戏的环境中会很好地工作。

3. 一个刽子手算法

在这里,我将概述一个非常好的求解器(但远非完美)。它将 setter 建模为从固定字典中统一选择单词。这是一种贪心算法:在每个阶段,它都会猜测使未命中次数最少的字母,即不包含猜测的单词。例如,如果到目前为止还没有做出任何猜测,并且可能的词是DEED,DEADDARE, 那么:

  • 如果您猜DE,则没有遗漏;
  • 如果你猜A的话,有一个错过(DEED);
  • 如果你猜R,有两个未命中(DEEDDEAD);
  • 如果您猜到任何其他字母,则有三个未命中。

因此,在这种情况下,要么 要么DE一个很好的猜测。

(感谢Panic 上校在评论中指出,刽子手中的正确猜测是免费的——我在第一次尝试时完全忘记了这一点!)

4. 实施

这是该算法在 Python 中的实现:

from collections import defaultdict
from string import ascii_lowercase

def partition(guess, words):
    """Apply the single letter 'guess' to the sequence 'words' and return
    a dictionary mapping the pattern of occurrences of 'guess' in a
    word to the list of words with that pattern.

    >>> words = 'deed even eyes mews peep star'.split()
    >>> sorted(list(partition('e', words).items()))
    [(0, ['star']), (2, ['mews']), (5, ['even', 'eyes']), (6, ['deed', 'peep'])]

    """
    result = defaultdict(list)
    for word in words:
        key = sum(1 << i for i, letter in enumerate(word) if letter == guess)
        result[key].append(word)
    return result

def guess_cost(guess, words):
    """Return the cost of a guess, namely the number of words that don't
    contain the guess.

    >>> words = 'deed even eyes mews peep star'.split()
    >>> guess_cost('e', words)
    1
    >>> guess_cost('s', words)
    3

    """
    return sum(guess not in word for word in words)

def word_guesses(words, wrong = 0, letters = ''):
    """Given the collection 'words' that match all letters guessed so far,
    generate tuples (wrong, nguesses, word, guesses) where
    'word' is the word that was guessed;
    'guesses' is the sequence of letters guessed;
    'wrong' is the number of these guesses that were wrong;
    'nguesses' is len(guesses).

    >>> words = 'deed even eyes heel mere peep star'.split()
    >>> from pprint import pprint
    >>> pprint(sorted(word_guesses(words)))
    [(0, 1, 'mere', 'e'),
     (0, 2, 'deed', 'ed'),
     (0, 2, 'even', 'en'),
     (1, 1, 'star', 'e'),
     (1, 2, 'eyes', 'en'),
     (1, 3, 'heel', 'edh'),
     (2, 3, 'peep', 'edh')]

    """
    if len(words) == 1:
        yield wrong, len(letters), words[0], letters
        return
    best_guess = min((g for g in ascii_lowercase if g not in letters),
                     key = lambda g:guess_cost(g, words))
    best_partition = partition(best_guess, words)
    letters += best_guess
    for pattern, words in best_partition.items():
        for guess in word_guesses(words, wrong + (pattern == 0), letters):
            yield guess

5. 示例结果

使用这种策略,可以评估猜测集合中每个单词的难度。在这里,我考虑了我的系统词典中的六个字母的单词:

>>> words = [w.strip() for w in open('/usr/share/dict/words') if w.lower() == w]
>>> six_letter_words = set(w for w in words if len(w) == 6)
>>> len(six_letter_words)
15066
>>> results = sorted(word_guesses(six_letter_words))

这本词典中最容易猜测的单词(连同求解器猜测它们所需的猜测序列)如下:

>>> from pprint import pprint
>>> pprint(results[:10])
[(0, 1, 'eelery', 'e'),
 (0, 2, 'coneen', 'en'),
 (0, 2, 'earlet', 'er'),
 (0, 2, 'earner', 'er'),
 (0, 2, 'edgrew', 'er'),
 (0, 2, 'eerily', 'el'),
 (0, 2, 'egence', 'eg'),
 (0, 2, 'eleven', 'el'),
 (0, 2, 'enaena', 'en'),
 (0, 2, 'ennead', 'en')]

最难的词是这些:

>>> pprint(results[-10:])
[(12, 16, 'buzzer', 'eraoiutlnsmdbcfg'),
 (12, 16, 'cuffer', 'eraoiutlnsmdbpgc'),
 (12, 16, 'jugger', 'eraoiutlnsmdbpgh'),
 (12, 16, 'pugger', 'eraoiutlnsmdbpcf'),
 (12, 16, 'suddle', 'eaioulbrdcfghmnp'),
 (12, 16, 'yucker', 'eraoiutlnsmdbpgc'),
 (12, 16, 'zipper', 'eraoinltsdgcbpjk'),
 (12, 17, 'tuzzle', 'eaioulbrdcgszmnpt'),
 (13, 16, 'wuzzer', 'eraoiutlnsmdbpgc'),
 (13, 17, 'wuzzle', 'eaioulbrdcgszmnpt')]

之所以难,是因为你猜到之后-UZZLE,还有七种可能:

>>> ' '.join(sorted(w for w in six_letter_words if w.endswith('uzzle')))
'buzzle guzzle muzzle nuzzle puzzle tuzzle wuzzle'

6. 词表的选择

当然,在为您的孩子准备单词表时,您不会从计算机的系统词典开始,而是从您认为他们可能知道的单词列表开始。例如,您可能会查看维基词典的各种英语语料库中最常用单词的列表。

例如,在2006 年古腾堡计划中最常见的 10,000 个单词中的 1,700 个六字母单词中,最难的十个是:

[(6, 10, 'losing', 'eaoignvwch'),
 (6, 10, 'monkey', 'erdstaoync'),
 (6, 10, 'pulled', 'erdaioupfh'),
 (6, 10, 'slaves', 'erdsacthkl'),
 (6, 10, 'supper', 'eriaoubsfm'),
 (6, 11, 'hunter', 'eriaoubshng'),
 (6, 11, 'nought', 'eaoiustghbf'),
 (6, 11, 'wounds', 'eaoiusdnhpr'),
 (6, 11, 'wright', 'eaoithglrbf'),
 (7, 10, 'soames', 'erdsacthkl')]

(Soames Forsyte 是John Galsworthy 的 Forsyte Saga中的一个角色;单词表已转换为小写字母,因此我无法快速删除专有名称。)

于 2013-04-25T22:04:19.763 回答
21

一个非常简单的方法是根据单词中元音的缺失、唯一字母的数量以及每个字母的共性来计算分数:

letters = 'etaoinshrdlcumwfgypbvkjxqz'
vowels = set('aeiou')

def difficulty(word):
    unique = set(word)
    positions = sum(letters.index(c) for c in word)

    return len(word) * len(unique) * (7 - len(unique & vowels)) * positions

words = ['the', 'potato', 'school', 'egypt', 'floccinaucinihilipilification']

for word in words:
    print difficulty(word), word

和输出:

432 the
3360 potato
7200 school
7800 egypt
194271 floccinaucinihilipilification

然后,您可以使用以下方法对单词进行评分:

        score < 2000   # Easy
 2000 < score < 10000  # Medium
10000 < score          # Hard
于 2013-04-25T19:56:04.237 回答
9

您可以使用蒙特卡罗方法来估计单词的难度:

  • 通过每次猜测一个随机字母来模拟游戏,由目标语言中字母的频率加权,并计算随机玩家需要多少次猜测才能得出一个解决方案。请注意,由于每次猜测都会消除一个字母,因此此过程是有限的,它返回一个从 1 到 26 的数字,包括 1 到 26。
  • 重复此过程2*N次数,您的单词中唯一字母N的数量在哪里,
  • 2*N通过平均运行结果来计算分数,
  • 判断复杂程度:分数低于十表示易词,分数高于十六表示难词;其他一切都是中等的。
于 2013-04-25T20:09:28.410 回答
4

之前围绕同一主题的类似讨论: 确定英语单词的难度

我喜欢链接末尾的答案^。对于儿童刽子手游戏,只需采用像拼字游戏一样的方法。

为每个字母分配一个点值,然后将这些字母相加。

于 2013-04-25T20:21:13.110 回答
3

去做就对了!玩刽子手反对这个词。计算要击败多少次没收(即错误的猜测)。

你需要一个策略来玩。这是一个人类(ish)策略。从字典中,删除迄今为止不符合揭示的所有单词。猜猜剩下的单词中出现频率最高的字母。

如果您的策略是随机的,您可以将您的度量定义为预期的没收数量,并根据经验进行估计。


另一种确定性策略,来自我几年前写的一个刽子手机器人。在猜测不正确的情况下,猜测使剩余单词数最小的字母(即优化最坏的情况)。今天我不喜欢这种过于机械化的策略,我更喜欢上面的那个。

于 2013-04-25T19:58:50.070 回答
3

不久前,我使用显而易见的算法编写了一个刽子手求解器:给定一个包含所有可能单词的初始字典,在每一轮我们选择出现在字典中剩余最多单词中的字母,然后删除不匹配的单词(取决于响应)来自字典。

该算法并不像这样简单,因为通常有几个字母出现在字典中相同数量的单词中。在这种情况下,字母的选择会对一个单词需要多少次猜测产生重大影响。我们选择最大值,其中关于该字母位置的结果信息(如果确实在单词中)给出了关于系统的最大信息(具有最大信息熵的字母)。例如,如果剩下的两个可能的词是“百科全书”和“百科全书”,则字母“c”出现的概率与 e,n,y,l,o,p,e,d,i 相同(即保证在单词中),但我们应该首先询问“c”,因为它具有非零信息熵。

源代码(C++,GPL)在这里

所有这一切的结果是一个单词列表,每个单词需要猜测的次数:难度.txt (630KB)。这个算法最难找到的词是“will”(有 14 次失败的猜测);i 和 double l 被猜得很快,但接下来的选项包括 bill、dill、fill、gill、hill、kill、mill、pill、rill、till、will,从那时起,唯一的选择就是猜中的每个字母转动。有点违反直觉,更长的单词被猜得更快(只是没有那么多可供选择)。

当然,在人类的刽子手游戏中,心理学(和词汇的广度)所起的作用比这个算法所扮演的要大得多……

于 2013-04-25T22:46:20.480 回答
2

首先,当然,您会生成一个唯一字母列表。然后按频率排序(用英语或任何语言 -有这个列表),频率较低的字母难度较高。

然后,您需要决定是通过加法、乘法还是使用其他方案来组合分数。

于 2013-04-25T19:52:07.613 回答
1

您之所以被否决,是因为您要求我们为您构建一个非常复杂的算法。

为什么不直接创建三个数组(简单、中等和困难)并用一百左右的单词填充每个数组?大约需要20分钟。

我保证你的孩子在玩完几百场比赛之前早就厌倦了刽子手……:D

于 2013-04-25T19:45:47.420 回答
1

好吧,可能涉及很多事情:

  1. 正如大家所说,个别字母的频率;
  2. 一个词的长度肯定应该计算在内,但不是以线性方式计算的——一个长的词可以随机猜测到字母,而一个短的词则很难得到;
  3. 此外,应该考虑这些词本身 - “二分”可能是 SO 上的人的词,但可能不适用于非技术人群。

实际上,您可以尝试共同进化几种策略,其中一半用于决定单词的价值,而另一半用于赢得比赛。后一组将尝试最大化分数,而第一组将尝试最小化分数。一段时间后可能会有一个模式,然后决定一个词的价值的一半可能会给你一些基准。

于 2013-04-25T20:10:38.837 回答
1

从单词列表开始,然后为每个单词启动谷歌搜索。让点击数作为术语难度的(粗略)代理。

在一个精炼的版本中,您可以通过基于同义词库的同义词关系对单词进行分组,并通过计算谷歌搜索的结果来确定一个类别中最难的单词。

进一步了解 n-Gram 的概念,一个单词的难度可以通过其在散文中的音节频率来评估。当然,这取决于音节统计的质量。您可能必须区分词素和虚词(限定词、连词等)并通过单词中的音节数进行规范化(感觉就像我写的矫枉​​过正......)。

于 2013-04-26T00:24:00.560 回答
0

我喜欢构建一个根据用户学习和改变的算法的想法。一开始,你可以执行任何建议的算法来得出这个列表,然后随着更多的人玩这个游戏,你根据猜测的数量为每个单词分配一个权重(这也是不断跟踪和计算的) )。这可以防止复杂但流行的单词被赋予困难的评级但为人们所熟知的问题。

于 2013-04-26T01:07:01.670 回答
0

计算拼字游戏点中单词的每个字母的值:E=1、D=2、V=4、X=8 等等。将它们相加并除以字母的数量以获得平均字母值,并使用它对单词进行评分。计算大型词典中每个单词的平均值,并确定四分位数之间的断点。将最低四分位数中的单词称为“容易”,将两个中间四分位数中的单词称为“中等”,将最高四分位数中的单词称为“困难”。

于 2013-05-09T18:17:33.807 回答