2012-12-22 12 views
7

In dem Skript, das ich schreibe, möchte ich die Länge eines Fixnum in Ruby finden. Ich könnte <num>.to_s.length tun, aber gibt es irgendeine Möglichkeit, die Länge eines Fixnum direkt zu finden, ohne es in einen String umzuwandeln?Wie ermittle ich die Länge eines Fixnum in Ruby?

+0

Was die „Länge eines' Fixnum'“ist? In welcher Darstellung? –

+0

@ JörgWMittag: Wie viele Ziffern sind drin? – Orcris

Antwort

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

Man könnte es hinzufügen, so Fixnum:

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

puts 1234.num_digits # => 4 
+11

Jedes Mal, wenn ich etwas profiliere, bekomme ich eine Überraschung. In MRI 1.9.3 ist 'n.to_s.length' für jede Ganzzahl, die durch ein Fixnum repräsentiert wird, schneller. Ein _lot_ schneller: Auf meiner Box dauert 'n.to_s.length' je nach Länge der Zahl zwischen der dritten und der Hälfte der Zeit der Logarithmusmethode. Wenn die Zahl ein Bignum darstellen soll, beginnt die Logarithmusmethode zu gewinnen. Beide Methoden sind jedoch sehr schnell, etwa 0,6 Millisekunden (für die Logarithmus-Methode) und zwischen 0,2 und 0,3 Millisekunden (für die String-Methode). –

+0

@WayneConrad: Klingt wie 'Math.log10' muss eine ziemlich ineffiziente Implementierung haben. Ich habe gerade eine einfache Methode ausprobiert, die über eine Tabelle mit allen Potenzen von 10 läuft, die in 32/64 Bits passt, und einen '> =' Vergleich für jeden durchführt - es war eine Berührung schneller als 'Math.log10', aber immer noch langsamer als 'to_s'. Es könnte schneller gemacht werden, indem man eine binäre Suche in der gleichen Tabelle "abrollt", genau wie das Abwickeln einer Schleife (dann würde die Tabelle nicht mehr benötigt werden - die gleichen Zahlen würden in eine Reihe von Bedingungen fest codiert werden). –

+4

Aufpassen. Dies führt zu einem Fehler bei nicht positiven Zahlen. – sawa

0

Wenn Sie regex nicht verwenden möchten, können Sie diese Methode verwenden können:

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

Ein anderer Weg:

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

Obwohl die top-votierte Schleife ist schön, es ist nicht sehr Ruby und wird für große Zahlen langsam sein, die .to_s ist eine eingebaute Funktion und wird daher viel schneller sein. FAST universal eingebaute Funktionen werden viel schneller sein als konstruierte Schleifen oder Iteratoren.

5

Ruby 2.4 hat eine Integer#digits Methode, die ein Array zurückgibt, das die Ziffern enthält.

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

EDIT:

So behandeln negative Zahlen (dank @MatzFan), verwenden Sie den absoluten Wert. Integer#abs

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

..wenn es eine positive Ganzzahl ist, sonst 'Math :: DomainError'. So viel mehr Ruby – MatzFan

0

Nebenbei bemerkt für Rubin 2.4+

lief ich einige Benchmarks auf die verschiedenen Lösungen und Math.log10(x).to_i + 1 ist eigentlich viel schneller alsx.to_s.length. Die comment from @Wayne Conrad ist veraltet. Die new solution with digits.count schleppen weit hinter, vor allem bei größeren Zahlen:

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)