走过字符串,根据名字字典从电子邮件中猜出一个名字?

时间:2011-11-25 05:47:38

标签: ruby-on-rails ruby loops pattern-matching email-validation

假设我有一个名字词典(一个巨大的CSV文件)。我想从一个没有明显可解析点(。, - ,_)的电子邮件中猜出一个名字。我想做这样的事情:

  dict = ["sam", "joe", "john", "parker", "jane", "smith", "doe"]
  word = "johnsmith"
  x = 0
  y = word.length-1
  name_array = []
  for i in x..y
     match_me = word[x..i]
     dict.each do |name|
       if match_me == name
         name_array << name
       end
     end
  end   

  name_array
  # => ["john"]

不错,但我想要“约翰史密斯”或[“约翰”,“史密斯”]

换句话说,我递归循环遍历单词(即未解析的电子邮件字符串,“johndoe@gmail.com”),直到我在字典中找到匹配项。 我知道:这是非常低效的。如果有一个更容易的方法来做到这一点,我全都耳朵!

如果没有更好的方法,那么告诉我如何解决上面的例子,因为它有两个主要缺陷:(1)如何设置循环的长度(参见查找“i”的问题)下面),以及(2)如何在上面的例子中增加“x”,以便我可以在给定任意字符串的情况下循环遍历所有可能的字符组合?

找到循环长度的问题,“i”:

  for an arbitrary word, how can we derive "i" given the pattern below?

  for a (i = 1)
  a

  for ab (i = 3)
  a
  ab
  b

  for abc (i = 6)
  a
  ab
  abc
  b
  bc
  c

  for abcd (i = 10)
  a
  ab
  abc
  abcd
  b
  bc
  bcd
  c
  cd
  d

  for abcde (i = 15)
  a
  ab
  abc
  abcd
  abcde
  b
  bc
  bcd
  bcde
  c
  cd
  cde
  d
  de
  e

5 个答案:

答案 0 :(得分:5)

r = /^(#{Regexp.union(dict)})(#{Regexp.union(dict)})$/
word.match(r)
=> #<MatchData "johnsmith" 1:"john" 2:"smith">

正则表达式可能需要一些时间来构建,但它的速度非常快。

答案 1 :(得分:3)

我敢提出一个不太优雅的蛮力解决方案,但仍然有用

  • 你有大量的物品(建立正则表达可能会很痛苦)
  • 要分析的字符串不限于两个组件
  • 你想得到一个字符串的所有分裂
  • 您只需要对字符串进行完整分析,范围从^到$。

由于我的英语不好,我无法找出一个可以分成多个方式的长个人姓名,所以让我们分析一个短语:

word = "godisnowhere"

字典:

@dict = [ "god", "is", "now", "here", "nowhere", "no", "where" ]

@lengths = @dict.collect {|w| w.length }.uniq.sort

数组@lengths为算法添加了一些优化,我们将使用它来修剪字典中不存在的长度的子词,而不实际执行字典查找。数组已排序,这是另一种优化。

解决方案的主要部分是递归函数,它在给定的单词中查找初始子词并重新启动尾部子词。

def find_head_substring(word)

  # boundary condition:
  # remaining subword is shorter than the shortest word in @dict
  return []  if word.length < @lengths[0]

  splittings = []

  @lengths.each do |len|
    break  if len > word.length

    head = word[0,len]

    if @dict.include?(head)
      tail = word[len..-1]

      if tail.length == 0
        splittings << head
      else
        tails = find_head_substring(tail)
        unless tails.empty?
          tails.collect!{|tail| "#{head} #{tail}" }
          splittings.concat tails
        end
      end
    end
  end

  return splittings
end

现在看看它是如何运作的

find_head_substring(word)
=>["god is no where", "god is now here", "god is nowhere"]

我没有对它进行过广泛的测试,所以我提前道歉:)

答案 2 :(得分:2)

如果你只想要字典中的匹配点击:

dict.select{ |r| word[/#{r}/] }
=> ["john", "smith"]

您存在太多令人困惑的子命令的风险,因此您可能希望对字典进行排序,以便首先使用更长的名称:

dict.sort_by{ |w| -w.size }.select{ |r| word[/#{r}/] }
=> ["smith", "john"]

你仍然会遇到这样的情况:较长的名字后面有一个较短的子串并获得多次点击,因此你需要找到一种方法来清除它们。你可以有一个名字数组和另一个姓氏,并且每个都有第一个返回的扫描结果,但考虑到名字和姓氏的多样性,这不能保证100%的准确性,并且仍会收集一些糟糕的结果。

如果没有关于此人姓名的代码的进一步提示,这种问题就没有真正好的解决方案。也许扫描信息的正文以表示敬意或告诫部分会有所帮助。

答案 3 :(得分:0)

我不确定你在做什么,但不是那么简单:

dict.each do |first|
    dict.each do |last|
        puts first,last if first+last == word
    end
end

答案 4 :(得分:0)

这个包装出现了所有情况,不一定是两个:

pattern = Regexp.union(dict)
matches = []
while match = word.match(pattern)
  matches << match.to_s # Or just leave off to_s to keep the match itself
  word = match.post_match
end
matches