#ruby #regex
#ruby #регулярное выражение
Вопрос:
У меня есть следующие строки:
Chicago CPA
New York CPA
West Virginia Accountant
Как мне всегда просто отсекать последнее слово (и предшествующий пробел) в строке, сохраняя все остальные слова перед последним словом?
Таким образом, правильные версии приведенного выше набора данных будут:
Chicago
New York
West Virginia
Кроме того, можно ли протестировать совпадающие группы в Rubular или есть другой онлайн-редактор / тестер регулярных выражений, который я могу использовать для тестирования регулярных выражений с совпадающими группами?
Редактировать 1
Многие ответы хороши в теории. Я читаю их, понимаю их и тестирую их на ванильной строке, и они, кажется, работают. Но когда я пытаюсь применить это к своим данным, это не так. Я был в тупике некоторое время, и я только что понял, почему.
Это HTML, над которым я работаю:
<h1 class="search-term">
Chicagoamp;nbsp;<strong>Cpa</strong>
</h1>
Итак, это текст, я пытаюсь выполнить эту манипуляцию со строкой:
Chicagoamp;nbsp;<strong>Cpa</strong>
Итак, вот что происходит, когда я пробую каждый из приведенных ниже ответов.
@Darshan’s:
[56] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text
=> "Chicago Cpa"
[57] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text.class
=> String
[58] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text.match(/(.*) w z/)[1]
NoMethodError: undefined method `[]' for nil:NilClass
from (pry):57:in `<class:PageCrawler>'
[59] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text[/.*(?=sw z)/]
=> nil
@Lucas’s own:
[60] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text
=> "Chicago Cpa"
[61] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text.class
=> String
[62] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text.split()[0...-1].join(' ')
=> ""
@Собственное Эрика:
[65] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text
=> "Chicago Cpa"
[66] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text.class
=> String
[67] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text.split().reverse.drop(1).reverse.join(" ")
=> ""
собственный @Casimir (на самом деле, этот пока лучший):
[68] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text
=> "Chicago Cpa"
[69] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text.class
=> String
[70] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text.sub(/W w W*$/, '')
=> "Chicago"
собственный @Santosh:
[71] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text
=> "Chicago Cpa"
[72] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text.class
=> String
[73] pry(YPCrawler::PageCrawler)> @document.css('header h1.search-term').first.text[/(.*)s/,1]
=> nil
Приношу свои извинения за то, что не сделал этого раньше, но я не ожидал, что это будет проблемой.
Ответ №1:
Я начну с того, что скажу, что я не особенно хорошо разбираюсь в регулярных выражениях, и я не уверен в своей голове (и при этом я не склонен сравнивать или думать об этом), будет ли это более или менее эффективным, чем подход @LucasP без регулярных выражений. Но это очевидный подход, который приходит мне на ум:
s.match(/(.*) w z/)[1]
Это сопоставляет в конце строки один или несколько символов word, которым предшествует пробел, и помещает все перед этим в группу, которую вы затем захватываете.
data = ['Chicago CPA',
'New York CPA',
'West Virginia Accountant']
data.map{|s| s.match(/(.*) w z/)[1]}
# => ["Chicago", "New York", "West Virginia"]
Редактировать: вариант этого подхода, предложенный @CarySwoveland, заключается в использовании выражения lookahead для игнорирования части, которую мы хотим отбросить, вместо моего первоначального подхода, заключающегося в том, чтобы поместить нужную нам часть в группу захвата, к которой мы затем получаем доступ. Вот версия этого подхода:
data.map{|s| s[/.*(?=sw z)/]}
# => ["Chicago", "New York", "West Virginia"]
Редактирование 2: с добавленной вами информацией теперь ясно, что проблема, с которой вы столкнулись, заключается в том, что у вас есть неразрывные пробелы, которые даже с s
не совпадают ( s
совпадают только с пробелами ASCII, эквивалентными [ trnf]
). Таким образом, использование выражения в скобках POSIX [[:space:]]
или явное сопоставление u00A0
для неразрывного пробела работает, предполагая, что все они неразрывные пробелы. Я предпочитаю первое, так как иногда там могут быть другие пробелы:
data.map{|s| s[/.*(?=[[:space:]]w z)/]}
Комментарии:
1. Я вижу, к чему вы клоните, но это регулярное выражение, похоже
Chicago CPA
, не соответствует Rubular.com . Он соответствует только2. @marcamillion Опять же, это работает для меня как на моей машине, так и на rubular.com . Я бы сбросил ваш набор данных в шестнадцатеричный редактор и посмотрел, что происходит с вашими пробелами.
3. Я не заметил вашего ответа, когда опубликовал свой. Эти два были довольно близки, поэтому я убил свой.
4. @CarySwoveland Да, похоже, у нас был тот же общий подход, спасибо. Мне понравилась ваша предварительная версия, которая игнорирует часть, которую мы хотим отбросить, вместо того, чтобы группировать нужную часть и захватывать группу.
5. Я изо всех сил пытался понять, почему ваши предложения не работают с моими фактическими данными, но когда я пытаюсь использовать ванильную строку, она работает. Я обновил вопрос, добавив более подробную информацию о том, как на самом деле выглядят мои данные. Не могли бы вы соответствующим образом обновить свой ответ, пожалуйста? Спасибо!
Ответ №2:
Один из способов добиться этого заключается в следующем:
myString.split()[0...-1].join(' ')
Где myString
находится каждая строка, с которой вы хотите выполнить эту операцию.
-
Сначала вы разделяете строку на список, содержащий каждое слово.
-
Затем выберите подсписок, содержащий все элементы, кроме последнего.
-
Наконец, вы возвращаетесь от списка к строке.
Комментарии:
1. Итак, я изначально пробовал что-то подобное, но самое странное происходит, когда я пытаюсь разделить эти строки.
> "Chicago Cpa".split => ["Chicago Cpa"]
. Он не создает новый элемент для каждого слова … что я нахожу странным. Что может быть причиной этого?2. @marcamillion Когда я копирую и вставляю это, я получаю
["Chicago", "Cpa"]
ожидаемое. Возможно ли, что в ваших строках есть какие-то шаткие пробелы?3. Это очень странно. Не уверен, почему мой не разделяется должным образом. Я не вижу никаких шатких пробелов.
4. Ты не имеешь в виду
[0..-2]
?5.
[0..-2]
или[0...-1
], это то же самое.Ranges constructed using .. run from the beginning to the end inclusively. Those created using ... exclude the end value.
Ответ №3:
Предполагая, что у вас более одного слова, вы можете использовать замену:
'West Virginia Accountant'.sub(/W w W*$/, '')
Комментарии:
1. Каковы недостатки использования этого подхода? В каких случаях это приведет к ложному срабатыванию?
2. @marcamillion: это зависит от того, что вы называете «словом», например, этот шаблон не будет работать со словом с буквами с ударением (но это можно легко решить с помощью нескольких изменений) или с именем, содержащим кавычки, такие как «Scarlett O’Hara» => «Scarlett O» или с сокращениями»родился в США» => «родился в США», но вы можете изменить шаблон на
/p{Z} P{Z} p{Z}*$/
(гдеp{Z}
соответствует всем разделителям юникода).
Ответ №4:
"New York Accountant".split().reverse.drop(1).reverse.join(" ")
Комментарии:
1. Разве это не
split[0..-2]
более прямолинейно, чемsplit().reverse.drop(1).reverse
?2. Я изо всех сил пытался понять, почему ваше предложение не работает с моими фактическими данными, но когда я пытаюсь использовать ванильную строку, она работает. Я обновил вопрос, добавив более подробную информацию о том, как на самом деле выглядят мои данные.
Ответ №5:
Попробуйте выполнить следующее.
str = ['Chicago CPA', 'New York CPA', 'West Virginia Accountant']
str.map{|s| s[0...s.rindex(' ')]}
вывод: ["Chicago", "New York", "West Virginia"]
Использование регулярного выражения.
str2 = "West Virginia Accountant"
p str2[/(.*)s/,1]
вывод: "West Virginia"
Комментарии:
1. Я изо всех сил пытался понять, почему ваше предложение не работает с моими фактическими данными, но когда я пытаюсь использовать ванильную строку, она работает. Я обновил вопрос, добавив более подробную информацию о том, как на самом деле выглядят мои данные.
2. @marcamillion в вашей фактической строке оно содержит ‘amp; nbsp;’. поэтому мое регулярное выражение не может это обнаружить. итак, вы можете использовать регулярное
/W w W*$/
выражение. можете ли вы пройти мимо вашей фактической строки?
Ответ №6:
Вы можете использовать регулярное /^(.*)s w s*$/
выражение для записи всего, кроме последнего слова:
Пример:
str = <<~EOF
Chicago CPA
New York CPA
West Virginia Accountant
EOF
str.each_line do |line|
puts line.match(/^(.*)s w s*$/).captures.first
end
Вывод:
Chicago
New York
West Virginia