2013-02-18 13 views
6

Jaki jest najskuteczniejszy sposób zwrócenia tablicy pozycji znaku początku linii w ciągu znaków?Pozycje Newline

text =<<_ 
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 
culpa qui officia deserunt mollit anim id est laborum. 
_ 

Oczekiwany:

find_newlines(text) # => [0, 80, 155, 233, 313, 393] 

zamieścić własne odpowiedzi. Chciałbym zaakceptować najszybszy sposób jako zaakceptowaną odpowiedź.


Benchmark wynik tutaj zostanie zaktualizowany, gdy nowe odpowiedzi dodaje

require "fruity" 

compare do 
    padde1 {find_newlines_padde1(text)} 
    digitalross1 {find_newlines_digitalross1(text)} 
    sawa1 {find_newlines1(text)} 
    sawa2 {find_newlines2(text)} 
end 

# Running each test 512 times. Test will take about 1 second. 
# digitalross1 is faster than sawa2 by 5x ± 0.1 
# sawa2 is faster than sawa1 by 21.999999999999996% ± 1.0% 
# sawa1 is faster than padde1 by 4.0000000000000036% ± 1.0% 
+0

Twoje oczekiwane wyniki wydają się nieprawidłowe. Pierwszą wartością będzie * zawsze * 0, więc nic nie mierzy. Co gorsza, ostatnia wartość ** 393 ** nie jest w rzeczywistości ostatnią nową linią. Jest to znak nowej linii po * sunt in *, a nie nowej linii po * est laborum *. – DigitalRoss

+1

Ale +1 mimo to. Uwielbiam format konkursu. – DigitalRoss

+2

To samo, czuje się trochę jak golf z kodem, ale dla szybkości :) –

Odpowiedz

3
def find_newlines text 
    s = 0 
    [0] + text.to_a[0..-2].map { |e| s += e.size } 
end 

Jak zaznaczono, należy text.each_line.to_a do 1,9. Wywołanie each_line działa również w wersji 1.8.7, ale jest o 20% wolniejsze niż wywoływanie tylko to_a.

+0

To nie zadziała na Ruby 1.9+ ("String" nie zawiera już "Enumerable"), ale 'text.each_line.to_a ...' robi. – matt

+0

Jest to najszybszy poza Padde jednym z C. – sawa

0
def find_newlines_sawa1 s 
    a = [] 
    s.scan(/^/){a.push($~.offset(0)[0])} 
    a 
end 

find_newlines_sawa1(text) # => [0, 80, 155, 233, 313, 393] 

def find_newlines_sawa2 s 
    a = [0] 
    s.split(/^/).each{|s| a.push(a.last + s.length)} 
    a.pop 
    a 
end 

find_newlines_sawa2(text) # => [0, 80, 155, 233, 313, 393] 
+0

Gdzie jest benchmark? :) –

+0

Miałem zamiar to zrobić, gdy pojawi się więcej odpowiedzi. Nie ma sensu, aby każda odpowiedź miała własny test porównawczy na innym komputerze. – sawa

+0

@SergioTulentsev Czy możesz spróbować? – sawa

2

Podobny do swojej odpowiedzi:

def find_newlines_padde1 text 
    text.enum_for(:scan, /^/).map do 
    $~.begin(0) 
    end 
end 

Nadal można zdobyć wydajność z rubyinline:

require "inline" 
module Kernel 
    inline :C do |builder| 
    builder.add_compile_flags '-std=c99' 
    builder.c %q{ 
     static VALUE find_newlines_padde2(VALUE str) { 
     char newline = '\n'; 
     char* s = RSTRING_PTR(str); 
     VALUE res = rb_ary_new(); 
     str = StringValue(str); 
     rb_ary_push(res, LONG2FIX(0)); 
     for (long pos=0; pos<RSTRING_LEN(str)-1; pos++) { 
      if (s[pos] == newline) { 
      rb_ary_push(res, LONG2FIX(pos+1)); 
      } 
     } 
     return res; 
     } 
    } 
    end 
end 

Zauważ, że sztucznie kończę pracę z pos<RSTRING_LEN(str)-1, aby uzyskać taki sam wynik, o jaki prosiłeś. Jeśli chcesz, możesz to zmienić na pos<RSTRING_LEN(str), więc ostatnia pusta linia liczy się jako początek linii. Będziesz musiał zdecydować, który z nich działa dla ciebie.

Owocowy mówi padde2 is faster than sawa2 by 22x ± 0.1

+0

Dzięki za odpowiedź. – sawa

+0

Sprawdź moją aktualizację, nie mogę jej przyspieszyć, chyba że zacznę pisać inline assembler w wbudowanym kodzie ;-) –

+0

Ponieważ 'res [0]' będzie zawsze 0, być może powinno być usunięte lub opcjonalne. – DigitalRoss