问题背景

给定一组特定的语法规则、语料单词,而后依此,不断替换所有概念词,直到生成一句具体的句子。

概念词包括:句子、主语、主语s、代号等,这些指向某一个语法概念,实际不应出现在最终句子中的词。
具体词包括:苹果、小狗、喜欢、吃掉、漂亮、一个、但是、而且等,这些构成最终句子的词。

算法思路

  1. 确定语法规则、语料单词。
    “=”左边,是一个概念词;“=”右边,是这个概念词,对应可能的情况,各种情况之间用“|”隔开。
grammar_rule = """
复合句子 = 句子 连词 复合句子 | 句子 
句子 = 主语s 谓语 宾语s
谓语 = 喜欢 | 讨厌 | 吃掉 | 玩 | 跑
主语s = 主语 和 主语 | 主语
宾语s = 宾语 和 宾语 | 宾语
主语 = 冠词 定语 代号
宾语 = 冠词 定语 代号
代号 = 名词 | 代词
名词 = 苹果 | 鸭梨 | 西瓜 | 小狗 | 小猫 | 滑板 | 老张 | 老王
代词 = 你 | 我 | 他 | 他们 | 你们 | 我们 | 它
定语 = 漂亮的 | 今天的 | 不知名的 | 神秘的 | 奇奇怪怪的
冠词 = 一个 | 一只 | 这个 | 那个
连词 = 但是 | 而且 | 不过
"""
  1. 将1中的语法规则,转换为字典形式,方便后续程序使用
    其中,“=”左边就是字典的“键”,“=”右边就是字典的“值”。我们把“=“右边的内容,用”|“分割成列表。
def parse_grammar(rule):
    grammar = {}

    for line in rule.split('\n'):
        if len(line.strip()) == 0: continue  # 如果是空行,就跳过
        ic(line)
        target, expands = line.strip().split('=')  # 分割“=”左右两边,左边为键,右边为值
        grammar[target.strip()] = expands.split('|')  # 将“=”右边的,用“|”分割成一个列表

    return grammar
  1. 语句生成
  • 实现的原理,就是从语法规则中的“复合句子”开始,不断将其中的概念词进行替换,直到句子中没有概念词为止。
  • 举个例子,从复合句子开始。它对应了两种可能句子 连词 复合句子或者句子。用这两种任意选择一种进行替换。简单起见,选择后者。那么句子替换为主语s 谓语 宾语s
  • 其中主语s可以随机替换为主语 和 主语或者主语。后面的谓语宾语s,同样通过查询,就可以将整句替换为主语 谓语 宾语(这里随机替换了一种最简单的,方便讲解)。
  • 主语 谓语 宾语–》冠词 定语 代号 喜欢 冠词 定语 代号–》这个 漂亮的 名词 喜欢 一个 神秘的 名词–》这个 漂亮的 小猫 喜欢 一个 神秘的 苹果
def gene(target, grammar):
    if target not in grammar: return target

    expand = random.choice(grammar[target])

    return ''.join([gene(e, grammar) for e in expand.split()])
  1. 调用运行
    gene('复合句子', parse_grammar(grammar_rule))

总结延伸

句子生成算法,通过一个给定的语法结构、语料单词,来生成一句完整的句子,是一个很好的上手锻炼代码能力的小算法。

熟练了之后,还可以考虑自己写一个数学表达式的语法规则。