tags:

views:

166

answers:

5

I am trying to return the index's to all occurrences of a specific character in a string using Ruby. A example string is "a#asg#sdfg#d##" and the expected return is [1,5,10,12,13] when seaching for # characters. The following code does the job but there must be a simpler way of doing this?

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
+4  A: 
require 'enumerator' # Needed in 1.8.6 only
"1#3#a#".enum_for(:scan,/#/).map { Regexp.last_match.begin(0) }
#=> [1, 3, 5]

ETA: This works by creating an Enumerator that uses scan(/#/) as its each method.

scan yields each occurence of the specified pattern (in this case /#/) and inside the block you can call Regexp.last_match to access the MatchData object for the match.

MatchData#begin(0) returns the index where the match begins and since we used map on the enumerator, we get an array of those indices back.

sepp2k
Cool, however I am not sure how this works.
Gerhard
+3  A: 
s = "a#asg#sdfg#d##"
a = (0 .. s.length - 1).find_all { |i| s[i,1] == '#' }
FM
+1  A: 

here's a long method chain:

"a#asg#sdfg#d##".
    each_char.
    each_with_index.
    inject([]) {|indices, (char, idx)| indices << idx if char == "#"; indices}
# => [1, 5, 10, 12, 13]

requires 1.8.7+

glenn jackman
In 1.9 you can do `.each_char.with_index` (instead of `each_char.each_with_index`). It reads better that way, I think.
Telemachus
it does indeed.
glenn jackman
+2  A: 

Here's a less-fancy way:

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

In a quick speed test this was about 3.3x faster than FM's find_all method, and about 2.5x faster than sepp2k's enum_for method.

glenn mcdonald
Those speed figures were from 1.8.5. In 1.9.1 this is still fastest by a wide margin, but find_all is about 3x slower and enum_for is about 5x slower!
glenn mcdonald
My quick guess is that it's `Regexp.last_match.begin(0)` that's slowing down the `enum_for` method. (That is, I hope that `enum_for` itself is not the problem.) Either way, I like that this is both simple and readable. Less fancy is often more good.
Telemachus
A: 

annother solution derived from FM's answer:

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

I love that Ruby never has only one way of doing something!

Gerhard