DEV Community

M Bellucci
M Bellucci

Posted on

Ruby Bit Manipulation

Some days ago as part of a recruiting process, I faced a code-challenge on Devskiller.
One of the challenges requires using an integer to represents the Groups that a User belongs to.
For example 101 means that the user belongs to Group0 and Group2 but not to Group1.

7 years ago I probably would pass this challenge but since this challenge is assessing the most underused feature of the language for a web-developer and it is time-constrained, I didn't pass :'( .

So if you want to succeed on this kind of challenges make sure you can unerstand this code:

# operators Description
# &   AND
# |   OR
# ^   XOR
# ~   NOT
# <<  left shift
# >>  right shift

# Calculating the binary version of the number (.to_s(2) doesn't work for negated values ~x)
def binary_string(number)
  31.downto(0).reduce('') {|result, index| result += number[index].to_s }
end
alias :bs :binary_string

# returns the value of the bit at the position index
# index 0 is the last bit
def get_bit(number, index)
  # since Ruby is awesome this also could be implemented as number[index]

  ith_one = 1 << index       # 0000000000100
  ith_bit = number & ith_one # 1101101101X01 & 0000000000100 => 0000000000X00
  ith_bit >> index           # 000000000000X
end

# puts a 1 in the index position of number starting from right to left
# doesn't change its parameter instead returns a new number
# index 0 is the less significant bit
def set_bit(number, index)
  ith_one = 1 << index # 00000000010000000
  number | ith_one
end

# puts a 0 in the index position of number starting on 0 from right to left
# index 0 is the less significant bit
def clear_bit(number, index)
  mask = ~(1 << index)
  number & mask
end

# param value must be 0 or 1
def update_bit(number, index, value)
  mask = ~(1 << index)
  (number & mask) | (value << i)
end

x = 5
zeroes = 0 # 000000000000000000000000000000
ones = ~0 # 111111111111111111111111111111

########################## XOR ##########################
p "#{ x ^ zeroes } == #{ x }"
p "#{ bs(x ^ ones) } == #{bs(~x)}" # using bs because to_s prints a negative binary
p "#{ x ^ x } == #{0}"

########################## OR ##########################
p "#{ x | zeroes } == #{ x }"
p "#{ x | ones } == #{ones}"
p "#{ x | x } == #{x}"

########################## AND ##########################
p "#{ x & zeroes } == #{ zeroes }"
p "#{ x & ones } == #{x}"
p "#{ x & x } == #{x}"

# Convert into array
0b01010101.to_s(2).split('')
# => ["1", "0", "1", "0", "1", "0", "1"]


################################## Sample Problem #################################################
# Insertion:
#   You are given two 32-bit numbers, N and M, and two bit positions, i and j.
#   Write a method to insert M into N such that M starts at bit j and ends at bit i.
#   You can assume that the bits j through i have enough space to fit all of M.
#
#   That is, if M = 10011, you can assume that there are at least 5 bits between j and i.
#   You would not, for example, have j = 3 and i = 2, because M could not fully fit between bit 3 and bit 2.
#
# EXAMPLE
# Input:
#   N = 10000000000, M = 10011, i = 2, j = 6
# Output:
#   N = 10001001100
###################################################################################################

def merge_bits_at(n, m, i, j)
  # m = M4 M3 M2 M1 M0
  # m1 = 0  0  M3 M2 M1 M0 0 0
  # n = N7 N6 N5 N4 N3 N2 N1 N0
  # n1 = N7 N6 0  0  0  0  N1 N0
  # n1 | m1 = N7 N6 M3  M2  M1  M0  N1 N0

  m1 = ( m << i )      # 0  0  M3 M2 M1 M0 0 0

  ones = ~0
  x1 = ones << i      # 1 1 1 1 1 1 0 0
  x2 = ~( ones << j ) # 0 0 1 1 1 1 1 1
  mask = x1 & x2      # 0 0 1 1 1 1 0 0
  mask = ~mask        # 1 1 0 0 0 0 1 1

  n1 = n & mask       # N7 N6 0  0  0  0  N1 N0

  m1 | n1
end


n, m, i, j = 0b10000000, 0b10011, 2, 6
puts "N = #{bs(n)}"
puts "M = #{bs(m)}"
puts "i = #{i}, j = #{j}"
puts 'Calling merge_bits_at(10000000, 10011, 2, 6)'
merged_bits = merge_bits_at(n, m, i, j)
puts "#{ bs(merged_bits) } should be 11001100"


#### Using string representation ####
( "101".to_i(2) & "110".to_i(2) ).to_s(2)

Enter fullscreen mode Exit fullscreen mode

Top comments (0)