如何创建食谱/成分解析器

时间:2012-12-22 06:53:48

标签: parsing

给出一行如

  
    

1磅牛肉

  

我想提取这种成分。最初我只对成分名称感兴趣

我看过rubys着名的时间解析器Chronic并喜欢它使用正则表达式。

 def self.scan_for_month_names(token)
scanner = {/^jan\.?(uary)?$/ => :january,
           /^feb\.?(ruary)?$/ => :february,
           /^mar\.?(ch)?$/ => :march,
           /^apr\.?(il)?$/ => :april,
           /^may$/ => :may,
           /^jun\.?e?$/ => :june,
           /^jul\.?y?$/ => :july,
           /^aug\.?(ust)?$/ => :august,
           /^sep\.?(tember)?$/ => :september,
           /^oct\.?(ober)?$/ => :october,
           /^nov\.?(ember)?$/ => :november,
           /^dec\.?(ember)?$/ => :december}
scanner.keys.each do |scanner_item|
  return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
end
return nil

然而在我的情况下,Id可能必须为每种成分创建超过300个正则表达式。

我还必须考虑诸如 Cilantro& amp;等同义词。 Corriander Leaf

我以前从未进行过解析,但是在这里使用正则表达式仍然是最好的方法。我想不出任何其他合理的选择。

3 个答案:

答案 0 :(得分:3)

首先,我假设成分并不总是采用QUANTITY UNIT of INGREDIENT的形式 - 否则,这将是一项非常简单的任务(只需复制of之后的子串

这是一个难题 - 解决方案并不简单。

我认为使用正则表达式可能不是最好的方法:

  • 如你所说,你必须为每个表达很多表达 成分
  • 您的可能成分列表将始终受到限制 通过正则表达式列表,你无法检测新的成分 编译更多。
  • 解析一些成分(cheese, 1 pound (parmesan)
  • 将非常困难

我认为natural language processing是前往此的方式。您有非结构化输入,但在非常有限的环境中。

也许与直觉相反,我认为找到这种成分的最佳方式可能是寻找它 - 而是寻找其他一切。如果你假设一条线总是

  • 数字(数量)
  • 一个单位(磅,茶匙等)
  • 一种成分

并且检测数字和单位非常容易,首先要识别它们然后然后提取成分应该是直截了当的。

如果您使用词性标注器,则可以轻松识别相关字词: [('1', 'LS'), ('pound', 'NN'), ('of', 'IN'), ('Beef', 'NNP')]

从那里,您可能想要使用classifier。为此,您需要在大量的线条(例如数百个)上手动标记成分。一些可能使用的好功能:

  • 单词在行中的位置
  • 存在于预先计算的成分词典中(可能使用某些部分字符串匹配度量标准,如Levenshtein's
  • 词性标注器的输出
  • 之前和之后的单词(如果你在单词之前有一个'of',很可能它是一个成分

我确信在完成几行工作后你将能够找到无数其他人。

最后,我希望有些线路很难处理。 1 pound of parmesan cheese, 1 pound of emmentaler:你必须推断第二种成分也是奶酪。

对于软件,如果你可以选择使用的语言,python就会有很棒的Natural Language Toolkit。我不能保证其他语言的工具包,但也许别人会。

答案 1 :(得分:1)

我想我会先对每一行运行一系列正则表达式检查,然后调整解析后的文本。例如(伪代码):

首先,检查说明:

/^(add|fold in|stir in|etc...)/

如果找到了指令,请将其从行中删除,设置一个标志,然后继续:

instruction = $1
this_line = this_line.substring(instruction.length())

如果找到了指令,请检查是否有后续指令(例如"并覆盖"或"并留出")

/\b(and\s)(.*)$/

如果找到,请将其剥离并在配方的下一行

之前插入
instruction = instruction.substring(0, instuction.length - $1.length - $2.length)
splice $2 into the array of lines immediately following this one

接下来,也许你会检查一个介词:

/((?in)to\s(.+)/

如果找到,您可以使用它来检查设备名称,碗,量杯等。 即使您不使用它,也可以将其从您正在解析的字符串中删除,以改善匹配。

最后,真正的工作是用左边的文字完成的:

Check against /^(\d+\s+(?a\s)?\w+)\s*(?of\s*)?(.+)$/

哪个应该给你$1包含度量单位和$2包含成分。

泡沫。冲洗。重复。 在那之后,做你的应用程序对这些信息所做的任何魔术。

答案 2 :(得分:0)

首先,我建议进行搜索以查看是否有人已经为该问题创建了一个解决方案,足以供您使用,而不是重新发明轮子。

例如,您可能会发现this project很有趣。它使用机器学习来尝试解析成分短语,包括成分类型和数量。

在搜索“成分分析器”时也会出现其他有趣的项目。

如果您真的决心自己编写此代码,那么建议您对称为“解析器生成器”的软件工具类别进行一些研究,该工具可让您编写所需的语言。以抽象形式(“语法”)识别,然后将以您选择的语言生成代码,该代码将根据该语法解析文本,并将非常高效地识别其中的特定子结构(比数百种方法要高效得多)正则表达式匹配)。

例如,用作解析器生成器输入的语法可能看起来像这样:

// I am making up the following syntax for demonstration purposes, but it illustrates the
// sort of things that one could specify in a grammar, and is not terribly different from
// the grammar languages that real parser generators use.
//
// Note that everything in the curly braces is code to be inserted into the generated parser.
// Each such code block will be invoked when the preceding parsing rule is matched.

%declare { bool organic=false; bool dried=false; bool smoked=false; }

INGREDIENT ::= "organic" INGREDIENT          { organic=true; }
             | INGREDIENT "(" "organic" ")"  { organic=true; }
             | "dried" INGREDIENT            { dried=true; }
             | "smoked" INGREDIENT           { smoked=true; }
             | AMOUNT "of" INGREDIENT
             | INGREDIENT "(" AMOUNT ")"
             | BASE_INGREDIENT

BASE_INGREDIENT ::= ( WORD )* {
   doSomethingWithBaseIngredient(organic, dried, smoked, $BASE_INGREDIENT);
}

AMOUNT ::= NUMBER ( VOLUME_UNIT | WEIGHT_UNIT )
VOLUME_UNIT ::= "cup" | "liter"
WEIGHT_UNIT ::= "mg" | "kg" | "pound"
NUMBER ::= [0-9]+
WORD ::= [a-zA-Z]+

... and so forth.

解析器生成器在运行时将以该语法作为输入,并以您所需的编程语言生成代码作为输出。此代码将根据语法解析输入文本,并且在匹配某些解析规则时,还可根据需要设置变量和/或调用函数。由此类工具生成的解析器通常使用特殊的解析技术(通常涉及大型表,状态机等)来高效地解析一次,而不必做多余的工作,并在可能的情况下避免回溯。

解析器生成器的一些常见示例是lexx / yacc,bison和Antlr。存在许多其他。 (就我个人而言,过去我在Antlr上取得了不错的成绩,并且特别喜欢它可以以许多不同的编程语言生成解析器的事实。)这些解析器生成器中的许多主要供编译器作者使用,但是确实如此。并不意味着它们不能用于其他目的,例如识别食谱中成分的各种形式。

This article提供了解析器生成器的概述,并且this article包含了各种解析器生成器及其属性(输出语言等)的表,以及有关在何处查找更多内容的链接。