2009-11-30 13 views
18

Próbuję zwrócić indeks do wszystkich wystąpień określonego znaku w ciągu za pomocą Ruby. Przykładowy ciąg znaków to "a#asg#sdfg#d##", a oczekiwany zwrot to [1,5,10,12,13] podczas wyszukiwania znaków #. Poniższy kod wykonuje zadanie, ale musi być prostszy sposób robienia tego?Indeks powrotu wszystkich wystąpień znaku w ciągu w rubi

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 

Odpowiedz

15
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] == "# '} powinno działać zbyt dobrze? nie ma potrzeby, 1 ...? –

+0

@SamJoseph W tym przypadku tak, dwa są synonimami. Wersja 2 argumentów '[x, y]' oznacza "podłańcuch długości' y' rozpoczynający się od 'x'", który jest taki sam jak '[x]', co oznacza "znak w' x' (również ciąg, ponieważ ruby ​​nie ma typu Char) ". – erich2k8

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

ETA: To działa poprzez stworzenie Enumerator który używa scan(/#/) jako każdej metody.

skanowanie daje każdy występ podanego wzorca (w tym przypadku /#/), a wewnątrz bloku można wywołać Regexp.last_match, aby uzyskać dostęp do obiektu MatchData dla danego dopasowania.

MatchData#begin(0) zwraca indeks, w którym zaczyna się mecz, a ponieważ użyliśmy mapy na moduł wyliczający, otrzymujemy tablicę tych indeksów z powrotem.

+1

Fajnie, ale nie jestem pewien jak to działa. – Gerhard

2

Oto długołańcuchowe metoda:

"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] 

wymaga 1.8.7+

+0

W 1.9 można wykonać '.each_char.with_index' (zamiast' each_char.each_with_index'). Brzmi lepiej w ten sposób, jak sądzę. – Telemachus

+0

rzeczywiście. –

12

Oto mniej fantazyjny sposób:

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

W teście szybkie łącze to było około 3,3x szybciej niż metoda find_all FM i około 2,5x szybciej niż metoda enum_for sepp2k.

+0

Te dane prędkości podano od 1.8.5. W wersji 1.9.1 jest to nadal najszybszy z dużym marginesem, ale find_all jest około 3 razy wolniejszy, a enum_for jest około 5 razy wolniejszy! –

+0

Moje szybkie przypuszczenie jest takie, że to 'Regexp.last_match.begin (0)' spowalnia metodę 'enum_for'. (To znaczy, mam nadzieję, że samo 'enum_for' nie stanowi problemu.) Tak czy inaczej, podoba mi się, że jest to zarówno proste, jak i czytelne. Mniej fantazji często jest lepiej. – Telemachus

+0

Jest to szybsze, ponieważ blok jest wykonywany dla każdego znaku w innych podejściach. Znalazłem podobne pytanie na http://stackoverflow.com/questions/6387428/why-is-counting-letters-faster-using-stringcount-than-using-stringchars-in-ruby/6475413#6475413 –

1

Innym rozwiązaniem pochodzą z FMC za odpowiedź:

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

Uwielbiam to, że Ruby nie ma tylko jeden sposób robienia czegoś!

Powiązane problemy