Решение на Осма задача от Мартина Тонковска

Обратно към всички решения

Към профила на Мартина Тонковска

Резултати

  • 3 точки от тестове
  • 0 бонус точки
  • 3 точки общо
  • 20 успешни тест(а)
  • 20 неуспешни тест(а)

Код

require 'ostruct'
module Validate
ARITHMETIC_FUNCTIONS = { ADD: [2, Float::INFINITY, :+],
MULTIPLY: [2, Float::INFINITY, :*],
SUBTRACT: [2, 2, :-],
DIVIDE: [2, 2, :/],
MOD: [2, 2, :%] }
def validate_index(index)
if (index =~ /^[A-Z]+\d+$/).nil?
raise self.class::Error.new("Invalid cell index #{index}")
else
return true
end
end
def validate_function_exists(function, valid_functions)
# check if function name exists
unless valid_functions.include?(function.name)
raise self.class::Error.new("Unknown function '#{function.name}'")
end
end
def validate_function(function)
validate_function_exists(function, ARITHMETIC_FUNCTIONS.keys)
# check if number of arguments passed is correct
validate_function_parameters(function)
end
def validate_function_parameters(function)
error_class = self.class::Error
if ARITHMETIC_FUNCTIONS[function.name][1] != Float::INFINITY &&
function.parameters.length != ARITHMETIC_FUNCTIONS[function.name][0]
raise error_class.new(error_class.argument_mismatch_strict(function))
elsif function.parameters.length < ARITHMETIC_FUNCTIONS[function.name][0]
raise error_class.new(error_class.argument_mismatch_loose(function))
end
end
end
module Parser
def parse_number(number)
number == number.floor ? number.to_i.to_s : '%.2f' % number
end
def parse_sheet(sheet)
sheet = sheet.split("\n")
sheet = sheet.reject { |row| row =~ /^\s*$/ }.map(&:strip)
sheet.each do |row|
row = row.split(/\t|(?:\ {2,})/)
@sheet << row
end
@sheet
end
def convert_index(cell_index)
indexes = cell_index.scan(/[A-Z]+|\d+/)
row = 0
indexes[0].chars.each_with_index do |element, index|
row += (element.ord - 64) * (26**(indexes[0].length - index - 1)) - 1
end
indexes[1], indexes[0] = row, indexes.last.to_i - 1
indexes
end
def evaluate_function(function)
match = /^=([A-Z]+)\((.+)\)$/.match(function)
# check if function is syntactically correct
raise self.class::Error, ("Invalid expression '#{function}'") if match.nil?
parameters = match[2].split(',').map(&:strip)
function = OpenStruct.new(name: match[1].to_sym, parameters: parameters)
validate_function(function)
call_function(function)
end
def parse_row(row)
row.map do |cell|
cell.count("=") != 0 ? evaluate_expression(cell) : cell
end.join("\t")
end
end
class Spreadsheet
include Validate, Parser
def initialize(sheet)
if sheet.nil?
@sheet = []
else
@sheet = []
parse_sheet(sheet)
end
end
def empty?
@sheet.empty?
end
def cell_at(cell_index)
validate_index(cell_index)
cell_at = cell_index
cell_index = convert_index(cell_index)
if cell_index.first >= @sheet[0].size || cell_index.last >= @sheet.size
raise Error.new("Cell '#{cell_at}' does not exist")
else
return @sheet[cell_index.first][cell_index.last]
end
end
def [](cell_index)
evaluate_cell(cell_index)
end
def to_s
@sheet.map do |row|
parse_row(row)
end.join("\n")
end
private
def call_function(function)
parse_number(function.parameters.map do |cell|
evaluate_expression('=' + cell).to_f
end.reduce(Validate::ARITHMETIC_FUNCTIONS[function.name][2]))
end
def evaluate_cell(cell_index)
cell = cell_at(cell_index)
return evaluate_expression(cell) if cell.count("=") != 0
cell
end
def evaluate_expression(expression)
case expression
when /^=[\d\.]+$/ then return expression[1..-1]
when /^=[A-Z]+\d+$/ then return evaluate_cell(expression[1..-1])
else return evaluate_function(expression)
end
end
class Error < Exception
def initialize(message)
@message = message
end
def to_s
@message
end
def self.argument_mismatch_strict(function)
"Wrong number of arguments for '#{function.name}': " \
"expected #{Validate::ARITHMETIC_FUNCTIONS[function.name][0]}, " \
"got #{function.parameters.length}"
end
def self.argument_mismatch_loose(function)
"Wrong number of arguments for '#{function.name}': expected at least " \
"#{Validate::ARITHMETIC_FUNCTIONS[function.name][0]}, " \
"got #{function.parameters.length}"
end
end
end

Лог от изпълнението

FF..F.......F.FF..FFFFF.F..F..F.FFFFF..F

Failures:

  1) Spreadsheet#new can be called with no arguments or with a single string argument
     Failure/Error: Spreadsheet.new
     ArgumentError:
       wrong number of arguments (0 for 1)
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:87:in `initialize'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:4:in `new'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:4:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  2) Spreadsheet#new creates a blank sheet when no arguments are passed
     Failure/Error: expect(Spreadsheet.new).to be_empty
     ArgumentError:
       wrong number of arguments (0 for 1)
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:87:in `initialize'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:10:in `new'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:10:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  3) Spreadsheet#to_s returns blank tables as blank strings
     Failure/Error: expect(Spreadsheet.new.to_s).to eq ''
     ArgumentError:
       wrong number of arguments (0 for 1)
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:87:in `initialize'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:24:in `new'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:24:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  4) Spreadsheet#[] raises an exception for non-existant cells
     Failure/Error: expect { Spreadsheet.new()['A1'] }.to raise_error(Spreadsheet::Error, /Cell 'A1' does not exist/)
       expected Spreadsheet::Error with message matching /Cell 'A1' does not exist/, got #<ArgumentError: wrong number of arguments (0 for 1)> with backtrace:
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:87:in `initialize'
         # /tmp/d20160121-5693-1ru4ljl/spec.rb:75:in `new'
         # /tmp/d20160121-5693-1ru4ljl/spec.rb:75:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1ru4ljl/spec.rb:75:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:75:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  5) Spreadsheet#[] returns the value of existing cells for complex cell indexes
     Failure/Error: expect(sheet['AD1']).to eq 'b'
     Spreadsheet::Error:
       Cell 'AD1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:93:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  6) Spreadsheet#[] returns the calculated value of formulae cells
     Failure/Error: expect(sheet['B1']).to eq 'ADD(2, 2)'
     Spreadsheet::Error:
       Cell 'B1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:102:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  7) Spreadsheet#[] raises an exception for less than two arguments passed to ADD
     Failure/Error: expect { Spreadsheet.new('=ADD()')['A1'] }.to raise_error(
       expected Spreadsheet::Error with message matching /Wrong number of arguments for 'ADD': expected at least 2, got 0/, got #<Spreadsheet::Error: Invalid expression '=ADD()'> with backtrace:
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:70:in `evaluate_function'
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:139:in `evaluate_expression'
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:131:in `evaluate_cell'
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
         # /tmp/d20160121-5693-1ru4ljl/spec.rb:123:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1ru4ljl/spec.rb:123:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:123:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  8) Spreadsheet#[] adds numbers from cell references and as immediate arguments with ADD
     Failure/Error: expect(sheet['B1']).to eq('55')
     Spreadsheet::Error:
       Cell 'B1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:131:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  9) Spreadsheet#[] adds numbers only from cell references with ADD
     Failure/Error: expect(sheet['D1']).to eq('10')
     Spreadsheet::Error:
       Cell 'D1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:137:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  10) Spreadsheet#[] multiplies numbers with MULTIPLY
     Failure/Error: expect(sheet2['E1']).to eq('120')
     Spreadsheet::Error:
       Cell 'E1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:145:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  11) Spreadsheet#[] raises an exception for less than two arguments to MULTIPLY
     Failure/Error: expect { Spreadsheet.new('=MULTIPLY()')['A1'] }.to raise_error(
       expected Spreadsheet::Error with message matching /Wrong number of arguments for 'MULTIPLY': expected at least 2, got 0/, got #<Spreadsheet::Error: Invalid expression '=MULTIPLY()'> with backtrace:
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:70:in `evaluate_function'
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:139:in `evaluate_expression'
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:131:in `evaluate_cell'
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
         # /tmp/d20160121-5693-1ru4ljl/spec.rb:153:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1ru4ljl/spec.rb:153:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:153:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  12) Spreadsheet#[] subtracts numbers via cell references
     Failure/Error: expect(sheet['D1']).to eq('4')
     Spreadsheet::Error:
       Cell 'D1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:167:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  13) Spreadsheet#[] divides numbers via cell references
     Failure/Error: expect(sheet1['C1']).to eq('42')
     Spreadsheet::Error:
       Cell 'C1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:190:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  14) Spreadsheet#[] calculates the modulo of two numbers with MOD via cell references
     Failure/Error: expect(sheet1['C1']).to eq('4')
     Spreadsheet::Error:
       Cell 'C1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:214:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  15) Spreadsheet#[] adds floating point numbers with ADD
     Failure/Error: expect(Spreadsheet.new('10  =ADD(A1, 1.1)')['B1']).to eq '11.10'
     Spreadsheet::Error:
       Cell 'B1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:229:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  16) Spreadsheet#[] subtracts floating point numbers with SUBTRACT
     Failure/Error: expect(Spreadsheet.new('10  =SUBTRACT(A1, 1.1)')['B1']).to eq '8.90'
     Spreadsheet::Error:
       Cell 'B1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:234:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  17) Spreadsheet#[] multiplies floating point numbers with MULTIPLY
     Failure/Error: expect(Spreadsheet.new('10  =MULTIPLY(A1, 1.1)')['B1']).to eq '11'
     Spreadsheet::Error:
       Cell 'B1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:239:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  18) Spreadsheet#[] divides floating point numbers with DIVIDE
     Failure/Error: expect(Spreadsheet.new('10  =DIVIDE(A1, 4)')['B1']).to eq '2.50'
     Spreadsheet::Error:
       Cell 'B1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:244:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  19) Spreadsheet#[] evaluates deeply-nested cell references
     Failure/Error: expect(Spreadsheet.new('10  =ADD(5, A1)  3  =DIVIDE(B1, C1)  =MOD(D1, 4)')['E1']).to eq '1'
     Spreadsheet::Error:
       Cell 'E1' does not exist
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:105:in `cell_at'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:130:in `evaluate_cell'
     # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:250:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  20) Spreadsheet#[] raises an exception for invalid expressions
     Failure/Error: expect { Spreadsheet.new('=FOO  100')['A1'] }.to raise_error(
       expected Spreadsheet::Error with message matching /Invalid expression 'FOO'/, got #<Spreadsheet::Error: Invalid expression '=FOO'> with backtrace:
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:70:in `evaluate_function'
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:139:in `evaluate_expression'
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:131:in `evaluate_cell'
         # /tmp/d20160121-5693-1ru4ljl/solution.rb:112:in `[]'
         # /tmp/d20160121-5693-1ru4ljl/spec.rb:266:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1ru4ljl/spec.rb:266:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'
     # /tmp/d20160121-5693-1ru4ljl/spec.rb:266:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.03133 seconds
40 examples, 20 failures

Failed examples:

rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:3 # Spreadsheet#new can be called with no arguments or with a single string argument
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:9 # Spreadsheet#new creates a blank sheet when no arguments are passed
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:23 # Spreadsheet#to_s returns blank tables as blank strings
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:74 # Spreadsheet#[] raises an exception for non-existant cells
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:90 # Spreadsheet#[] returns the value of existing cells for complex cell indexes
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:98 # Spreadsheet#[] returns the calculated value of formulae cells
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:118 # Spreadsheet#[] raises an exception for less than two arguments passed to ADD
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:128 # Spreadsheet#[] adds numbers from cell references and as immediate arguments with ADD
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:134 # Spreadsheet#[] adds numbers only from cell references with ADD
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:140 # Spreadsheet#[] multiplies numbers with MULTIPLY
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:148 # Spreadsheet#[] raises an exception for less than two arguments to MULTIPLY
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:164 # Spreadsheet#[] subtracts numbers via cell references
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:186 # Spreadsheet#[] divides numbers via cell references
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:210 # Spreadsheet#[] calculates the modulo of two numbers with MOD via cell references
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:228 # Spreadsheet#[] adds floating point numbers with ADD
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:233 # Spreadsheet#[] subtracts floating point numbers with SUBTRACT
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:238 # Spreadsheet#[] multiplies floating point numbers with MULTIPLY
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:243 # Spreadsheet#[] divides floating point numbers with DIVIDE
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:249 # Spreadsheet#[] evaluates deeply-nested cell references
rspec /tmp/d20160121-5693-1ru4ljl/spec.rb:265 # Spreadsheet#[] raises an exception for invalid expressions

История (1 версия и 0 коментара)

Мартина обнови решението на 11.01.2016 00:11 (преди над 9 години)

+require 'ostruct'
+
+module Validate
+ ARITHMETIC_FUNCTIONS = { ADD: [2, Float::INFINITY, :+],
+ MULTIPLY: [2, Float::INFINITY, :*],
+ SUBTRACT: [2, 2, :-],
+ DIVIDE: [2, 2, :/],
+ MOD: [2, 2, :%] }
+
+ def validate_index(index)
+ if (index =~ /^[A-Z]+\d+$/).nil?
+ raise self.class::Error.new("Invalid cell index #{index}")
+ else
+ return true
+ end
+ end
+
+ def validate_function_exists(function, valid_functions)
+ # check if function name exists
+ unless valid_functions.include?(function.name)
+ raise self.class::Error.new("Unknown function '#{function.name}'")
+ end
+ end
+
+ def validate_function(function)
+ validate_function_exists(function, ARITHMETIC_FUNCTIONS.keys)
+ # check if number of arguments passed is correct
+ validate_function_parameters(function)
+ end
+
+ def validate_function_parameters(function)
+ error_class = self.class::Error
+ if ARITHMETIC_FUNCTIONS[function.name][1] != Float::INFINITY &&
+ function.parameters.length != ARITHMETIC_FUNCTIONS[function.name][0]
+ raise error_class.new(error_class.argument_mismatch_strict(function))
+ elsif function.parameters.length < ARITHMETIC_FUNCTIONS[function.name][0]
+ raise error_class.new(error_class.argument_mismatch_loose(function))
+ end
+ end
+end
+
+module Parser
+ def parse_number(number)
+ number == number.floor ? number.to_i.to_s : '%.2f' % number
+ end
+
+ def parse_sheet(sheet)
+ sheet = sheet.split("\n")
+ sheet = sheet.reject { |row| row =~ /^\s*$/ }.map(&:strip)
+ sheet.each do |row|
+ row = row.split(/\t|(?:\ {2,})/)
+ @sheet << row
+ end
+ @sheet
+ end
+
+ def convert_index(cell_index)
+ indexes = cell_index.scan(/[A-Z]+|\d+/)
+ row = 0
+ indexes[0].chars.each_with_index do |element, index|
+ row += (element.ord - 64) * (26**(indexes[0].length - index - 1)) - 1
+ end
+ indexes[1], indexes[0] = row, indexes.last.to_i - 1
+ indexes
+ end
+
+ def evaluate_function(function)
+ match = /^=([A-Z]+)\((.+)\)$/.match(function)
+ # check if function is syntactically correct
+ raise self.class::Error, ("Invalid expression '#{function}'") if match.nil?
+ parameters = match[2].split(',').map(&:strip)
+ function = OpenStruct.new(name: match[1].to_sym, parameters: parameters)
+ validate_function(function)
+ call_function(function)
+ end
+
+ def parse_row(row)
+ row.map do |cell|
+ cell.count("=") != 0 ? evaluate_expression(cell) : cell
+ end.join("\t")
+ end
+end
+
+class Spreadsheet
+ include Validate, Parser
+
+ def initialize(sheet)
+ if sheet.nil?
+ @sheet = []
+ else
+ @sheet = []
+ parse_sheet(sheet)
+ end
+ end
+
+ def empty?
+ @sheet.empty?
+ end
+
+ def cell_at(cell_index)
+ validate_index(cell_index)
+ cell_at = cell_index
+ cell_index = convert_index(cell_index)
+ if cell_index.first >= @sheet[0].size || cell_index.last >= @sheet.size
+ raise Error.new("Cell '#{cell_at}' does not exist")
+ else
+ return @sheet[cell_index.first][cell_index.last]
+ end
+ end
+
+ def [](cell_index)
+ evaluate_cell(cell_index)
+ end
+
+ def to_s
+ @sheet.map do |row|
+ parse_row(row)
+ end.join("\n")
+ end
+
+ private
+
+ def call_function(function)
+ parse_number(function.parameters.map do |cell|
+ evaluate_expression('=' + cell).to_f
+ end.reduce(Validate::ARITHMETIC_FUNCTIONS[function.name][2]))
+ end
+
+ def evaluate_cell(cell_index)
+ cell = cell_at(cell_index)
+ return evaluate_expression(cell) if cell.count("=") != 0
+ cell
+ end
+
+ def evaluate_expression(expression)
+ case expression
+ when /^=[\d\.]+$/ then return expression[1..-1]
+ when /^=[A-Z]+\d+$/ then return evaluate_cell(expression[1..-1])
+ else return evaluate_function(expression)
+ end
+ end
+
+ class Error < Exception
+ def initialize(message)
+ @message = message
+ end
+
+ def to_s
+ @message
+ end
+
+ def self.argument_mismatch_strict(function)
+ "Wrong number of arguments for '#{function.name}': " \
+ "expected #{Validate::ARITHMETIC_FUNCTIONS[function.name][0]}, " \
+ "got #{function.parameters.length}"
+ end
+
+ def self.argument_mismatch_loose(function)
+ "Wrong number of arguments for '#{function.name}': expected at least " \
+ "#{Validate::ARITHMETIC_FUNCTIONS[function.name][0]}, " \
+ "got #{function.parameters.length}"
+ end
+ end
+end