Skip to content

Instantly share code, notes, and snippets.

@nikhgupta
Last active December 5, 2019 05:32
Show Gist options
  • Save nikhgupta/23113f0f6dba6dd8381a4a4382a74f55 to your computer and use it in GitHub Desktop.
Save nikhgupta/23113f0f6dba6dd8381a4a4382a74f55 to your computer and use it in GitHub Desktop.
Math Problem Generator - Sample
#!/usr/bin/env ruby
#
# Problem Statement
# =================
# 1a) Two fractions with different denominators (Minuend and Subtrahend). Choose
# from the following pairs of denominators: 2 and 3, 2 and 4, 2 and 5, 3 and 6,
# 3 and 9, 4 and 8, 5 and 10, 6 and 12. Difference is greater than 0. (i.e.
# 1/2-2/5)
# =================
require 'optparse'
DENOM_PAIRS = [[2,3], [2,4], [2,5], [3,6], [3,9], [4,8], [5,10], [6,12]]
module ProblemGenerator
# Utility module containing simple maths functions/expressions
module Utility
def gcd(a, b)
b == 0 ? a : gcd(b, a.modulo(b))
end
def reduce_fraction(a, b)
gcd = gcd(a, b)
[a, b].map{|i| i / gcd}
end
def irreducible?(a, b)
gcd(a, b) == 1
end
end
# Problem generator for the current problem statement.
#
# Generates two fractions (optionally, irreducible or mixed fractions) in order,
# such that their difference is positive.
class Fractions1A
include Utility
attr_accessor :reduce, :mixed
# Initialize problem generator.
#
# If `reduce` is set to true, only irreducible fractions will be generated.
# Irreducible fractions are fractions where numerator and denominator are
# co-prime.
#
# If `mixed` is set to a numeric value greater than 1, mixed fractions will
# be generated such that the rational number is less than `mixed` value.
# Setting `mixed` to `1` (default) will generate proper fractions.
#
def initialize(reduce: true, mixed: false)
self.mixed = mixed
self.reduce = reduce
end
def mixed=(mixed)
@mixed = mixed && mixed.to_i.positive? ? mixed : 1
end
# Generate a problem statement.
#
# If `display` is true, problem statement will be printed. We might want to
# generate several problem statements at once, wherein setting `display` to
# false will help.
def generate(display: true)
pick_denominators
generate_minuend
generate_subtrahend
puts self if display
[[@n_min, @d_min], [@n_sub, @d_sub], @res]
end
def inspect
"#{@n_min}/#{@d_min} - #{@n_sub}/#{@d_sub} = #{@res[0]}/#{@res[1]}"
end
alias to_s inspect
protected
# Pick denominators, at random, from the given pairs.
def pick_denominators
@d_min, @d_sub = DENOM_PAIRS.sample
end
# Generate numerator for the minuend. We pick a numerator at random,
# such that either the minuend is irreducible, or we are not concerned with
# irreducible fractions (in which case, any numerator picked would work).
#
# Since, 2 consecutive numbers are always co-prime, numerator will be set
# via this method, always.
def generate_minuend
loop do
@n_min = 1 + rand(max_numerator_for(@d_min))
break if !reduce || irreducible?(@n_min, @d_min)
end
end
# Generate the numerator for subtrahend. We shuffle available numerators,
# and stop at the first match which results in a valid value.
def generate_subtrahend
val = []
@n_sub = (1..max_numerator_for(@d_sub)).to_a.shuffle.detect do |i|
next if @reduce && !irreducible?(i, @d_sub)
val = reduce_fraction(@n_min * @d_sub - @d_min * i, @d_min * @d_sub)
valid_resulting_fraction?(*val)
end
@res = val
end
private
def max_numerator_for(n)
@mixed * n - 1
end
def valid_resulting_fraction?(a, b)
return false unless a.positive? && b.positive?
return false if @mixed == 1 && a >= b
return false if @reduce && !irreducible?(a, b)
return false if gcd(a, b) == b
true
end
end
end
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.on("-r", "--[no-]reduce", "Generate irreducible fractions only? [Default: True]") do |r|
options[:reduce] = r
end
opts.on("-mVAL", "--mixed=VAL", "Max value of generated fractions (Default: 1)") do |m|
options[:mixed] = m.to_i
end
opts.on("-nNUM", "--count=NUM", "Number of problem statements to generate (Default: 10)") do |n|
options[:count] = n.to_i
end
end.parse!
count = options.delete(:count)
count = 10 unless count.to_i.positive?
generator = ProblemGenerator::Fractions1A.new(options)
count.times.map { generator.generate }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment