Есть ли ruby методы для выбора строки между другими строками? - PullRequest
2 голосов
/ 14 апреля 2020

Я начинаю программировать и ищу программу для извлечения всех слов, содержащихся между двумя словами в тексте (чтобы сохранить их в переменной)

Например, со словами «START» и «STOP»: «START 1 2 3 STOP 5 6 START 7 8 STOP 9 10»

Я хотел бы сохранить в переменных: 1 2 3 7 8

Я начал чтобы сделать это с Ruby, как вы можете видеть в приведенном ниже коде, моя текущая идея заключалась в том, чтобы преобразовать строку «global» в массив, а затем пронумеровать позиции string1 и string2; затем создайте массив 'string1' со значениями исходного массива # string1 + 1,… string2 -1. К сожалению, он работает только один раз, потому что функция .index работает только в первый раз ... есть ли лучший способ сделать это?

Заранее благодарен за помощь

text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"

start= text.split(' ')

a = start.index('start')
b = start.index('stop')

puts a
puts b
puts c = start[a+1,b-a-1].join(" ")

# returns 
#1
#5
#2 3 4 ```





Ответы [ 4 ]

2 голосов
/ 15 апреля 2020

Вы можете начать с scan -метода и регулярного выражения:

text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"
res1 = text.scan(/start\s*(.*?)\s*stop/) #[["2 3 4"], ["9 10"]]
res2 = res1.flatten #["2 3 4", "9 10"]

или без промежуточных переменных:

res = text.scan(/start(.*?)stop/).flatten #["2 3 4", "9 10"]

Объяснение:

См. https://apidock.com/ruby/String/scan о способе сканирования.

Регулярное выражение /start\s*(.*?)\s*stop/ представляет собой комбинацию

  1. start
  2. \s* : любой пробел
  3. (.*?):

    1. ( и ) ответственны за запоминание содержимого.
    2. . означает любой символ, * означает повторение (ноль или более символов), ? ограничивает результат самой короткой возможностью (подробности см. ниже)
  4. \s*: любой пробел

  5. stop

Результатом является массив с попаданиями регулярного выражения. Регулярное выражение может содержать различные части для обнаружения (несколько () -пар). Так что это массив массивов. В нашем случае каждый внутренний массив имеет один элемент, поэтому вы можете использовать flatten для получения «плоского» массива.

Если вы не используете ? в регулярном выражении, вы найдете 2 3 4 stop 6 7 start 9 10 вместо коротких деталей.

1 голос
/ 15 апреля 2020

Опция использования массива : в качестве отправной точки я мог бы предложить использовать Enumerable # slice_before после String # split

Учитывая вашу команду и стоп-слова:

command = "START 1 2 3 STOP 5 6 START 7 8 STOP 9 10"

start = 'START'
stop = 'STOP'

Вы можете использовать его примерно так:

grouped_cmd = command.split.slice_before { |e| [start, stop].include? e } # .to_a
#=> [["START", "1", "2", "3"], ["STOP", "5", "6"], ["START", "7", "8"], ["STOP", "9", "10"]]

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

grouped_cmd.select { |first, *rest| first == start }
#=> [["START", "1", "2", "3"], ["START", "7", "8"]]

Или

grouped_cmd.each_with_object([]) { |(first, *rest), ary| ary << rest if first == start }
#=> [["1", "2", "3"], ["7", "8"]]

Или даже

grouped_cmd.each_slice(2).map { |(start, *stt), (stop, *stp)| { start.downcase.to_sym => stt, stop.downcase.to_sym => stp } }
#=> [{:start=>["1", "2", "3"], :stop=>["5", "6"]}, {:start=>["7", "8"], :stop=>["9", "10"]}]

И т. Д.

1 голос
/ 15 апреля 2020

Однострочная цепочка методов

Вот подход, основанный на String # scan :

text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"
text.scan(/\bstart\s+(.*?)\s+stop\b/i).flat_map { _1.flat_map &:split }
#=> ["2", "3", "4", "9", "10"]

Идея заключается в следующем:

  1. Извлечь все строковые сегменты, заключенные в скобки между нечувствительными к регистру ключевыми словами start и stop.

    text.scan /\bstart\s+(.*?)\s+stop\b/i
    #=> [["2 3 4"], ["9 10"]]
    
  2. Извлечь слова, разделенные пробелами, между ключевые слова.

    [["2 3 4"], ["9 10"]].flat_map { _1.flat_map &:split }
    #=> ["2", "3", "4", "9", "10"]
    

Предостережения

Известные предостережения для подхода, изложенного выше, включают:

  • String # scan создает вложенные массивы, а повторные вызовы Enumerable # flat_map , используемые для их обработки, менее элегантны, чем я мог бы предпочесть.
  • \b - утверждение нулевой ширины, поэтому ищите границы слов могут привести к тому, что #scan включит начальные и конечные пробелы в результаты, которые затем необходимо обработать с помощью String # strip или String # split .
  • Substituting \s+ для \b обрабатывает некоторые крайние случаи при создании других.
  • Он ничего не делает для защиты от несбалансированных пар, например, "start 0 start 2 3 4 stop 6 stop".

Для простых случаев использования сканирование String # с настроенным регулярным выражением, вероятно, все, что вам нужно. Чем более разнообразны и непредсказуемы ваши структуры ввода и данных, тем больше крайних случаев потребуется для обработки ваших подпрограмм.

1 голос
/ 15 апреля 2020

Вы не получаете точно ошибку, codereview может быть лучше спросить. Но так как вы новичок в сообществе, вот регулярное выражение с обходными утверждениями , которое делает работу:

text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"
text.scan(/start ((?:(?!start).)*?) stop/).join(' ')
# => "2 3 4 9 10"

Кстати, отличное место для проверки ваших регулярных выражений в Ruby is https://rubular.com/

Надеюсь, вы найдете это полезным.

...