2009-11-30 15 views
18

Ich versuche, den Index auf alle Vorkommen eines bestimmten Zeichens in einer Zeichenfolge mithilfe von Ruby zurückzugeben. Eine Beispielzeichenfolge ist "a#asg#sdfg#d##" und die erwartete Rückgabe ist [1,5,10,12,13] bei der Suche nach # Zeichen. Der folgende Code erledigt die Aufgabe, aber es muss einen einfacheren Weg geben, dies zu tun?Rückgabe-Index aller Vorkommen eines Zeichens in einer Zeichenfolge in Ruby

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 

Antwort

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] == '#'} sollte auch richtig funktionieren? keine Notwendigkeit für die, 1 ...? –

+0

@SamJoseph In diesem Fall, ja, die beiden sind auch. Die 2-Argument-Version von '[x, y]' bedeutet "ein Teilstring der Länge' y' beginnend bei 'x'", was dasselbe ist wie '[x]', was "Zeichen an' x' bedeutet (auch a String, weil Ruby keinen Char-Typ hat) ". – 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: Dies funktioniert durch einen Enumerator schaffen, die scan(/#/) als jede Methode verwendet.

Scan ergibt jedes Vorkommen des angegebenen Musters (in diesem Fall /#/) und innerhalb des Blocks können Sie Regexp.last_match aufrufen, um auf das MatchData-Objekt für die Übereinstimmung zuzugreifen.

MatchData#begin(0) gibt den Index zurück, wo die Übereinstimmung beginnt, und da wir map für den Enumerator verwendet haben, erhalten wir ein Array dieser Indizes zurück.

+1

Cool, aber ich bin mir nicht sicher, wie das funktioniert. – Gerhard

2

Hier ist eine lange Kette Methode:

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

erfordert 1.8.7+

+0

In 1.9 können Sie '.each_char.with_index' (anstelle von' each_char.each_with_index') tun. Es liest sich besser so, denke ich. – Telemachus

+0

ist es tatsächlich. –

12

Hier ist eine weniger Phantasie Art:

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

In einer schnellen Geschwindigkeit Test war etwa 3.3x schneller als die Methode find_all von FM und etwa 2.5x schneller als die Methode enum_for von sepp2k.

+0

Diese Geschwindigkeitswerte waren von 1.8.5. In 1.9.1 ist dies mit weitem Abstand am schnellsten, aber find_all ist etwa 3x langsamer und enum_for ist etwa 5x langsamer! –

+0

Meine schnelle Vermutung ist, dass es 'Regexp.last_match.begin (0)' ist, das die 'enum_for' Methode verlangsamt. (Das heißt, ich hoffe, dass 'enum_for' selbst nicht das Problem ist.) Wie auch immer, ich mag, dass dies sowohl einfach als auch lesbar ist. Weniger Phantasie ist oft mehr gut. – Telemachus

+0

Dies ist schneller, weil für jedes Zeichen in den anderen Ansätzen ein Block ausgeführt wird. Ich stieß auf und löste eine ähnliche Frage unter http://stackoverflow.com/questions/6387428/why-is-counting-letters-faster-using-stringcount-than-using-stringchars-in-ruby/6475413#6475413 –

1

Eine weitere Lösung aus FMc Antwort abgeleitet:

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

Ich liebe, dass Ruby nie nur eine Möglichkeit hat, etwas zu tun!

Verwandte Themen