在Ruby中返回一个字符串中某个字符的所有出现索引。

25
我正在尝试使用 Ruby 返回字符串中特定字符的所有出现位置索引。例如,给定字符串为 "a#asg#sdfg#d##",期望返回结果为在搜索#字符时得到[1,5,10,12,13]。下面的代码可以实现此任务,但肯定还有更简单的方法吧?
def occurances (line)

  index = 0
  all_index = []

  line.each_byte do |x|
    if x == '#'[0] then
      all_index << index
    end
    index += 1
  end

  all_index
end
6个回答

28
s = "a#asg#sdfg#d##"
a = (0 ... s.length).find_all { |i| s[i,1] == '#' }

3
s = "a#asg#sdfg#d##"a = (0...s.length).find_all { |i| s[i] == '#' }这样也可以,不需要加上",1"。 - Sam Joseph
@SamJoseph 在这种情况下,是的,两者是同义词。[x, y] 的 2 个参数版本表示“从 x 开始长度为 y 的子字符串”,这与 [x] 相同,它表示“在 x 处的字符(也是一个字符串,因为 Ruby 没有 Char 类型)”。 - Eric Haynes

19
require 'enumerator' # Needed in 1.8.6 only
"1#3#a#".enum_for(:scan,/#/).map { Regexp.last_match.begin(0) }
#=> [1, 3, 5]

更新时间:通过创建一个使用scan(/#/)作为其each方法的枚举器来实现。

在这种情况下,scan会产生指定模式(在本例中为/#/)的每个匹配项,并且您可以在块内调用Regexp.last_match来访问匹配的MatchData对象。

MatchData#begin(0)返回匹配开始的索引,由于我们在枚举器上使用了map,因此我们会得到这些索引的数组。


17

以下是一个更简单的方式:

i = -1
all = []
while i = x.index('#',i+1)
  all << i
end
all

在一个快速的速度测试中,这个方法比FM's find_all方法快约3.3倍,比sepp2k的enum_for方法快约2.5倍。


这些速度数据来自1.8.5版本。在1.9.1版本中,它仍然是最快的,但find_all慢了约3倍,enum_for慢了约5倍! - glenn mcdonald
我的快速猜测是Regexp.last_match.begin(0)正在拖慢enum_for方法。(也就是说,我希望enum_for本身不是问题所在。)无论如何,我喜欢这既简单又易读的方法。简单常常比花哨更好。 - Telemachus
这是更快的,因为在其他方法中,每个字符都会执行一个块。我在https://dev59.com/0ljUa4cB1Zd3GeqPQlpF#6475413上遇到并解决了一个类似的问题。 - Andrew Grimm

3

这里有一个长的方法链:

"a#asg#sdfg#d##".
  each_char.
  each_with_index.
  inject([]) do |indices, (char, idx)|
    indices << idx if char == "#"
    indices
  end

# => [1, 5, 10, 12, 13]

需要1.8.7或更高版本


在1.9中,你可以使用.each_char.with_index(而不是each_char.each_with_index)。我认为这样读起来更好。 - Telemachus

1

另一个解决方案源自FMc的答案:

s = "a#asg#sdfg#d##"
q = []
s.length.times {|i| q << i if s[i,1] == '#'}

我喜欢 Ruby 永远不止一种做事的方式!


1
这里提供了一个处理大字符串的解决方案。我正在对4.5MB的文本字符串进行文本查找,而其他解决方案则会停滞不前。这个方法利用了Ruby的.split功能,与字符串比较相比非常高效。
def indices_of_matches(str, target)
      cuts = (str + (target.hash.to_s.gsub(target,''))).split(target)[0..-2]
      indicies = []
      loc = 0
      cuts.each do |cut|
        loc = loc + cut.size
        indicies << loc
        loc = loc + target.size
      end
      return indicies
    end

基本上是利用.split方法的功能,然后使用分离的部分和搜索字符串的长度来计算位置。我已经从使用各种方法需要30秒的时间,转变为在极大的字符串上瞬间完成。
我相信有更好的方法,但是:
(str + (target.hash.to_s.gsub(target,'')))

在字符串末尾添加一些内容,以防目标位于末尾(以及分割方式),但必须确保“随机”添加的内容不包含目标本身。
indices_of_matches("a#asg#sdfg#d##","#")
=> [1, 5, 10, 12, 13]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接