RegexpTrie , которого не было рядом, когда я в последний раз искал что-то подобное, помогает с такой проблемой:
require 'regexp_trie'
sentence = 'life on the mississippi'
words_ary = %w[the sip life]
words_regex = /\b(?:#{RegexpTrie.union(words_ary, option: Regexp::IGNORECASE).source})\b/i
# => /\b(?:(?:the|sip|life))\b/i
words_to_ints = words_ary.each_with_index.to_h
# => {"the"=>0, "sip"=>1, "life"=>2}
sentence_words = sentence.split
# => ["life", "on", "the", "mississippi"]
word_hits = sentence_words.map { |w| w[words_regex] }
# => ["life", nil, "the", nil]
nil
означает, что в регулярном выражении не было совпадения этого слова.
words_to_ints.values_at(*word_hits)
# => [2, nil, 0, nil]
Опять же, nil
означает, что совпадений не было. nil
значения можно игнорировать, используя:
word_hits = sentence_words.map { |w| w[words_regex] }.compact
# => ["life", "the"]
words_to_ints.values_at(*word_hits)
# => [2, 0]
Аналогично, если вы хотите отсканировать предложение на предмет совпадения слов вместо отдельных слов:
require 'regexp_trie'
sentence = 'life on the mississippi'
words = %w[the sip life]
words_regex = /\b(?:#{RegexpTrie.union(words, option: Regexp::IGNORECASE).source})\b/i
# => /\b(?:(?:the|sip|life))\b/i
words_to_ints = words.each_with_index.to_h
# => {"the"=>0, "sip"=>1, "life"=>2}
word_hits = sentence.scan(words_regex)
# => ["life", "the"]
words_to_ints.values_at(*word_hits)
# => [2, 0]
В Perl есть действительно полезный модуль для такого рода вещей, который называется Regexp :: Assemble , который позволяет объединять регулярные выражения в один большой, а затем искать строку, возвращая попадания. Вы можете попросить его указать, какой шаблон использовался, если хотите знать.
В Ruby такого модуля нет, но это довольно близко:
patterns = {
/(foo)/ => 1,
/(bar)/ => 2
}
pattern_union = Regexp.union(patterns.keys)
pattern_union # => /(?-mix:(foo))|(?-mix:(bar))/
str = 'foo some text'
if (pattern_union =~ str)
# these show what are being processed...
pattern_union.match(str).captures # => ["foo", nil]
pattern_union.match(str).captures.zip(patterns.keys).find_all{ |c| c[0] }.map{ |c| c[1] } # => [/(foo)/]
# process it...
matched_pattern_values = patterns.values_at(*pattern_union.match(str).captures.zip(patterns.keys).find_all{ |c| c[0] }.map{ |c| c[1] })
# here's what we got
matched_pattern_values # => [1]
end
Возможно, есть способ сделать это в одну строку, но это работает.
Я думаю, что важно избегать итерации по шаблонам для поиска совпадений в строках, если это вообще возможно, потому что они могут сильно замедляться при увеличении размера текста или количества шаблонов.
См. " Существует ли эффективный способ выполнения сотен подстановок текста в Ruby? " для получения дополнительной информации об использовании Regexp :: Assemble из Ruby.