将刽子手难度级别的单词分类为“简单”,“中等”或“硬”的算法

时间:2013-04-25 19:36:58

标签: algorithm

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

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

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

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

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

我正在写一个我希望我的孩子们玩的刽子手游戏;我太老了,不能尝试“做作业”,这可能就是为什么这个问题得到了这么多的选票...... 从大字数据库中随机抽取单词,其中包括许多模糊的单词,并按照为单词确定的难度级别进行过滤。

12 个答案:

答案 0 :(得分:92)

1。引言

这是一种系统地解决这个问题的方法:如果你有一个很好地扮演刽子手的算法,那么你可以把每个单词的难度都算作你的程序在猜测那个单词时会采取的错误猜测的数量。

2。除了刽子手策略

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

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

3。一个刽子手算法

在这里,我将概述一个非常好的解算器(但远非完美)。它将setter建模为从固定字典中统一选择单词。它是greedy algorithm:在每个阶段它都会猜到最小化未命中数的字母,即不包含猜测的单词。例如,如果到目前为止没有进行猜测,可能的单词是DEEDDEADDARE,那么:

  • 如果您猜到DE,则没有错过;
  • 如果你猜A,就会有一个错过(DEED);
  • 如果你猜R,则会有两次失误(DEEDDEAD);
  • 如果您猜到任何其他字母,则有三个未命中。

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

(感谢Colonel Panic in comments指出正确的猜测在刽子手中是免费的 - 我在第一次尝试时完全忘记了这一点!)

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。选择词表

当然,在为您的孩子准备单词表时,您不会从计算机的系统词典开始,而是从您认为他们可能知道的单词列表开始。例如,您可以查看各种英语语料库中的Wiktionary's lists of the most frequently used words

例如,在10,000 most common words in Project Gutenberg as of 2006中的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是Forsyte Saga by John Galsworthy中的一个字符;单词列表已转换为小写字母,因此我无法快速删除正确的名称。)

答案 1 :(得分: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

答案 2 :(得分:9)

您可以使用Monte Carlo Method来估算单词的难度:

  • 通过每次猜测一个随机字母来模拟游戏,用目标语言中的字母频率加权,并计算您的随机玩家到达解决方案所需的猜测次数。请注意,由于每次猜测都会删除一个字母,因此该过程是有限的,它会返回1到26之间的数字。
  • 重复此过程2*N次,其中N是您单词中唯一字母的数量,
  • 通过平均2*N次运行
  • 的结果来计算得分
  • 确定复杂程度:小于10的分数表示一个简单的单词,十六分以上的分数表示难以理解的单词;其他一切都是中等的。

答案 3 :(得分:4)

以前关于同一主题的类似讨论: Determine the difficulty of an english word

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

为每个字母指定一个点值,然后将这些字母加起来。

答案 4 :(得分:3)

做吧!玩刽子手反对这个词。计算击败需要多少次罚款(即不正确的猜测)。

你需要一个策略来玩。这是一个人类(ish)战略。从字典中删除所有不符合显示内容的词。猜猜剩下的字中最常出现的字母。

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


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

答案 5 :(得分:3)

前段时间我用一个明显的算法编写了一个刽子手解算器:给出一个所有可能单词的初始字典,在每个回合中我们选择字典中剩余的大多数单词中出现的字母,然后删除不匹配的单词(字典中的答案)。

算法不是那么简单,因为通常有几个字母,每个字母出现在字典中相同数量的单词中。在这种情况下,字母的选择可以对一个单词需要多少猜测产生显着差异。我们选择最大值,其中有关该字母放置的结果信息(如果确实在单词中)提供了有关系统的最大信息(具有最大information entropy的字母)。例如如果剩下的两个可能的单词是'百科全书'和'百科全书',则字母'c'出现的概率与e,n,y,l,o,p,e,d,i相同(即保证在单词中),但我们应首先询问'c',因为它具有非零信息熵。

来源(C ++,GPL)是here

所有这些的结果是一个单词列表,每个单词都需要猜测次数:difficulty.txt(630KB)。这个算法找到的最难的词是“将”(14个失败的猜测); i和double l猜得很快,但随后的选项包括bill,dill,fill,gill,hill,kill,mill,pill,rill,till,will,从那以后唯一的选择是猜猜每个字母转。有点违反直觉的是,更长的单词被更快地猜到了(他们可能没有选择)。

当然,在人类的刽子手游戏中,心理学(和词汇的广度)比这个算法占据的角色要大得多......

答案 6 :(得分:2)

首先,当然,您要生成一个独特字母列表。然后按频率排序(用英语或其他任何语言 - there are lists for this),频率较低的字母难度较高。

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

答案 7 :(得分:1)

你被低估了,因为你要求我们为你构建一个非常复杂的算法。

为什么不创建三个数组(简单,中等和硬)并用每一百个单词填充每个数组?大约需要20分钟。

我保证你的孩子在他们烧掉几百场比赛之前就会厌倦挂人......:D

答案 8 :(得分:1)

嗯,可能涉及很多事情:

  1. 正如大家所说,个别字母的频率;
  2. 一个单词的长度肯定应该计算,但不能以线性方式计算 - 一个长单词可以使随机猜测击中字母,而短单词可能很难得到;
  3. 此外,应该考虑单词本身 - “二分”可能是SO上的一个词,但也许不适用于非技术人群。
  4. 实际上,你可以try to co-evolve several strategies,其中一半用于决定一个词的价值,一半用于试图赢得比赛。后一组将尝试最大化得分,而第一组试图最小化得分。过了一会儿可能有一个模式,然后决定一个单词价值的一半可能会给你一些基准。

答案 9 :(得分:1)

从单词列表开始,为每个单词启动Google搜索。让命中数作为术语难度的(粗略)代理。

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

采用n-gram的概念更进一步,Word的难度可以通过散文中其音节的频率来评定。当然,取决于音节统计的质量。你可能不得不区分Lexemes和功能词(决定者,连词等)和按字母数量标准化(感觉像我写的一样过度杀戮......)。

答案 10 :(得分:0)

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

答案 11 :(得分:0)

计算拼字游戏中单词的每个字母的值:E = 1,D = 2,V = 4,X = 8,依此类推。将它们相加并除以字母数以获得平均字母值,并使用它来对单词进行评分。计算大字典中每个单词的平均值,并确定四分位数之间的断点。调用最低四分位数中的单词“easy”,两个中间四分位数中的单词“medium”,以及最高四分位数中的单词“hard”。