【问题标题】:Walking over strings to guess a name from an email based on dictionary of names?遍历字符串以根据名称字典从电子邮件中猜测名称?
【发布时间】:2012-01-06 02:29:54
【问题描述】:

假设我有一本名称字典(一个巨大的 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"]

不错,但我想要“John Smith”或 ["john", "smith"]

换句话说,我递归地遍历单词(即未解析的电子邮件字符串“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

【问题讨论】:

  • 进一步研究表明,“i”可以使用一系列三角数推导出来:a(n) = C(n+1,2) = n(n+1)/2 = 0+ 1+2+...+n。 oeis.org/…

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


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

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

【讨论】:

  • 我喜欢它,但我认为你想要 ^$ 边界
  • ^$ 边界是干什么用的?
【解决方案2】:

我敢建议一种暴力解决方案,虽然不是很优雅,但在万一时仍然有用

  • 您有大量项目(构建正则表达式可能很痛苦)
  • 要分析的字符串不限于两个组件
  • 你想得到一个字符串的所有拆分
  • 您只需要对从 ^ 到 $ 的字符串进行完整分析。

由于我的英语很差,我想不出一个可以分成多种方式的长个人名字,所以让我们分析一个短语:

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"]

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

【讨论】:

  • 我喜欢这个方向,但是当字典中没有“j”时,这种方法对于“johnjsmith”有困难。 @the Tin Man 的方法似乎忽略了“j”并在字符串中查找其他匹配项。
  • 虽然...看起来我可以将字母表中的所有单个字母添加到@dict。在这种情况下,您的方法返回“john j smith”。不错!
【解决方案3】:

如果您只想在字典中找到匹配项:

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

您可能会遇到太多令人困惑的子点击,因此您可能需要对字典进行排序,以便将较长的名称排在第一位:

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

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

如果没有进一步提示有关此人姓名的代码,此类问题没有真正好的解决方案。也许扫描邮件正文中的称呼或告别部分会有所帮助。

【讨论】:

    【解决方案4】:

    我不确定你在用 i 做什么,但它不是这么简单:

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

    【讨论】:

      【解决方案5】:

      这一项囊括了所有的出现,不一定是两个:

      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
      

      【讨论】:

        猜你喜欢
        • 2010-12-21
        • 2015-04-09
        • 1970-01-01
        • 1970-01-01
        • 2020-11-06
        • 2013-10-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多