2009-11-30 14 views
18

Sto tentando di restituire l'indice a tutte le occorrenze di un carattere specifico in una stringa utilizzando Ruby. Una stringa di esempio è "a#asg#sdfg#d##" e il rendimento previsto è [1,5,10,12,13] durante la ricerca di caratteri #. Il seguente codice fa il lavoro ma ci deve essere un modo più semplice per farlo?Indice di ritorno di tutte le occorrenze di un carattere in una stringa in rubino

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 

risposta

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] == '#'} dovrebbe funzionare anche a destra? non c'è bisogno del, 1 ...? –

+0

@SamJoseph In questo caso, sì, i due sono sinonimi. La versione a 2 argomenti di '[x, y]' significa "una sottostringa di lunghezza' y' che inizia a 'x'", che è la stessa di '[x]', che significa "carattere a' x' (anche a stringa perché ruby ​​non ha un tipo 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: Questo funziona creando un enumeratore che utilizza scan(/#/) come ogni metodo.

la scansione produce ogni occorrenza del modello specificato (in questo caso /#/) e all'interno del blocco è possibile chiamare Regexp.last_match per accedere all'oggetto MatchData per la corrispondenza.

MatchData#begin(0) restituisce l'indice in cui inizia la partita e dato che abbiamo usato mappa su dell'enumeratore, otteniamo una serie di tali indici indietro.

+1

Cool, tuttavia non sono sicuro di come funzioni. – Gerhard

2

Ecco una lunga catena metodo:

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

richiede 1.8.7+

+0

Nella 1.9 si può fare '.each_char.with_index' (invece di' each_char.each_with_index'). Si legge meglio così, penso. – Telemachus

+0

lo fa davvero. –

12

Ecco un modo meno-fantasia:

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

In un test di velocità rapida questo è stato circa 3,3 volte più veloce del metodo find_all di FM e circa 2,5 volte più veloce del metodo enum_for di sepp2k.

+0

Queste cifre di velocità erano 1.8.5. Nella 1.9.1 questo è ancora più veloce con un ampio margine, ma find_all è circa 3 volte più lento e enum_for è circa 5 volte più lento! –

+0

La mia ipotesi rapida è che è 'Regexp.last_match.begin (0)' che sta rallentando il metodo 'enum_for'. (Cioè, spero che 'enum_for' stesso non sia il problema.) Ad ogni modo, mi piace che sia sia semplice che leggibile. Meno fantasia è spesso più buona. – Telemachus

+0

Questo è più veloce perché un blocco viene eseguito per ogni carattere negli altri approcci. Mi sono imbattuto e ho risolto una domanda simile a http://stackoverflow.com/questions/6387428/why-is-counting-letters-faster-using-stringcount-than-using-stringchars-in-ruby/6475413#6475413 –

1

Un'altra soluzione derivata dalla risposta della FMC:

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

amo che Ruby non ha un solo modo di fare qualcosa!

Problemi correlati