Допустим, у меня есть массив имен вместе с их регулярным выражением:
match_array = [/Dan/i, /Danny/i, /Daniel/i]
match_values = Regexp.union(match_array)
Я использую объединение регулярных выражений, поскольку фактический набор данных, с которым я работаю, содержит строки, которые часто содержат лишние символы, пробелы и различную капитализацию.
Я хочу перебрать серию строк, чтобы увидеть, соответствуют ли они какому-либо значению в этом массиве. Если я использую .scan
, возвращается только первый соответствующий элемент:
'dan'.scan(match_values) # => ["dan"]
'danny'.scan(match_values) # => ["dan"]
'daniel'.scan(match_values) # => ["dan"]
'dannnniel'.scan(match_values) # => ["dan"]
'dannyel'.scan(match_values) # => ["dan"]
Я хочу иметь возможность фиксировать все совпадения (именно поэтому я решил использовать .scan
вместо .match
), но сначала хочу расставить приоритеты для наиболее близких/наиболее точных совпадений. Если ничего не найдено, я бы хотел по умолчанию использовать частичные совпадения. Таким образом, результаты будут выглядеть так:
'dan'.scan(match_values) # => ["dan"]
'danny'.scan(match_values) # => ["danny","dan"]
'daniel'.scan(match_values) # => ["daniel","dan"]
'dannnniel'.scan(match_values) # => ["dan"]
'dannyel'.scan(match_values) # => ["danny","dan"]
Это возможно?
Каким было бы желаемое возвращаемое значение, если бы строка была "dan and daniel"
?
match_array = [/Daniel/i, /Danny/i, /Dan/i]
def prioritized_scan(string, match_array)
matches = []
match_array.each do |pattern|
string.scan(pattern) do |match|
matches << match unless matches.include?(match)
end
end
matches
end
p prioritized_scan('dan', match_array)
p prioritized_scan('danny', match_array)
p prioritized_scan('daniel', match_array)
p prioritized_scan('dannnniel', match_array)
p prioritized_scan('dannyel', match_array)
Выход
["dan"]
["danny", "dan"]
["daniel", "dan"]
["dan"]
["danny", "dan"]
Я думаю, вы могли бы сделать следующее:
Отсортируйте массив ваших регулярных выражений по длине символов в них (если вы не хотите сортировать его вручную):
match_array = [/Dan/i, /Danny/i, /Daniel/i]
sorted_regexes = match_array.sort_by{|x| -x.source.length}
p sorted_regexes
Выход:
[/Daniel/i, /Danny/i, /Dan/i]
Перебирайте его, чтобы найти совпадения (сначала он найдет лучшее совпадение, поскольку сначала проверяются самые длинные регулярные выражения):
def find_matches(string, sorted_regexes)
sorted_regexes.reduce([]) do |acc, regex|
match = string.match(regex)
acc.push(match[0]) if match
acc
end
end
p find_matches('dan', sorted_regexes)
p find_matches('danny', sorted_regexes)
p find_matches('daniel', sorted_regexes)
p find_matches('dannnniel', sorted_regexes)
p find_matches('dannyel', sorted_regexes)
Выход:
["dan"]
["danny", "dan"]
["daniel", "dan"]
["dan"]
["danny", "dan"]
Вы можете сделать что-то вроде этого:
match_array = [/Dan/i, /Danny/i, /Daniel/i]
strings=['dan','danny','daniel','dannnniel','dannyel']
p strings.
map{|s| [s, match_array.filter{|m| s=~m}]}.to_h
Распечатки:
{"dan"=>[/Dan/i],
"danny"=>[/Dan/i, /Danny/i],
"daniel"=>[/Dan/i, /Daniel/i],
"dannnniel"=>[/Dan/i],
"dannyel"=>[/Dan/i, /Danny/i]}
И при желании вы можете преобразовать регулярные выражения в строки любого регистра:
p strings.
map{|s| [s, match_array.filter{|m| s=~m}.
map{|r| r.source.downcase}]}.to_h
Распечатки:
{"dan"=>["dan"],
"danny"=>["dan", "danny"],
"daniel"=>["dan", "daniel"],
"dannnniel"=>["dan"],
"dannyel"=>["dan", "danny"]}
Тогда, если «ближайший» эквивалентен «самому длинному», просто отсортируйте по длине источника регулярного выражения (т. е. Dan
в регулярном выражении /Dan/i
):
p strings.
map{|s| [s, match_array.filter{|m| s=~m}.
map{|r| r.source.downcase}.
sort_by(&:length).reverse]}.to_h
Распечатки:
{"dan"=>["dan"],
"danny"=>["danny", "dan"],
"daniel"=>["daniel", "dan"],
"dannnniel"=>["dan"],
"dannyel"=>["danny", "dan"]}
Но это работает только с буквальными совпадениями строк. Чего вы ожидаете от "dannnniel"=~/.*/
, который более «близок», чем "dannnniel"=~/Dan/i
?
Предположим, что под «ближайшим» вы подразумеваете самую длинную подстроку, возвращаемую совпадением с регулярным выражением, поэтому что-то вроде /.*/
длиннее, чем любая подстрока сопоставляемой строки. Ты можешь сделать:
match_array = [/Dan/i, /Danny/i, /Daniel/i, /.{3}/, /.*/]
strings=['dan','danny','daniel','dannnniel','dannyel']
p strings.
map{|s| [s, match_array.filter{|m| s=~m}.
sort_by{|m| s[m].length}.reverse]}.to_h
Теперь сортировка осуществляется по длине совпадения и длине регулярного выражения:
{"dan"=>[/.*/, /.{3}/, /Dan/i],
"danny"=>[/.*/, /Danny/i, /.{3}/, /Dan/i],
"daniel"=>[/.*/, /Daniel/i, /.{3}/, /Dan/i],
"dannnniel"=>[/.*/, /.{3}/, /Dan/i],
"dannyel"=>[/.*/, /Danny/i, /.{3}/, /Dan/i]}
Обратите внимание: если 'mundane'
добавляется к strings
, пара ключ-значение "mundane"=>[/Dan/i]
будет добавлена в хеш. В первую очередь это отражает неясность вопроса.
Хотя это не объединяет и не использует ваш список, я решил предоставить другой вариант, используя обратную ссылку для «корня» «dan». /(dan)?(\g<1>(?:iel|ny)?)/i
Это предполагает, что каждая производная должна появляться только один раз, например:
"dandan"
будет показывать только ["dan"]
, а не ["dan","dan"]
; и"dandannydaniel"
будет ["dan","danny","daniel"]
, а не ["dan","dan","danny","dan","daniel"]
Пример:
a = %w[dan
danny
daniel
dannnniel
dannyel
dandan
dandannydaniel]
a.map {|s| {s => s.scan(/(dan)?(\g<1>(?:iel|ny)?)/i).flatten.uniq} }
#=> [{"dan"=>["dan"]},
# {"danny"=>["dan", "danny"]},
# {"daniel"=>["dan", "daniel"]},
# {"dannnniel"=>["dan"]},
# {"dannyel"=>["dan", "danny"]},
# {"dandan"=>["dan"]},
# {"dandannydaniel"=>["dan", "danny", "daniel"]}]
Спасибо за редактирование. Я вижу, что я был несовместим со словом «граница». Сейчас я удалил его.
Вы могли бы написать
rgx = /^(?=(dan))(?=(daniel|danny))?/i
Затем
["dan", "danny", "daniel", "dannnniel", "dannyel", "dannyboy", "dandan"].each do |str|
puts "#{str}: #{str.scan(rgx)}"
end
дисплеи
dan: [["dan", nil]]
danny: [["dan", "danny"]]
daniel: [["dan", "daniel"]]
dannnniel: [["dan", nil]]
dannyel: [["dan", "danny"]]
dannyboy: [["dan", "danny"]]
dandan: [["dan", nil]]
Демоверсия Ruby | Демонстрация регулярных выражений
Обратите внимание: для самодокументирования я выразил регулярное выражение по ссылке «Демо-версия Regex» в режиме свободного пространства.
Чего вы ожидаете от
"dannnniel"=~/.*/
, который более «близок», чем"dannnniel"=~/Dan/i
?