Coding Challenge #12: Draw the multiplication

Coding Challenge #12

Hello! Welcome back to another challenge. I am still looking for people to help me out and make challenges as well! So if you’re interested, help me out!

As usual, same requirements:

  • Has to be written in Lua 5.1 (the version Roblox uses)
  • The program has to be compatible with Roblox Studio, meaning you can’t use features vanilla Lua has but Luau has disabled
  • You can’t use httpservice to cheat the challenge

Given two positive integers, write a function a draw(a, b) that draws the way you would write the multiplication of a and b by hand. (This function will print the thing instead of returning it)

Goodluck! You can find a list of all the challenges here or a github version here.

Answer!
local function draw(a, b)
    local n = math.clamp(#tostring(a*b), 2, math.huge) --this is in case b was less than 3, because the --- line always has to be 3-long
    --n would just be the length of the final result
    print(string.rep(" ", n-#tostring(a))..a) --string.rep(" ", n-#tostring(a)) this is calculating how many spaces to put before a
    print("x"..string.rep(" ", n-#(" "..b))..b) --"x"..string.rep(" ", n-#(" "..b)) calculates how many spaces before b, including the x sign
    print(string.rep("-", n)) --the --- line
    local sum = 0 --this is the end result
    for i = #tostring(b), 1, -1 do 
      if not(string.sub(b, i, i) == "0") then --if the current digit is 0 we just discard the current line
            local add = a*string.sub(b,i,i)*10^(#tostring(b)-i) --this is the current number, a*string.sub(b,i,i) is multiplying the current digit with a, 10^(#tostring(b)-i) decides how many zeros it has
            sum = sum + add
            print(string.rep(" ", n-#tostring(add))..add) 
      end
    end
    print(string.rep("-", n))
    print(string.rep(" ", n-#(tostring(sum)))..sum)
end
10 Likes

This looks pretty hard, I can’t wait to try it out. Hopefully I’ll solve it, but I rarely do, not smart enough.

1 Like

I can’t wait to try this out! Thanks so much!

1 Like

You didn’t specifiy whether the integers were positive or negative, and your answer errors for me when I pass a negative number.

Here is my approach (which uses a lot less string manipulation):

local function CharacterAmount(Num)
	local CharAmount = math.log(math.abs(Num),10)
	if CharAmount%1 ~= 0 then
		CharAmount = math.ceil(CharAmount)
	else
		CharAmount += 1
	end
	if Num < 0 then
		CharAmount += 1
	end
	return math.max(CharAmount,1)
end
local function draw(a,b)
	local Result = a*b
	local CharAmountB = CharacterAmount(b)
	local CharLimit = math.max(CharacterAmount(a),CharAmountB,CharacterAmount(Result))+2
	local CharFormat = string.format("%%%ii",CharLimit)
	print(string.format(CharFormat,a))
	print(string.format(string.format("x%%%ii",CharLimit-1),b))
	local Seperator = string.rep("-",CharLimit)
	print(Seperator)
	if a ~= 0 and b ~= 0 then
		for i=1,b > 0 and CharAmountB or CharAmountB-1 do
			local Pow = 10^(i-1)
			local Char = math.fmod(math.floor(b/Pow),10)
			if Char ~= 0 then
				print(string.format(CharFormat,a*Char*Pow))
			end
		end
		print(Seperator)
	end
	print(string.format(CharFormat,Result))
end
4 Likes

Here’s my approach:

code
local finalFormat, partialFormat = [[%s%d
×%s%d
%s%s
%s
%d]], [[

%s%s]]

return function(multiplicand, multiplier)
	local product = multiplicand * multiplier
	local sign = math.sign(product)
	
	local absMultiplicand, absMultiplier = math.abs(multiplicand), math.abs(multiplier)
	
	local multiplicandString, multiplierString, productString = tostring(multiplicand), tostring(multiplier), tostring(product)
	local absMultiplierString = tostring(absMultiplier)
	
	local plicandLength, plierLength, absPlierLength, productLength = #multiplicandString, #multiplierString, #absMultiplierString, #productString -- not typing that out would be nice lol
	
	local partialProduct = [[]]
	
	for i = absPlierLength, 1, -1 do
		local digit = absMultiplierString:sub(i, i)
		
		local temp = digit * absMultiplier * sign .. ([[0]]):rep(absPlierLength - i)
		
		if 0 ~= tonumber(temp) then
			partialProduct ..= (partialFormat):format(([[ ]]):rep(productLength - #tostring(temp)), temp)
		end
	end
	
	local divider = ([[-]]):rep(productLength)
	
	local final = finalFormat:format(([[ ]]):rep(productLength - plicandLength), multiplicand, ([[ ]]):rep(productLength - (plierLength + 1)), multiplier, divider, partialProduct, divider, product)
	
	return final
end

Most of my time was spent trying to right align numbers and get the right number of dashes to have everything line up. I think I’ve got it, though. Not the prettiest code for the job, certainly, but it works.

Edit: Should work with negatives now, thanks Halalaluya.

5 Likes
local function draw(a,b)
      local myA = tostring(a)
      local myB = tostring(b)
      if a < b then
         myA = tostring(b)
         myB = tostring(a)
      end
      print("         "..myA)
      print("x".."         "..myB)
      print("____________")
      for i = #tostring(b), 1, -1 do
         print("         "..tostring(string.sub(tostring(b), i, i + 1)) * a)
      end
end

(Its a bit offset but it works.)

2 Likes

I guess I’ll share my approach, too :stuck_out_tongue:

My approach
local function draw(a, b)
  local products = {}
  for i = 1, tostring(b):len() do
    local multiplier = math.floor(b % (10 ^ i) / (10 ^ (i - 1))) * (10 ^ (i - 1))
    if multiplier * a ~= 0 then
      table.insert(products, a * multiplier)
    end
  end
  local lineLength, sum = math.max(tostring(a):len(), tostring(b):len() + 2), 0
  for _, product in ipairs(products) do
    sum = sum + product
    lineLength = math.max(tostring(product):len(), lineLength)
  end
  lineLength = math.max(lineLength, tostring(sum):len())
  -- Now there's just printing everything
  local function format(str) -- New print function
    return string.rep(" ", lineLength - str:len()) .. str
  end
  print(format(tostring(a)) .. "\nx" .. string.rep(" ", lineLength - tostring(b):len() - 1) .. tostring(b))
  print(string.rep('-', lineLength))
  for _, product in ipairs(products) do
    print(format(tostring(product)))
  end
  print(string.rep('-', lineLength))
  print(format(tostring(sum)))
end
2 Likes

Here is my solution:

function idk(top, bottom)
	local maxLen = string.len(tostring(top * bottom))
	print(string.rep('_', maxLen - string.len(top)) .. tostring(top))
	print('X' .. string.rep('_', (maxLen - string.len(bottom)) - 1) .. tostring(bottom))
--	print(maxLen)
--	print(string.len('X' .. string.rep('_', (maxLen - string.len(bottom)) - 2) .. tostring(bottom)))
	print(string.rep('..', maxLen))
	local botStr = tostring(bottom)
	local products = {}
	local numZeros = 0
	for x = string.len(botStr), 1, -1 do
		local digit = tonumber(string.sub(botStr, x,x))
		local multiplySlotNum = tonumber((tostring(digit) .. string.rep('0', numZeros)))
		table.insert(products, (multiplySlotNum * top))
		local space = maxLen - string.len((multiplySlotNum * top))
		if (multiplySlotNum * top) ~= 0 then
			print(string.rep('_', space) .. (multiplySlotNum * top))
		else
			
		end
		numZeros += 1
	end
	print(string.rep('_', maxLen))
	local sum = 0
	for x = 1, #products do
		sum += products[x]
	end
	print(sum .. '\n' .. ' New Stuff \n')
end

idk(23958233, 5830)
idk(78, 14086)

I’m not sure if I cheated by just multiplying the current digit added with 0 by the top, but my printed result looks the same as yours. I’m surprised I managed to do this, but really happy as well.

3 Likes
function draw_math (x, z)
	-- Variable Setup; Convert args to strings
	local product, line_len, line
	x, z, product = tostring(x), tostring(z), tostring(x*z)
	
	-- Length function to align text on right; const line string
	line_len = math.max(2+#z, #product)
	line = ('-'):rep(line_len)
	local function right(str) return ( (' '):rep(line_len)..str):sub(-line_len) end
	
	-- Output
	print(right(x), '\n'.. right('x '..z), '\n'.. line)
	for i=#z, 1, -1 do
		local z_i = string.byte(z, i)-48
		if math.clamp(z_i, 1, 9)~=z_i then
			continue
		end
		print(  right( (z_i*x)..(' '):rep(#z-(i)) )  )
	end
	print(line,'\n'..right(product) ..'\n')
end

Yeah I don’t remember adding a 0 to the right after every smaller multiplication. I just left a blank space.

1 Like

I only used number manipulation in my attempt, using math.log(x, 10) to get the number’s length and voodoo magic string.format to right-justify everything:

code
local function draw(a, b)
	
	-- finding the numbers
	local addends = {}
	local bDigits = math.log(b, 10)
	for i = 0, bDigits do
		local digit = math.floor(b / 10^i) % 10
		if digit ~= 0 then
			table.insert(addends, a * digit * 10^i)
		end
	end
	local product = a * b
	
	-- formatting
	local width = math.floor(math.log(addends[#addends], 10)) + 3
	local formatNumber = string.format("%%%dd\n", width)
	local formatOp = string.format(" %%%dd\n", width - 2)
	local separator = string.rep("-", width) .. "\n"
	
	local formatString = string.format("%s*%s%s%s+%s%s%s", 
		string.format(formatNumber, a),
		string.format(formatOp, b),
		separator, 
		string.rep(formatNumber, #addends - 1), formatOp,
		separator,
		string.format(formatNumber, product):sub(1, -2)
	) 
	
	return string.format(formatString, table.unpack(addends))
end

I have no idea where to put :sub(1, -2) in terms of organization…

It looks pretty clean in my opinion :open_mouth:

Edit: oops, didn’t mean to reply to you goldenstein

Burns my corneas to look at, but hey, I’d like to see someone compactify this challenge further than I did (without minifying the code).

function draw(a, b)
	local aStr, bStr = '' .. a, '' .. b
	local bar = ('-'):rep(math.max(#('' .. a*b), math.max(#aStr, #bStr) + 2))
	local count, sign = -1, math.sign(b)
	print(('% d\nx% d\n%s\n%s%s\n% d')
		:gsub(' ', #bar):format(a, b, bar, bStr:reverse():gsub('%d', function(num)
			count = count + 1
			local result = a * num * sign * 10^count
			return result > 0 and ('%' .. #bar .. 'd'):format(result) .. '\n' or ''  
		end), bar, a*b):gsub('x ', 'x'))
end
2 Likes

Here’s my solution 1 year later

function rjust(s,l,p)
	return string.rep(p or " ",l-#s)..s
end

function draw(n,m)
	local barlength = #tostring(n*m)+1
	
	local a = rjust(tostring(n),barlength)
	local b = "x"..rjust(tostring(m),barlength-1)
	
	print(a)
	print(b)
	print(string.rep("-",barlength))
	
	for i = 1,#tostring(m) do
		local x = tostring(n*tonumber(string.sub(tostring(m),#tostring(m)-(i-1),#tostring(m)-(i-1))))
		if tonumber(x) ~= 0 then
			print(rjust(x,barlength-#string.rep("0",i-1))..string.rep("0",i-1))
		end
	end
	print(string.rep("-",barlength))
	print(rjust(tostring(n*m),barlength))
end

draw(5,4)