Найти все индексы подстроки в строке

Я хочу быть в состоянии найти индекс всех вхождений подстроки в большую строку, используя Ruby. Например: все "в" в "Эйнштейне"

str = "Einstein"
str.index("in") #returns only 1
str.scan("in")  #returns ["in","in"]
#desired output would be [1, 6]

4 ответа

Решение

Стандартный хак это:

"Einstein".enum_for(:scan, /(?=in)/).map { Regexp.last_match.offset(0).first }
#=> [1, 6]
def indices_of_matches(str, target)
  sz = target.size
  (0..str.size-sz).select { |i| str[i,sz] == target }
end

indices_of_matches('Einstein', 'in')
  #=> [1, 6]
indices_of_matches('nnnn', 'nn')
  #=> [0, 1, 2]

Второй пример отражает предположение, которое я сделал относительно обработки перекрывающихся строк. Если перекрывающиеся строки не должны рассматриваться (т.е. [0, 2] является желаемым возвращаемым значением во втором примере), этот ответ явно неуместен.

Это более подробное решение, которое дает преимущество не полагаться на глобальную ценность:

def indices(string, regex)
  position = 0
  Enumerator.new do |yielder|
    while match = regex.match(string, position)
      yielder << match.begin(0)
      position = match.end(0)
    end
  end
end

p indices("Einstein", /in/).to_a
# [1, 6]

Выводит Enumeratorтак что вы также можете использовать его лениво или просто взять n Первые показатели.

Кроме того, если вам может потребоваться больше информации, чем просто индексы, вы можете вернуть Enumerator из MatchData и извлечь индексы:

def matches(string, regex)
  position = 0
  Enumerator.new do |yielder|
    while match = regex.match(string, position)
      yielder << match
      position = match.end(0)
    end
  end
end

p matches("Einstein", /in/).map{ |match| match.begin(0) }
# [1, 6]

Чтобы получить поведение, описанное @Cary, вы можете заменить последнюю строку в блоке на position = match.begin(0) + 1,

#Recursive Function

          def indexes string, sub_string, start=0
      index = string[start..-1].index(sub_string)
      return [] unless index
      [index+start] + indexes(string,sub_string,index+start+1)
    end

# Для лучшего использования я бы открыл String класс

        class String

    def indexes sub_string,start=0
      index = self[start..-1].index(sub_string)
      return [] unless index
      [index+start] + indexes(sub_string,index+start+1)
    end

  end

Таким образом мы можем вызвать таким образом: "Einstein".indexes("in") #=> [1, 6]

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