使用正则表达式解析自然语言音乐引文

时间:2011-08-16 01:26:24

标签: regex nlp

我正在努力寻找一个相当复杂的正则表达式来解析歌曲标题,并从松散类型的英语中选择艺术家归属。用户输入来自单个文本字段,正则表达式匹配将用于查询歌曲数据库以获取唯一的轨道ID。我需要能够得到这些比赛:

  • \1 =歌曲名称
  • \2 =艺术家

虽然在允许的格式中相当自由。

实施例

wold“by”应该将字符串分成歌曲标题和艺术家(但仅限于字边界);应该是带有/不带尾随空格的逗号:

  

宝贝再一次由布兰妮斯皮尔斯

     宝贝再一次,布兰妮斯皮尔斯

     宝贝再一次,布兰妮斯皮尔斯

  • \1 =宝贝再一次
  • \2 =布兰妮斯皮尔斯

这些误报是可以接受的:

  

沿着海湾

  • \1 = down
  • \2 =海湾
  

不管别人怎么说,那就是我不是

  • \1 =无论人们说我是什么
  • \2 =这就是我不是

...假设引号可用于将一行文本标记为歌曲标题:

  

“沿着海湾走下去”

  • \1 =在海湾旁边
  • \2不匹配
  

“无论人们说我是谁,这都是我不是”北极猴子

  • \1 =无论人们说我是什么,那就是我不是
  • \2 =北极猴子

单引号也应该有效,但显然不会出现在标题中:

  

'无论别人说我是谁,那就是我不是'

  • \1 =无论人们怎么说,
  • \2 = s我不是什么'

此外,如果正在使用引号,则“by”或逗号这个词是可选的:

  

“海湾下来”raffi

  • \1 =在海湾旁边
  • \2 = raffi

但是,如果没有引号,并且有多个“by”,则只应使用最后一个“by”作为分隔符:

  

在拉菲海湾旁边

  • \1 =在海湾旁边
  • \2 = raffi

单个正则表达式甚至可以实现这一点吗?或者更明智的方法是将它分成多个表达式?无论哪种方式,这看起来像什么?

3 个答案:

答案 0 :(得分:3)

以下是使用C#:

的示例
var regex = @"^((""(?<title>[^""]+)""|'(?<title>[^']+)')(\s*,\s*|\s+by\s+)?|(?<title>.*)(\s*,\s*|\s+by\s+))\s*(?<artist>.*)$";

var items = new []{
    "baby one more time by britney spears",
    "baby one more time, britney spears",
    "baby one more time,britney spears",
    "down by the bay",
    "whatever people say i am, that's what i'm not",
    "\"down by the bay\"",
    "\"whatever people say i am, that's what i'm not\" by arctic monkeys",
    "'whatever people say i am, that's what i'm not'",
    "\"down by the bay\" raffi",
    "down by the bay by raffi",
};

foreach (var item in items)
{
    var match = Regex.Match(item, regex, RegexOptions.ExplicitCapture);
    Console.WriteLine(match.Groups["title"] + " - " + match.Groups["artist"]);
}

输出符合您的规格,据我所知:

baby one more time - britney spears
baby one more time - britney spears
baby one more time - britney spears
down - the bay
whatever people say i am - that's what i'm not
down by the bay - 
whatever people say i am, that's what i'm not - arctic monkeys
whatever people say i am, that - s what i'm not'
down by the bay - raffi
down by the bay - raffi

通过在单词中包含撇号,实际上可以使单引号更好:

var regex = @"^((""(?<title>[^""]+)""|'(?<title>([^']|(?<=\w)'(?=\w))+)')(\s*,\s*|\s+by\s+)?|(?<title>.*)(\s*,\s*|\s+by\s+))\s*(?<artist>.*)$";

修复了这种情况:

whatever people say i am, that's what i'm not - 

以下是正则表达式的注释版本,它解释了每个部分的作用(应与RegexOptions.ExplicitCapture|RegexOptions.IgnorePatternWhitespace匹配):

var regex = @"
^
  (
    (
      ""(?<title>[^""]+)""               (?# matches a double-quote string )
    | '(?<title>([^']|(?<=\w)'(?=\w))+)' (?# matches a single-quote string, allowing quotes in words )
    ) (\s*,\s*|\s+by\s+)?   (?# optionally follow these by ',' or 'by' )
  | 
  (?<title>.*)(\s*,\s*|\s+by\s+) (?# otherwise, everything up to ',' or 'by' )
)
\s*(?<artist>.*) (?# everything after this is the artist name )
$";

编辑:

我使用PHP代码玩了一下,但我无法正确使用命名捕获组。这是一个使用未命名捕获组的版本:

$regex = "/^(?:(?:\"([^\"]+)\"|'((?:[^']|(?<=\\w)'(?=\\w))+)')(?:\\s*,\\s*|\\s+by\\s+)?|(.*)(?:\\s*,\\s*|\\s+by\\s+))\s*(.*)\$/";

preg_match($regex, '"down by the river"', $matches);

print_r($matches);

标题将在第1,2或3组,第4组为艺术家。

答案 1 :(得分:2)

根据您发布的示例,我当然不会尝试为所有情况编写单个正则表达式,除非有一些令人信服的理由这样做。写这样一个表达方式,我认为是可能的,会非常脆弱,并且可能很难维护。

听起来你只是有一些简单的基于规则的处理,我会这样对待。您可以将每个规则都设为正则表达式,以您喜欢的顺序存储它们,然后随着您获得更多处理经验,您可以尝试确定是否有更好的顺序,可能取决于解析的百分比你想要的方式。

只是反复尝试优化您的规则;您可能会开始注意到更复杂的模式,您可以扩展规则类,以便针对一个规则考虑多个步骤,例如:也许你注意到对于一个特定的规则,它失败了,但如果你要对该规则添加额外的检查,你可以清除大部分失败。

对于每个正则表达式,我认为最简单的可能是最好的,并且没有一个单独的规则可能需要那么复杂,尤其是最初。正则表达式是非常强大的工具,但我不会过分关注尝试将自然语言解析成更适合解析定义良好的正式语言的东西。 (因此,“常规”部分。)

另外一个让我感到高兴的想法是考虑到在某些情况下您可能会发现在输入文本上运行某种一致性可以使处理更容易,例如通过减少处理的数量你必须处理的案件。要使用提供的示例中的(可能是好的或坏的)示例,您可以使用规则来处理X by Y,使用规则来处理X, Y和规则来处理"X" Y运行一个过滤器,将by[space]替换为,,将,[space]替换为,,将"X"[space]替换为X,。然后在结束时,你只有 左侧X,Y,这意味着你只需要处理一个案例。可能过于简单化的一个例子是有用的,但它是一个很好的模式,能够搜索;有时,一致性可以大大简化这种处理。

答案 2 :(得分:0)

我会采用更多统计/垃圾邮件过滤器的方式,将自然语言缩减为单词数组,然后测量组成标题的单词与艺术家姓名之间的距离。

regexp术语中,这可能意味着转换单个\w+中的每个普通单词(-)以及!中标题和作者中的每个单词

但这只是一种可视化单词运行的奇特方式。