2012-12-22 32 views
7

W scenariuszu, który piszę, chcę znaleźć długość Fixnum w Ruby. Mogę zrobić <num>.to_s.length, ale czy istnieje sposób, aby bezpośrednio znaleźć długość Fixnum bez konwersji go na ciąg?Jak określić długość Fixnum w Ruby?

+0

Co to jest "długość" Fixnum "? W jakiej reprezentacji? –

+0

@ JörgWMittag: Ile jest cyfr. – Orcris

Odpowiedz

23
puts Math.log10(1234).to_i + 1 # => 4 

Można dodać go do Fixnum tak:

class Fixnum 
    def num_digits 
    Math.log10(self).to_i + 1 
    end 
end 

puts 1234.num_digits # => 4 
+11

Za każdym razem, gdy coś profiluję, dostaję niespodziankę. W MRI 1.9.3, 'n.to_s.length' jest szybsze dla dowolnej liczby całkowitej reprezentowanej przez Fixnum. A _lot_ szybciej: Na moim pudełku, 'n.to_s.length' zajmuje gdzieś od trzeciej do połowy czasu metody logarytmicznej, w zależności od długości liczby. Jeśli liczba musi być reprezentowana przez Bignum, to metoda logarytmiczna zaczyna wygrywać. Obie metody są jednak bardzo szybkie i trwają około 6 milisekund (dla metody logarytmicznej) oraz między 0,2 a 0,3 milisekund (dla metody łańcuchowej). –

+0

@WayneConrad: Brzmi jak 'Math.log10' musi mieć dość nieefektywną implementację. Po prostu wypróbowałem prostą metodę, która przechodzi przez tabelę wszystkich mocy 10, które pasują do bitów 32/64, i wykonuje porównanie "> =" dla każdego - było to dotknięcie szybsze niż "Math.log10", ale wciąż wolniej niż 'to_s'. Można go przyspieszyć przez "rozwijanie" binarnego wyszukiwania tej samej tabeli, tak jak rozwijanie pętli (wtedy tabela nie byłaby już potrzebna - te same liczby byłyby zakodowane na stałe w szeregu warunków). –

+4

Uważaj. Powoduje to błąd w przypadku liczb nie dodatnich. – sawa

0

Jeśli nie chcesz, aby użyć wyrażenia regularnego, można użyć tej metody:

def self.is_number(string_to_test) 
is_number = false 
# use to_f to handle float value and to_i for int 
string_to_compare = string_to_test.to_i.to_s 
string_to_compare_handle_end = string_to_test.to_i 

# string has to be the same 
if(string_to_compare == string_to_test) 
    is_number = true 
end 
# length for fixnum in ruby 
size = Math.log10(string_to_compare_handle_end).to_i + 1 
# size has to be the same 
if(size != string_to_test.length) 
    is_number = false 
end 
is_number 
end 
1

Innym sposobem:

def ndigits(n) 
    n=n.abs 
    (1..1.0/0).each { |i| return i if (n /= 10).zero? } 
end 

ndigits(1234) # => 4 
ndigits(0) # => 1 
ndigits(-123) # => 3 
4

Mimo że najlepiej oddana pętla jest ładny, nie jest bardzo Ruby i będzie wolny dla dużych liczb, .to_s jest wbudowaną funkcją i dlatego będzie znacznie szybszy. ALMOST uniwersalnie wbudowane funkcje będą znacznie szybsze niż konstruowane pętle lub iteratory.

5

Ruby 2.4 ma metodę Integer#digits, która zwraca tablicę zawierającą cyfry.

num = 123456 
num.digits 
# => [6, 5, 4, 3, 2, 1] 
num.digits.count 
# => 6 

Edycja:

obsługi numerów ujemne (dzięki @MatzFan) wykorzystanie wartości bezwzględnej. Integer#abs

-123456.abs.digits 
# => [6, 5, 4, 3, 2, 1] 
+0

..jeśli jest liczbą całkowitą dodatnią, w przeciwnym razie 'Math :: DomainError'. O wiele więcej Rubiego – MatzFan

0

Sidenote dla Ruby 2.4+

Pobiegłem jakieś punkty odniesienia na różnych rozwiązań, a Math.log10(x).to_i + 1 jest rzeczywiście dużo szybciej niż x.to_s.length. comment from @Wayne Conrad jest nieaktualny. Numer new solution with digits.count jest daleko w tyle, szczególnie w przypadku większych numerów:

with_10_digits = 2_040_240_420 

print Benchmark.measure { 1_000_000.times { Math.log10(with_10_digits).to_i + 1 } } 
# => 0.100000 0.000000 0.100000 ( 0.109846) 
print Benchmark.measure { 1_000_000.times { with_10_digits.to_s.length } } 
# => 0.360000 0.000000 0.360000 ( 0.362604) 
print Benchmark.measure { 1_000_000.times { with_10_digits.digits.count } } 
# => 0.690000 0.020000 0.710000 ( 0.717554) 

with_42_digits = 750_325_442_042_020_572_057_420_745_037_450_237_570_322 

print Benchmark.measure { 1_000_000.times { Math.log10(with_42_digits).to_i + 1 } } 
# => 0.140000 0.000000 0.140000 ( 0.142757) 
print Benchmark.measure { 1_000_000.times { with_42_digits.to_s.length } } 
# => 1.180000 0.000000 1.180000 ( 1.186603) 
print Benchmark.measure { 1_000_000.times { with_42_digits.digits.count } } 
# => 8.480000 0.040000 8.520000 ( 8.577174)