Ruby coding challenges

I'm the founder of Taaalk and a Ruby on Rails engineer
Software Engineer at Stripe. Blog at robertheaton.com
Follow this Taaalk

3 followers

390 views

Joshua Summers
10:21, 09 Apr 22
In this Taaalk we will solve some Ruby coding challenges together. I'm going to use this list of advanced Ruby coding challenges on codecademy.
Challenge one is:
1. Re-inventing multiplication
In this challenge, write a function in Ruby that takes two parameters, both of them are numbers. The function should multiply the numbers and return the result, but there’s a catch. You’re not allowed to use the * symbol.
Let me know when you've had a think about it and then we will both reveal our solutions.
Robert Heaton
17:43, 09 Apr 22
Sounds good! What inputs are we allowing? Do they have to be integers or can they be any of the decimal types? Can they be negative?
Joshua Summers
23:35, 09 Apr 22
Anything that * works with traditionally.
Joshua Summers
10:57, 10 Apr 22
Let's also say you can return the answer in the format of your choice.
Joshua Summers
12:19, 10 Apr 22
Actually, scratch that. I think we should start with integers and gradually expand the solution. 
Robert Heaton
12:25, 10 Apr 22
OK! I have some solutions ready.
Joshua Summers
12:27, 10 Apr 22
def multiply(a, b)
  result = 0
  a.times { result += b}
  result
end
Your turn.
Robert Heaton
12:46, 10 Apr 22
I have one serious answer:
def multiply_ints_properly(x, y)
  # We want to use `x.times` to sum x copies of y, but if
  # x < 0 then this won't work. We therefore use the absolute
  # value of x and flip the sign if necessary.  
  ans_using_abs_x = x.abs.times.inject(0) {|acc, _| acc + y}
  x < 0 ? -ans_using_abs_x : ans_using_abs_x
end
Robert Heaton
12:47, 10 Apr 22
My toddler wouldn't go to sleep last night so I also have 4 sillier answers:
def multiply_hack1(x, y)
    # It's a mathematical identity that:
    #
    #   x * y == x / (1.0 / y)
    #
    # 1.0 / y is a `Float` so this solution is not fully
    # accurate for most inputs.
    #
    # We should technically look at what the types of x and
    # y were and return the answer as that type too.
    x / (1.0 / y)
  end

def multiply_hack2(x, y)
  # Another mathematical identity:
  #
  #   x * y == Math.exp(Math.log(x) + Math.log(y))
  #
  # Again, `Math.log` returns a `Float` which loses some
  # precision, so in practice this seems to only be
  # accurate to about 15 sig figs.
  # 
  # Also again, we should  look at what the types of x and
  # y were and return the answer as that type.

  # We need to handle 0 as a special case since `log(0)` is
  # undefined.
  return 0 if x == 0 || y == 0

  # If x and/or y are negative then we need to handle that
  # ourselves too, since `log(x)` is a complex number if x < 0. 
  #
  # We get around this by taking the log of the absolute
  # values, then making the answer negative if exactly one of
  # x and y are negative (XOR - the ^ operator).
  abs_ans = Math.exp(Math.log(x.abs) + Math.log(y.abs))
  (x < 0) ^ (y < 0) ? -abs_ans : abs_ans
end

def multiply_hack3(x, y)
  # 42 is the ASCII code for `*`, so this just calls `*`
  # while technically not useing the `*` symbol. This one
  # is the most correct of all the solution.
  x.send(42.chr, y)
end

def multiply_hack4(x, y)
  # Use a maths API
  require 'uri'
  require 'net/http'

  uri = URI("http://api.mathjs.org/v4/?expr=#{x}#{42.chr}#{y}")
  res = Net::HTTP.get_response(uri)
  if res.is_a?(Net::HTTPSuccess)
    res.body.to_f
  else
    raise "Failed to multiply #{x} and #{y}"
  end
end

# Tests
inputs = [
  [5, 9],
  [-3, -4.5],
  [900, 235],
  [32.2179, -87.11],
  [2893217361128, 0]
]

inputs.each do |inp|
  puts "#{inp[0]} * #{inp[1]} = #{inp[0] * inp[1]}"
  (1..4).each do |m|
    puts "multiply_hack#{m}: " + send("multiply_hack#{m}", *inp).to_s
  end
  puts ""
end

Joshua Summers
13:14, 10 Apr 22
I'm so ashamed I didn't write tests and put myself in a position to make it clear my method would fail with negative integers! Sloppy of me 🙈
I was just refactoring my code, experimenting with #inject / #reduce, and I noticed something odd. While this runs without any errors:
def multiply(a, b)
  a.times.inject(0) { |sum, _| sum + b }
end
When I tried to puts sum + b , I got undefined method '+' for nil:NilClass (NoMethodError), which surprised me. Is this something you would have expected?
Robert Heaton
13:57, 10 Apr 22
No shame!
Yep, that's because puts returns nil, so the accumulator will have the value nil on the second time through. Try ans = sum + b; puts ans; ans or something like that.
Follow this Taaalk

3 followers

390 views

Start your own Taaalk, leave your details or meet someone to Taaalk with.