Как мне сопоставить каждое слово в строке, кроме последнего слова?

#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. Сначала вы разделяете строку на список, содержащий каждое слово.

  2. Затем выберите подсписок, содержащий все элементы, кроме последнего.

  3. Наконец, вы возвращаетесь от списка к строке.

Комментарии:

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