名前の辞書に基づいて電子メールから名前を推測するために文字列を歩いていますか?

名前の辞書(巨大な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"]

言い換えれば、辞書内で一致するものが見つかるまで、その単語(つまり、解析されていない電子メール文字列 "[email protected]")を繰り返しループします。 わかっています:これは非常に非効率的です。これを行うもっと簡単な方法があれば、私はすべて耳です!

より良い方法がない場合は、上の例を修正する方法を教えてください。これには2つの大きな欠点があります。(1)ループの長さを設定するにはどうすればいいですか? (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
1
さらに研究では、 "i"は三角形の数列を使って導かれることが示されている:a(n)= C(n + 1,2)= n(n + 1)/ 2 = 0 + 1 + 2 + n。 oeis.org/ &hellip;
追加された 著者 MorningHacker,

5 答え

r = /^(#{Regexp.union(dict)})(#{Regexp.union(dict)})$/
word.match(r)
=> #

正規表現は構築に時間がかかるかもしれませんが、速くて驚異的です。

5
追加された
文字列の始まり/終わり
追加された 著者 Reactormonk,
私はそれが好きですが、あなたは$境界が欲しいと思います
追加された 著者 pguardiario,
^ $境界は何ですか?
追加された 著者 MorningHacker,

私はあまり優雅ではないが、それでもなお役に立つ有用な解決策を提案する

  • アイテム数が多い(正規表現を構築するのが苦しい)
  • 解析する文字列は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"]

私はそれを広範囲にテストしていないので、私は事前に謝罪します:)

3
追加された
私はこれがどこに向かうのが好きですが、 "j"が辞書にない場合、このアプローチは "johnjsmith"には困難があります。ティン・マンのアプローチは、 "j"を無視し、文字列内の他のマッチを見つけるように見えます。
追加された 著者 MorningHacker,
しかし...私は@dictにアルファベットの個々の文字をすべて追加できるように見えますが。その場合、あなたのメソッドは "john j smith"を返します。かなり良い!
追加された 著者 MorningHacker,

あなたの辞書に一致するヒットがほしいだけの場合:

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

あまりにも多くの混乱するサブヒットのリスクを冒すので、長い名前が最初になるように辞書を並べ替えることができます:

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

より長い名前の後ろに続く部分文字列が短く、複数のヒットを取得する状況に遭遇するので、それらを除外する方法を見つけ出す必要があります。最初の名前ともう1つの名前の配列を持つことができ、最初に返されたそれぞれの結果を返すことができますが、ファーストネームとファーストネームの多様性を考慮すると、100%の精度は保証されず、悪い結果。

この種の問題には、人の名前についてのコードへのさらなるヒントなしに、本当の良い解決策はありません。おそらく、挨拶や評価セクションのメッセージの本文をスキャンすることが役に立ちます。

2
追加された

私はあなたが何をやっているのかは分かりませんが、それほど単純ではありません:

dict.each do |first|
    dict.each do |last|
        puts first,last if first+last == word
    end
end
0
追加された

これは必ずしも正確に2つではないすべての出現を覆う:

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
0
追加された