tags:

views:

170

answers:

5

Chinese people do not like numbers with a digit 4 in it. I am going to implement a membership program with membership numbers not including the digit 4, say:

number = 3
number.next.has4?
=> true

how the has4? method can be done (efficiently)?

** EDIT

Thanks for the answers, I performed simple benchmarking as a reference:

    class Fixnum
      def has4a?
        String(self).index('4') != nil
      end
    end

    class Fixnum
      def has4b?
        self.to_s[/4/]
      end
    end

    number = 3

    puts Time.now

    n = 0
    while n < 1000000
       number.next.has4a?
       n += 1
    end

    puts Time.now

    n = 0
    while n < 1000000
       number.next.has4b?
       n += 1
    end

    puts Time.now

the result on my PC shows index is faster than regex:

> ruby has4.rb
Tue May 11 18:36:04 +0800 2010
Tue May 11 18:36:05 +0800 2010
Tue May 11 18:36:11 +0800 2010

Edited below to include all 4 solutions, and make it easy to see duration of each:

class Fixnum
  def has4a?
    String(self).index('4') != nil
  end
end

class Fixnum
  def has4b?
    self.to_s[/4/]
  end
end

class Fixnum
  def has4c?
    temp = self
    while temp > 0
        if (temp % 10) == 4
            return true 
        end
        temp /= 10
    end
    false 
  end
end

class Fixnum
  def digits
    d, m = divmod(10)
    d > 0 ? d.digits + [m] : [m]
  end

  def has4d?
    self.digits.member?(4)
  end
end

before_A = Time.now

n = 0
has4 = 0
no4 = 0
while n < 5000000
   has4 += 1 if n.has4a? 
   no4  += 1 if !n.has4a?
   n    += 1
end

after_A = Time.now

puts after_A, has4, no4
puts "A duration: " + (after_A - before_A).to_s

before_B = Time.now

n = 0
has4 = 0
no4 = 0
while n < 5000000
   has4 += 1 if n.has4b? 
   no4  += 1 if !n.has4b?
   n    += 1
end

after_B = Time.now

puts after_B, has4, no4
puts "B duration: " + (after_B - before_B).to_s

before_C = Time.now

n = 0
has4 = 0
no4 = 0
while n < 5000000
   has4 += 1 if n.has4c? 
   no4  += 1 if !n.has4c?
   n    += 1
end

after_C = Time.now

puts after_C, has4, no4
puts "C duration: " + (after_C - before_C).to_s

before_D = Time.now

n = 0
has4 = 0
no4 = 0
while n < 5000000
   has4 += 1 if n.has4d? 
   no4  += 1 if !n.has4d?
   n    += 1
end

after_D = Time.now

puts after_D, has4, no4
puts "D duration: " + (after_D - before_D).to_s

result (ruby 1.8.7 (2009-06-12 patchlevel 174) [i486-linux] on Karmic). Feel free to post data from other machines.

Tue May 11 16:25:38 -0400 2010
2874236
2125764
A duration: 35.375095
Tue May 11 16:26:19 -0400 2010
2874236
2125764
B duration: 40.659878
Tue May 11 16:27:38 -0400 2010
2874236
2125764
C duration: 79.12419
Tue May 11 16:31:28 -0400 2010
2874236
2125764
D duration: 229.573483

sorry for my previous typo and thanks Matthew Flaschen for fixing it. here's my benchmark:

    >ruby has4.rb
    Wed May 12 09:14:25 +0800 2010
    2874236
    2125764
    A duration: 18.186685
    Wed May 12 09:15:06 +0800 2010
    2874236
    2125764
    B duration: 40.388816
    Wed May 12 09:15:38 +0800 2010
    2874236
    2125764
    C duration: 32.639162
    Wed May 12 09:18:08 +0800 2010
    2874236
    2125764
    D duration: 150.024529

    >ruby -v
    ruby 1.8.7 (2010-01-10 patchlevel 249) [i386-mingw32]
+3  A: 

something like

def has4?
  self.to_s[/4/]
end

?

Staelen
converting a number to a string, and generating an automat with a regex is wonderfully efficient! (irony)
Pedro Morte Rolo
The `self.` can be left out, if you wish.
Wayne Conrad
+4  A: 
class Fixnum
  def has4?
    String(self).index('4') != nil
  end
end
Matthew Flaschen
It is ridiculously inefficient to use string manipulation for a numeric problem.
Pedro Morte Rolo
@Pedro, The efficiency of the human is paramount. Only when the code is proven to be not fast enough should the machine's needs take precedence.
Wayne Conrad
@Pedro, My version is fastest among those submitted on the OP's benchmark (after fixing it to call a different method every while loop, not `a` every time). This is not at all surprising as both index and converting to string are both very common and therefore heavily optimized. Next time, benchmark before you start making performance claims.
Matthew Flaschen
It's actually not all that uncommon that converting to a string is more efficient, especially for problems like this where we're looking for a specific digit. After the highly optimized string conversion, checking for a substring is much faster than performing division.
Chuck
+6  A: 

If you want to do this mathematically without converting the number to a string, the following algorithm will work:

while num > 0
    if (num % 10) == 4
        return true
    num = num / 10
return false 
R Samuel Klatchko
this looks like python to me ;-)
ohho
+1 most efficient solution. I felt extreme radiation burns in my eyes when I read the string solutions. http://stackoverflow.com/questions/2349378/new-programming-jargon-you-coined/2444303#2444303
wilhelmtell
This is the way it should be done.
Pedro Morte Rolo
@Wilhelm, You may be surprised to learn that the string solution executes in about half the time. I tested 1,000,000 random integers between 0 and 999,999; this took 6.6. seconds to ran. The string solution (`to_s[/4/]`) took 2.9.
Wayne Conrad
@WilhelmTell, this is the *least* efficient solution, except for Mladen's which I think was tongue in cheek.
Matthew Flaschen
@Wayne @Matthew ah. then there's more to the underlying mechanisms of Ruby than meet the eye. Either Ruby is extraordinarily smart about strings or (and) Ruby is extraordinarily dumb about integers. But I should have definitely been more cautious with my statements and assumptions.
wilhelmtell
@Wilhelm, it's definitely smart about strings. They seem to be almost entirely [implemented in C](http://ruby-doc.org/doxygen/1.8.4/string_8c-source.html). And my code only directly uses three Ruby methods. In contrast, the above algorithm uses a long series of short Ruby methods (operators), which probably causes much of the overhead. With a JIT like [Ludicrous](http://rubystuff.org/ludicrous/) the results might be different, since it might be able to automatically compile the Ruby into native code.
Matthew Flaschen
A: 

If you dislike strings, but like recursion:

class Fixnum
  def digits
    d, m = divmod(10)
    d > 0 ? d.digits + [m] : [m]
  end
end

12093.digits
#=> [1, 2, 0, 9, 3]
1.digits
#=> [1]
115.digits.member?(4)
#=> false
145.digits.member?(4)
#=> true

:)

Mladen Jablanović
+1  A: 

Here's a different solution: Use a simple one-up counter for your internal ids. Then, when you want to show the user their id#, render it in base 9, swapping all 4s for 9s.

user_visible_id = internal_id.to_s(9).gsub('4','9').to_i

Then, when processing their information, you can get their internal id back just as easily:

internal_id = user_visible_id.to_s.gsub('9', '4').to_i(9)

This way generating internal ids is easy (you don't have to loop through generating and checking them until you get one without a 4). If you wanted, you could wrap the oneup counter in a module, so that the rest of your application uses the user_visible_id, which will cut down on the confusion:

module IDGen
  @counter = 0
  def self.next
    @counter += 1
    @counter.to_s(9).gsub('4','9').to_i
  end
  def self.reset
    @counter = 0
  end
end

#...
User.new( IDGen.next )
rampion
I like this, except I would have the user-visible id always be treated as a string. So it would be `user_visible_id = internal_id.to_s(9).gsub('4','9')` and `internal_id = user_visible_id.gsub('9', '4').to_i(9)`
Matthew Flaschen