Сбор всех совпадений строкового значения из массива шаблонов регулярных выражений с определением приоритета ближайших совпадений

Допустим, у меня есть массив имен вместе с их регулярным выражением:

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

Это возможно?

Чего вы ожидаете от "dannnniel"=~/.*/, который более «близок», чем "dannnniel"=~/Dan/i?

dawg 09.07.2024 17:47

Каким было бы желаемое возвращаемое значение, если бы строка была "dan and daniel"?

Cary Swoveland 09.07.2024 21:26
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
2
137
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

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

Я думаю, вы могли бы сделать следующее:

  1. Отсортируйте массив ваших регулярных выражений по длине символов в них (если вы не хотите сортировать его вручную):

    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]
    
  2. Перебирайте его, чтобы найти совпадения (сначала он найдет лучшее совпадение, поскольку сначала проверяются самые длинные регулярные выражения):

    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] будет добавлена ​​в хеш. В первую очередь это отражает неясность вопроса.

Cary Swoveland 11.07.2024 02:46

Хотя это не объединяет и не использует ваш список, я решил предоставить другой вариант, используя обратную ссылку для «корня» «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"]}]

Спасибо за редактирование. Я вижу, что я был несовместим со словом «граница». Сейчас я удалил его.

Cary Swoveland 10.07.2024 21:47

Вы могли бы написать

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» в режиме свободного пространства.

Другие вопросы по теме