Coding Challenge: Converting Integers to Binary String Representations in Ruby

5 minute read

Lately I've been trying to challenge myself by taking a stab at some common coding questions asked in software engineering job interviews. After years of working on apps you would think that this kind of thing comes easy, but software development in the modern age comes with a heavy side of Googling and lookup, so challenging your brain in this way to implement an algorithm or process from just its definition is good practice to keep you sharp.

I had come across a practice coding question which required me to compare integers and binary strings, and I realized that I had forgotten how to convert an integer to binary. Looking it up, the algoritm is pretty simple:

To convert integer to binary, start with the integer in question and divide it by 2 keeping notice of the quotient and the remainder. Continue dividing the quotient by 2 until you get a quotient of zero. Then just write out the remainders in the reverse order.
Angular in Depth

Here's how I implemented that in ruby.

Basically, we need to keep dividing by 2 and record the remainder, so this sounds to me like a while loop, or even a recursive function. I'll implement both here, and at the end, I'll show you an even easier method to use with built-in ruby methods. We will only deal with integers >= 0, and output a string representation of the integer written in binary. For example, an input of 9 would output "1001", and an input of 1337 would output "10100111001".

Starting with a simple test case to prove our results are correct:

def convert_to_binary(integer)
  return integer.to_s
end

integer = 1337
binary = convert_to_binary(integer)
puts "CORRECT: #{binary == '10100111001'}\n#{integer} -> #{binary}"

Running that of course is going to fail:

CORRECT: false
1337 -> 1337

So let's get to work implementing the while method. We want to take the input n and divide it by 2, but how do we do that while "keeping notice of the quotient and the remainder"? We can get the quotient by simply running a standard divide by the integer 2, as ruby will always round down. If we divide by a float 2.0, ruby would provide a float return and we could see if the portion to the right of the decimal was > 0 or not, but I think there is a better way using the mod operator: %. This mod operator only returns the remainder as an integer, not as a fraction. Because we are dividing by 2, the remainder will always be either 1 or 0, and thus the mod will always return either 1 or 0, already conveniently in base2, or binary representation.

def convert_to_binary(integer)
  binary = []
  while integer > 0
    binary << integer % 2
    integer /= 2
  end
  binary.reverse.join
end

Line by line, we first initialize an empty array to hold the binary digits. We do a while loop over integer while it is sill larger than 0, and on each iteration we add the remainder of a mod operation to the empty binary array, then divide our integer by 2 for the next iteration. After we have hit 0, the binary array will be holding all of our digits, but in the wrong order, and as integers. So we reverse the array, then join it, which will automatically convert each array value to a string and concatenate them.

Running this through the test gives:

CORRECT: true
1337 -> 10100111001

Ok so this works, is easy to understand, and is relatively short. Let's try to make it recursive. Whatever is in the while loop should be able to be pulled out and recursed upon. The base case would be when we have an integer of 0 or 1, we can simply return that integer. Because we can combine the elements in the correct order as part of our recursive step, we don't need an array, and will call to_s on the base case and the remainder to always return a correct binary string representation.

def convert_to_binary_recursive(integer)
  if integer <= 1
    integer.to_s
  else
    convert_to_binary_recursive(integer / 2) + (integer % 2).to_s
  end
end

Running this through the test also gives correct results:

CORRECT: true
1337 -> 10100111001

Let's expand our testing a little bit by checking all numbers 0 through n, and testing them out. How can we test for correctness without manually typing out each value's string representation? Here is where I show you the much easier built-in method for converting that already exists in ruby. Are you ready for it?

integer.to_s(2)

A ruby Integer has the ability to change its base through the to_s method. To convert an integer to binary, being base 2, you simply call .to_s(2) to get the binary string representation. Knowing this, we can write a test suite to check the correctness of our method much more exhaustively, with over 1 million checks.

n = 2**20

%i(convert_to_binary convert_to_binary_recursive).each do |method|
  failed = []
  t1 = Time.now
  (0..n).each do |i|
    binary = self.send(method, i)
    correct = (binary == i.to_s(2))
    failed << i if !correct
  end
  t2=Time.now
  puts "\n#{method}"
  puts " - CORRECT: #{failed.empty?}"
  puts " - FAILED: #{failed}" if failed.any?
  puts " - #{t2-t1}sec"
end
convert_to_binary
- CORRECT: false
- FAILED: [0]
- 6.815202sec

convert_to_binary_recursive
- CORRECT: true
- 3.98154sec

Running this, we spot an error! Turns out the while loop method fails for 0. Let's add a simple check for that edge case:

def convert_to_binary(integer)
  return integer.to_s if integer <= 1

  binary=[]
  while integer > 0
    binary << integer % 2
    integer /= 2
  end
  binary.reverse.join
end

Running the fix, we are all good now.

  convert_to_binary
  - CORRECT: true
  - 7.444135sec

  convert_to_binary_recursive
  - CORRECT: true
  - 4.553685sec
  

So, the recursive method is about twice as fast as the iterative one, but how does they compare to the native method? Let's add a third method convert_to_binary_native and run the tests again.

def convert_to_binary_native(integer)
  integer.to_s(2)
end
convert_to_binary
- CORRECT: true
- 7.444135sec

convert_to_binary_recursive
- CORRECT: true
- 4.553685sec

convert_to_binary_native
- CORRECT: true
- 0.710849sec

Ruby's implementation is faster by an order of magnitude. Their implementation of to_s on a Fixnum bubbles down a couple of lower level methods until it hits a core C function in the numeric.c source. It's been awhile since I've worked in C, but it appears that there is a base case when the number is 0, then a do/while loop which the current digit is shifted and modded by the base. This method is essentially what we have in our convert_to_binary method as defined above, but is obviously much faster as it is written in pure C.

Was this page helpful for you? Buy me a slice of 🍕 to say thanks!

Comments