module Math24

Constants

OPERATORS
VERSION

Public Class Methods

check(problem, solution) click to toggle source
# File lib/math24.rb, line 61
def self.check(problem, solution)
  raise ArgumentError unless /\A\d{4}\z/.match problem.join
  raise ArgumentError unless solution.is_a? String
  raise ArgumentError unless /\A(\(*(\d{1}[()\s]*[*+-\/]+[()\s]*){3}\d{1}\)*)\z/.match(solution)

  problem.count {|i| solution.include?(i.to_s) } == 4 &&
    instance_eval(solution) == 24
end
generate_problem() click to toggle source
# File lib/math24.rb, line 5
def self.generate_problem
  loop do
    problem = Array.new(4) { rand(1...9) }
    return problem if solve(problem)
  end
end
solve(problem) click to toggle source
# File lib/math24.rb, line 12
def self.solve(problem)
  raise ArgumentError unless /\A\d{4}\z/.match problem.join

  op_permutation = OPERATORS.repeated_permutation(3)
  # Unique permutations removes duplicates when a number appears multiple
  # times in the problem
  num_permutation = problem.permutation(4).to_a.uniq

  num_permutation.each do |numbers|
    op_permutation.each do |operators|
      begin
        proposed_solution = "((#{numbers[0].to_f} #{operators[0]} #{numbers[1]}.to_f) #{operators[1]} #{numbers[2]}.to_f) #{operators[2]} #{numbers[3].to_f}"
        forward_result = instance_eval(proposed_solution)
      rescue ZeroDivisionError
        forward_result = 0
      end

      begin
        proposed_solution = "(#{numbers[0].to_f} #{operators[0]} #{numbers[1].to_f}) #{operators[1]} (#{numbers[2].to_f} #{operators[2]} #{numbers[3].to_f})"
        alternate_result = instance_eval(proposed_solution)
      rescue ZeroDivisionError
        alternate_result = 0
      end

      begin
        proposed_solution = "#{numbers[0].to_f} #{operators[0]} (#{numbers[1].to_f} #{operators[1]} (#{numbers[2].to_f} #{operators[2]} #{numbers[3].to_f}))"
        reverse_result = instance_eval(proposed_solution)
      rescue ZeroDivisionError
        reverse_result = 0
      end

      if forward_result == 24 && integer_division_only?(proposed_solution)
        if (operators.include?("+") || operators.include?("-")) && (operators.include?("*") || operators.include?("/"))
          #Might need parentheses for order of operations
          return "((#{numbers[0]} #{operators[0]} #{numbers[1]}) #{operators[1]} #{numbers[2]}) #{operators[2]} #{numbers[3]}"
        else
          return "#{numbers[0]} #{operators[0]} #{numbers[1]} #{operators[1]} #{numbers[2]} #{operators[2]} #{numbers[3]}"
        end
      elsif alternate_result == 24 && integer_division_only?(proposed_solution)
        return "(#{numbers[0]} #{operators[0]} #{numbers[1]}) #{operators[1]} (#{numbers[2]} #{operators[2]} #{numbers[3]})"
      elsif reverse_result == 24 && integer_division_only?(proposed_solution)
        return "#{numbers[0]} #{operators[0]} (#{numbers[1]} #{operators[1]} (#{numbers[2]} #{operators[2]} #{numbers[3]}))"
      end
    end
  end

  return false
end

Private Class Methods

integer_division_only?(proposed_solution) click to toggle source
# File lib/math24.rb, line 72
def self.integer_division_only? proposed_solution
  # float and integer division yield same result
  begin
    instance_eval(proposed_solution) == instance_eval(proposed_solution.delete('.0'))
  rescue ZeroDivisionError
    false
  end
end