Решение на Осма задача от Алекс Николов

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

Към профила на Алекс Николов

Резултати

  • 4 точки от тестове
  • 0 бонус точки
  • 4 точки общо
  • 27 успешни тест(а)
  • 13 неуспешни тест(а)

Код

class Spreadsheet
attr_accessor :table
def initialize(table = nil)
@table = Array.new
@table = Parser.parse_table(table) unless /\A\s*\Z/.match table
end
def empty?
@table.empty?
end
def cell_at(call_index)
unless Parser.correct_cell_index?(call_index)
raise Spreadsheet::Error, "Invalid cell index '#{call_index}'"
end
cell = @table[Parser.row(call_index) - 1][Parser.column(call_index) - 1]
rescue NoMethodError => e
raise Spreadsheet::Error, "Cell '#{call_index}' does not exist"
end
def [](call_index)
raw_cell = cell_at(call_index)
if raw_cell != nil and raw_cell[0] == '='
Error.new.unknown_function_error(raw_cell)
Error.new.invalid_expression_error(raw_cell)
Error.new.argument_number_error(raw_cell)
end
evaluate_cell_data(raw_cell)
end
def to_s
evaluated_table = @table.map do |row|
row.map { |cell| evaluate_cell_data(cell) }
end
evaluated_table.map { |row| row.join("\t") }.join("\n")
end
private
def evaluate_cell_data(raw_cell)
case raw_cell
when /\A=#{Parser::NUMBER}\Z/ then Parser.evaluate_number(raw_cell)
when /\A=#{Parser::CELL}\Z/ then evaluate_cell(raw_cell)
when /\A#{Parser::FORMULA}\Z/ then evaluate_formula(raw_cell)
else raw_cell
end
end
def evaluate_formula(raw_cell)
match, formula = Parser::FORMULA.match(raw_cell), /=(.*)\(/.match(raw_cell)
arguments = Parser.extract_formula_arguments(match)
arguments.map! { |argument| evaluate_cell_data(argument).to_f }
accumulated = arguments.reduce do |memo, argument|
Parser::FORMULA_TO_METHOD[formula[1]].to_proc.call(memo, argument)
end
accumulated % 1 == 0 ? accumulated.to_i.to_s : '%.2f' % accumulated
end
def evaluate_cell(raw_cell)
reference_data = cell_at(raw_cell[1..-1]).dup
if /\A#{Parser::NUMBER}\Z/.match reference_data
reference_data.insert(0, '=')
end
evaluate_cell_data(reference_data)
end
class Error < Exception
def unknown_function_error(formula)
function_name = /=(.*)\(/.match formula
if Parser::FORMULA_TO_METHOD[function_name[1]].nil?
raise Spreadsheet::Error, "Unknown function '#{function_name[1]}'"
end
end
def invalid_expression_error(formula)
expression = formula[1..-1]
number = Parser::NUMBER
cell = Parser::CELL
at_least_one = "(((#{number}|#{cell})(\s*,\s*))*(#{number}|#{cell}))"
unless /\A[A-Z]+\(#{at_least_one}\)\Z/.match expression
raise Spreadsheet::Error, "Invalid expression '#{expression}'"
end
end
def argument_number_error(formula)
function_name = /=(.*)\(/.match formula
argument_size = (/\((.*)\)/.match formula).to_s.split(',').size
case function_name[1]
when 'ADD', 'MULTIPLY' then argument_size_error('<', argument_size)
when 'SUBSTRACT', 'MOD', 'DIVIDE'
argument_size_error('!=', argument_size)
end
end
def argument_size_error(operation, argument_size)
message_start = "Wrong number of arguments for 'FOO': "
error_condition = operation.to_sym.to_proc.call(argument_size, 2)
case operation
when '<' then message_end = "expected at least 2, got #{argument_size}"
when '!=' then message_end = "expected 2, got #{argument_size}"
end
raise Spreadsheet::Error, message_start + message_end if error_condition
end
end
class Parser
CELL = "([A-Z]+[1-9]+[0-9]*)"
NUMBER = "(0|-?0\.[0-9]*|-?[1-9][0-9]*\.?[0-9]*)"
MULTIPLE_ARGUMENTS = "(((#{NUMBER}|#{CELL})(\s*,\s*))+(#{NUMBER}|#{CELL}))"
TWO_ARGUMENTS = "((#{NUMBER}|#{CELL})\s*,\s*(#{NUMBER}|#{CELL}))"
ADD = /\A=ADD\(#{MULTIPLE_ARGUMENTS}\)\Z/
MULTIPLY = /\A=MULTIPLY\(#{MULTIPLE_ARGUMENTS}\)\Z/
SUBSTRACT = /\A=SUBSTRACT\(#{TWO_ARGUMENTS}\)\Z/
DIVIDE = /\A=DIVIDE\(#{TWO_ARGUMENTS}\)\Z/
MOD = /\A=MOD\(#{TWO_ARGUMENTS}\)\Z/
FORMULA = /(#{ADD}|#{MULTIPLY}|#{SUBSTRACT}|#{DIVIDE}|#{MOD})/
FORMULA_TO_METHOD = { 'ADD' => '+'.to_sym, 'MULTIPLY' => '*'.to_sym,
'SUBSTRACT' => '-'.to_sym, 'DIVIDE' => '/'.to_sym,
'MOD' => '%'.to_sym,
}
def self.parse_table(table)
split_by_spaces = table.lstrip.rstrip.split(/( {2,}|\t|\n)/)
first_new_line = split_by_spaces.find_index("\n")
width = first_new_line ? (first_new_line + 1) / 2 : split_by_spaces.size
no_spaces = split_by_spaces.select { |s| not /\A(\s*|\t|\n)\Z/.match s }
no_spaces.each_slice(width).to_a
end
def self.correct_cell_index?(call_index)
/\A#{CELL}\Z/.match call_index
end
def self.column(call_index)
column_letters = /[A-Z]+/.match call_index
column_letters.to_s.chars.map { |c| c.ord - 'A'.ord + 1 }.join.to_i(26)
end
def self.row(call_index)
row_numbers = /[0-9]+/.match call_index
row_numbers.to_s.to_i
end
def self.evaluate_number(raw_cell)
cell_to_f = raw_cell[1..-1].to_f
cell_to_f % 1 == 0 ? cell_to_f.to_i : '%.2f' % cell_to_f
end
def self.extract_formula_arguments(match)
arguments = match[1].gsub(/(=.*\()/,'').chomp(')').split(',')
arguments.each { |argument| argument.gsub!(/\s+/, '') }
arguments.map! { |argument| argument.insert(0, '=') }
end
end
end

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

FF..F.......F.....F...FFFF..F..F.F.....F

Failures:

  1) Spreadsheet#new can be called with no arguments or with a single string argument
     Failure/Error: Spreadsheet.new
     NoMethodError:
       undefined method `lstrip' for nil:NilClass
     # /tmp/d20160121-5693-1a68qzj/solution.rb:141:in `parse_table'
     # /tmp/d20160121-5693-1a68qzj/solution.rb:7:in `initialize'
     # /tmp/d20160121-5693-1a68qzj/spec.rb:4:in `new'
     # /tmp/d20160121-5693-1a68qzj/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
     NoMethodError:
       undefined method `lstrip' for nil:NilClass
     # /tmp/d20160121-5693-1a68qzj/solution.rb:141:in `parse_table'
     # /tmp/d20160121-5693-1a68qzj/solution.rb:7:in `initialize'
     # /tmp/d20160121-5693-1a68qzj/spec.rb:10:in `new'
     # /tmp/d20160121-5693-1a68qzj/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 ''
     NoMethodError:
       undefined method `lstrip' for nil:NilClass
     # /tmp/d20160121-5693-1a68qzj/solution.rb:141:in `parse_table'
     # /tmp/d20160121-5693-1a68qzj/solution.rb:7:in `initialize'
     # /tmp/d20160121-5693-1a68qzj/spec.rb:24:in `new'
     # /tmp/d20160121-5693-1a68qzj/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 #<NoMethodError: undefined method `lstrip' for nil:NilClass> with backtrace:
         # /tmp/d20160121-5693-1a68qzj/solution.rb:141:in `parse_table'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:7:in `initialize'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:75:in `new'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:75:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1a68qzj/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-1a68qzj/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#[] raises an exception for less than two arguments passed to ADD
     Failure/Error: expect { Spreadsheet.new('=ADD(1)')['A1'] }.to raise_error(
       expected Spreadsheet::Error with message matching /Wrong number of arguments for 'ADD': expected at least 2, got 1/, got #<Spreadsheet::Error: Wrong number of arguments for 'FOO': expected at least 2, got 1> with backtrace:
         # /tmp/d20160121-5693-1a68qzj/solution.rb:116:in `argument_size_error'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:101:in `argument_number_error'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:31:in `[]'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:119:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:119: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-1a68qzj/spec.rb:119: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#[] raises an exception for less than two arguments to MULTIPLY
     Failure/Error: expect { Spreadsheet.new('=MULTIPLY(1)')['A1'] }.to raise_error(
       expected Spreadsheet::Error with message matching /Wrong number of arguments for 'MULTIPLY': expected at least 2, got 1/, got #<Spreadsheet::Error: Wrong number of arguments for 'FOO': expected at least 2, got 1> with backtrace:
         # /tmp/d20160121-5693-1a68qzj/solution.rb:116:in `argument_size_error'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:101:in `argument_number_error'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:31:in `[]'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:149:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:149: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-1a68qzj/spec.rb:149: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#[] subtracts two numbers with SUBTRACT
     Failure/Error: expect(sheet['A1']).to eq('2')
     Spreadsheet::Error:
       Unknown function 'SUBTRACT'
     # /tmp/d20160121-5693-1a68qzj/solution.rb:81:in `unknown_function_error'
     # /tmp/d20160121-5693-1a68qzj/solution.rb:29:in `[]'
     # /tmp/d20160121-5693-1a68qzj/spec.rb:161: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#[] subtracts numbers via cell references
     Failure/Error: expect(sheet['D1']).to eq('4')
     Spreadsheet::Error:
       Unknown function 'SUBTRACT'
     # /tmp/d20160121-5693-1a68qzj/solution.rb:81:in `unknown_function_error'
     # /tmp/d20160121-5693-1a68qzj/solution.rb:29:in `[]'
     # /tmp/d20160121-5693-1a68qzj/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)>'

  9) Spreadsheet#[] raises an exception when SUBTRACT is called with a wrong number of arguments
     Failure/Error: expect { Spreadsheet.new('=SUBTRACT(1)')['A1'] }.to raise_error(
       expected Spreadsheet::Error with message matching /Wrong number of arguments for 'SUBTRACT': expected 2, got 1/, got #<Spreadsheet::Error: Unknown function 'SUBTRACT'> with backtrace:
         # /tmp/d20160121-5693-1a68qzj/solution.rb:81:in `unknown_function_error'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:29:in `[]'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:171:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:171: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-1a68qzj/spec.rb:171: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#[] raises an exception when DIVIDE is called with a wrong number of arguments
     Failure/Error: expect { Spreadsheet.new('=DIVIDE(1)')['A1'] }.to raise_error(
       expected Spreadsheet::Error with message matching /Wrong number of arguments for 'DIVIDE': expected 2, got 1/, got #<Spreadsheet::Error: Wrong number of arguments for 'FOO': expected 2, got 1> with backtrace:
         # /tmp/d20160121-5693-1a68qzj/solution.rb:116:in `argument_size_error'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:103:in `argument_number_error'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:31:in `[]'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:195:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:195: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-1a68qzj/spec.rb:195: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 when MOD is called with a wrong number of arguments
     Failure/Error: expect { Spreadsheet.new('=MOD(1)')['A1'] }.to raise_error(
       expected Spreadsheet::Error with message matching /Wrong number of arguments for 'MOD': expected 2, got 1/, got #<Spreadsheet::Error: Wrong number of arguments for 'FOO': expected 2, got 1> with backtrace:
         # /tmp/d20160121-5693-1a68qzj/solution.rb:116:in `argument_size_error'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:103:in `argument_number_error'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:31:in `[]'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:219:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:219: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-1a68qzj/spec.rb:219: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 floating point numbers with SUBTRACT
     Failure/Error: expect(Spreadsheet.new('10  =SUBTRACT(A1, 1.1)')['B1']).to eq '8.90'
     Spreadsheet::Error:
       Unknown function 'SUBTRACT'
     # /tmp/d20160121-5693-1a68qzj/solution.rb:81:in `unknown_function_error'
     # /tmp/d20160121-5693-1a68qzj/solution.rb:29:in `[]'
     # /tmp/d20160121-5693-1a68qzj/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)>'

  13) 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 #<NoMethodError: undefined method `[]' for nil:NilClass> with backtrace:
         # /tmp/d20160121-5693-1a68qzj/solution.rb:80:in `unknown_function_error'
         # /tmp/d20160121-5693-1a68qzj/solution.rb:29:in `[]'
         # /tmp/d20160121-5693-1a68qzj/spec.rb:266:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1a68qzj/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-1a68qzj/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.11178 seconds
40 examples, 13 failures

Failed examples:

rspec /tmp/d20160121-5693-1a68qzj/spec.rb:3 # Spreadsheet#new can be called with no arguments or with a single string argument
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:9 # Spreadsheet#new creates a blank sheet when no arguments are passed
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:23 # Spreadsheet#to_s returns blank tables as blank strings
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:74 # Spreadsheet#[] raises an exception for non-existant cells
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:118 # Spreadsheet#[] raises an exception for less than two arguments passed to ADD
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:148 # Spreadsheet#[] raises an exception for less than two arguments to MULTIPLY
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:158 # Spreadsheet#[] subtracts two numbers with SUBTRACT
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:164 # Spreadsheet#[] subtracts numbers via cell references
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:170 # Spreadsheet#[] raises an exception when SUBTRACT is called with a wrong number of arguments
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:194 # Spreadsheet#[] raises an exception when DIVIDE is called with a wrong number of arguments
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:218 # Spreadsheet#[] raises an exception when MOD is called with a wrong number of arguments
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:233 # Spreadsheet#[] subtracts floating point numbers with SUBTRACT
rspec /tmp/d20160121-5693-1a68qzj/spec.rb:265 # Spreadsheet#[] raises an exception for invalid expressions

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

Алекс обнови решението на 06.01.2016 20:39 (преди над 8 години)

+class Spreadsheet
+ attr_accessor :table
+
+ def initialize(table = nil)
+ @table = Array.new
+
+ @table = Parser.parse_table(table) if table
+ end
+
+ def empty?
+ @table.empty?
+ end
+
+ def cell_at(call_index)
+ unless Parser.correct_cell_index?(call_index)
+ raise Spreadsheet::Error, "Invalid cell index '#{call_index}'"
+ end
+
+ cell = @table[Parser.row(call_index) - 1][Parser.column(call_index) - 1]
+
+ rescue NoMethodError => e
+ raise Spreadsheet::Error, "Cell '#{call_index}' does not exist"
+ end
+
+ def [](call_index)
+ raw_cell = cell_at(call_index)
+
+ if raw_cell != nil and raw_cell[0] == '='
+ Error.new.unknown_function_error(raw_cell)
+ Error.new.invalid_expression_error(raw_cell)
+ Error.new.argument_number_error(raw_cell)
+ end
+
+ evaluate_cell_data(raw_cell)
+ end
+
+ def to_s
+ evaluated_table = @table.map do |row|
+ row.map { |cell| evaluate_cell_data(cell) }
+ end
+
+ evaluated_table.map { |row| row.join("\t") }.join("\n")
+ end
+
+ private
+
+ def evaluate_cell_data(raw_cell)
+ case raw_cell
+ when /\A=?#{Parser::NUMBER}\Z/ then Parser.evaluate_number(raw_cell)
+ when /\A=#{Parser::CELL}\Z/
+ evaluate_cell_data(cell_at(raw_cell[1..-1]))
+ when /\A#{Parser::FORMULA}\Z/ then evaluate_formula(raw_cell)
+ when /\A[^=].*\Z/ then raw_cell
+ end
+ end
+
+ def evaluate_formula(raw_cell)
+ match, formula = Parser::FORMULA.match(raw_cell), /=(.*)\(/.match(raw_cell)
+ arguments = extract_formula_arguments(match)
+ accumulated = arguments.reduce do |memo, argument|
+ Parser::FORMULA_TO_METHOD[formula[1]].to_proc.call(memo, argument)
+ end
+ accumulated % 1 == 0 ? accumulated.to_i.to_s : '%.2f' % accumulated
+ end
+
+ def extract_formula_arguments(match)
+ arguments = match[1].scan(/#{Parser::NUMBER}|#{Parser::CELL}/)
+ arguments.flatten!.compact!
+ arguments.each { |argument| argument.chomp!(',') }
+ arguments.map! { |argument| argument.insert(0, '=') }
+ arguments.map! { |argument| evaluate_cell_data(argument) }
+ arguments.map! { |argument| argument.to_f }
+ end
+
+ class Error < Exception
+ def unknown_function_error(formula)
+ function_name = /=(.*)\(/.match formula
+
+ if Parser::FORMULA_TO_METHOD[function_name[1]].nil?
+ raise Spreadsheet::Error, "Unknown function '#{function_name[1]}'"
+ end
+ end
+
+ def invalid_expression_error(formula)
+ expression = formula[1..-1]
+ number = Parser::NUMBER
+ cell = Parser::CELL
+ at_least_one = "(((#{number}|#{cell})(\s*,\s*))*(#{number}|#{cell}))"
+
+ unless /\A[A-Z]+\(#{at_least_one}\)\Z/.match expression
+ raise Spreadsheet::Error, "Invalid expression '#{expression}'"
+ end
+ end
+
+ def argument_number_error(formula)
+ function_name = /=(.*)\(/.match formula
+ argument_size = (/\((.*)\)/.match formula).to_s.split(',').size
+
+ case function_name[1]
+ when 'ADD', 'MULTIPLY' then argument_size_error('<', argument_size)
+ when 'SUBSTRACT', 'MOD', 'DIVIDE'
+ argument_size_error('!=', argument_size)
+ end
+ end
+
+ def argument_size_error(operation, argument_size)
+ message_start = "Wrong number of arguments for 'FOO': "
+ error_condition = operation.to_sym.to_proc.call(argument_size, 2)
+
+ case operation
+ when '<' then message_end = "expected at least 2, got #{argument_size}"
+ when '!=' then message_end = "expected 2, got #{argument_size}"
+ end
+
+ raise Spreadsheet::Error, message_start + message_end if error_condition
+ end
+ end
+
+ class Parser
+ CELL = "([A-Z]+[1-9]+[0-9]*)"
+ NUMBER = "(0|-?0\.[0-9]*|-?[1-9][0-9]*\.?[0-9]*)"
+
+ MULTIPLE_ARGUMENTS = "(((#{NUMBER}|#{CELL})(\s*,\s*))+(#{NUMBER}|#{CELL}))"
+ TWO_ARGUMENTS = "((#{NUMBER}|#{CELL})\s*,\s*(#{NUMBER}|#{CELL}))"
+
+ ADD = /\A=ADD\(#{MULTIPLE_ARGUMENTS}\)\Z/
+ MULTIPLY = /\A=MULTIPLY\(#{MULTIPLE_ARGUMENTS}\)\Z/
+ SUBSTRACT = /\A=SUBSTRACT\(#{TWO_ARGUMENTS}\)\Z/
+ DIVIDE = /\A=DIVIDE\(#{TWO_ARGUMENTS}\)\Z/
+ MOD = /\A=MOD\(#{TWO_ARGUMENTS}\)\Z/
+
+ FORMULA = /(#{ADD}|#{MULTIPLY}|#{SUBSTRACT}|#{DIVIDE}|#{MOD})/
+
+ FORMULA_TO_METHOD = { 'ADD' => '+'.to_sym, 'MULTIPLY' => '*'.to_sym,
+ 'SUBSTRACT' => '-'.to_sym, 'DIVIDE' => '/'.to_sym,
+ 'MOD' => '%'.to_sym,
+ }
+
+ def self.parse_table(table)
+ split_by_spaces = table.lstrip.rstrip.split(/( {2,}|\t|\n)/)
+ first_new_line = split_by_spaces.find_index("\n")
+ width = first_new_line ? (first_new_line + 1) / 2 : split_by_spaces.size
+
+ no_spaces = split_by_spaces.select { |s| not /\A(\s*|\t|\n)\Z/.match s }
+ no_spaces.each_slice(width).to_a
+ end
+
+ def self.correct_cell_index?(call_index)
+ /\A#{CELL}\Z/.match call_index
+ end
+
+ def self.column(call_index)
+ column_letters = /[A-Z]+/.match call_index
+ column_letters.to_s.chars.map { |c| c.ord - 'A'.ord + 1 }.join.to_i(26)
+ end
+
+ def self.row(call_index)
+ row_numbers = /[0-9]+/.match call_index
+ row_numbers.to_s.to_i
+ end
+
+ def self.evaluate_number(raw_cell)
+ cell_to_f = raw_cell[0] == '=' ? raw_cell[1..-1].to_f : raw_cell.to_f
+ cell_to_f % 1 == 0 ? cell_to_f.to_i : '%.2f' % cell_to_f
+ end
+ end
+end

Алекс обнови решението на 09.01.2016 17:38 (преди над 8 години)

class Spreadsheet
attr_accessor :table
def initialize(table = nil)
@table = Array.new
@table = Parser.parse_table(table) if table
end
def empty?
@table.empty?
end
def cell_at(call_index)
unless Parser.correct_cell_index?(call_index)
raise Spreadsheet::Error, "Invalid cell index '#{call_index}'"
end
cell = @table[Parser.row(call_index) - 1][Parser.column(call_index) - 1]
rescue NoMethodError => e
raise Spreadsheet::Error, "Cell '#{call_index}' does not exist"
end
def [](call_index)
raw_cell = cell_at(call_index)
if raw_cell != nil and raw_cell[0] == '='
Error.new.unknown_function_error(raw_cell)
Error.new.invalid_expression_error(raw_cell)
Error.new.argument_number_error(raw_cell)
end
evaluate_cell_data(raw_cell)
end
def to_s
evaluated_table = @table.map do |row|
row.map { |cell| evaluate_cell_data(cell) }
end
evaluated_table.map { |row| row.join("\t") }.join("\n")
end
private
def evaluate_cell_data(raw_cell)
case raw_cell
when /\A=?#{Parser::NUMBER}\Z/ then Parser.evaluate_number(raw_cell)
when /\A=#{Parser::CELL}\Z/
evaluate_cell_data(cell_at(raw_cell[1..-1]))
when /\A#{Parser::FORMULA}\Z/ then evaluate_formula(raw_cell)
when /\A[^=].*\Z/ then raw_cell
end
end
def evaluate_formula(raw_cell)
match, formula = Parser::FORMULA.match(raw_cell), /=(.*)\(/.match(raw_cell)
+
arguments = extract_formula_arguments(match)
accumulated = arguments.reduce do |memo, argument|
Parser::FORMULA_TO_METHOD[formula[1]].to_proc.call(memo, argument)
end
accumulated % 1 == 0 ? accumulated.to_i.to_s : '%.2f' % accumulated
end
def extract_formula_arguments(match)
- arguments = match[1].scan(/#{Parser::NUMBER}|#{Parser::CELL}/)
- arguments.flatten!.compact!
- arguments.each { |argument| argument.chomp!(',') }
- arguments.map! { |argument| argument.insert(0, '=') }
+ arguments = match[1].gsub(/(=.*\()/,'').chomp(')').split(',')
+
+ arguments.each { |argument| argument.gsub!(/\s+/, '') }
+ arguments.each { |argument| argument.insert(0, '=') }
arguments.map! { |argument| evaluate_cell_data(argument) }
arguments.map! { |argument| argument.to_f }
end
class Error < Exception
def unknown_function_error(formula)
function_name = /=(.*)\(/.match formula
if Parser::FORMULA_TO_METHOD[function_name[1]].nil?
raise Spreadsheet::Error, "Unknown function '#{function_name[1]}'"
end
end
def invalid_expression_error(formula)
expression = formula[1..-1]
number = Parser::NUMBER
cell = Parser::CELL
at_least_one = "(((#{number}|#{cell})(\s*,\s*))*(#{number}|#{cell}))"
unless /\A[A-Z]+\(#{at_least_one}\)\Z/.match expression
raise Spreadsheet::Error, "Invalid expression '#{expression}'"
end
end
def argument_number_error(formula)
function_name = /=(.*)\(/.match formula
argument_size = (/\((.*)\)/.match formula).to_s.split(',').size
case function_name[1]
when 'ADD', 'MULTIPLY' then argument_size_error('<', argument_size)
when 'SUBSTRACT', 'MOD', 'DIVIDE'
argument_size_error('!=', argument_size)
end
end
def argument_size_error(operation, argument_size)
message_start = "Wrong number of arguments for 'FOO': "
error_condition = operation.to_sym.to_proc.call(argument_size, 2)
case operation
when '<' then message_end = "expected at least 2, got #{argument_size}"
when '!=' then message_end = "expected 2, got #{argument_size}"
end
raise Spreadsheet::Error, message_start + message_end if error_condition
end
end
class Parser
CELL = "([A-Z]+[1-9]+[0-9]*)"
NUMBER = "(0|-?0\.[0-9]*|-?[1-9][0-9]*\.?[0-9]*)"
MULTIPLE_ARGUMENTS = "(((#{NUMBER}|#{CELL})(\s*,\s*))+(#{NUMBER}|#{CELL}))"
TWO_ARGUMENTS = "((#{NUMBER}|#{CELL})\s*,\s*(#{NUMBER}|#{CELL}))"
ADD = /\A=ADD\(#{MULTIPLE_ARGUMENTS}\)\Z/
MULTIPLY = /\A=MULTIPLY\(#{MULTIPLE_ARGUMENTS}\)\Z/
SUBSTRACT = /\A=SUBSTRACT\(#{TWO_ARGUMENTS}\)\Z/
DIVIDE = /\A=DIVIDE\(#{TWO_ARGUMENTS}\)\Z/
MOD = /\A=MOD\(#{TWO_ARGUMENTS}\)\Z/
FORMULA = /(#{ADD}|#{MULTIPLY}|#{SUBSTRACT}|#{DIVIDE}|#{MOD})/
FORMULA_TO_METHOD = { 'ADD' => '+'.to_sym, 'MULTIPLY' => '*'.to_sym,
'SUBSTRACT' => '-'.to_sym, 'DIVIDE' => '/'.to_sym,
'MOD' => '%'.to_sym,
}
def self.parse_table(table)
split_by_spaces = table.lstrip.rstrip.split(/( {2,}|\t|\n)/)
first_new_line = split_by_spaces.find_index("\n")
width = first_new_line ? (first_new_line + 1) / 2 : split_by_spaces.size
no_spaces = split_by_spaces.select { |s| not /\A(\s*|\t|\n)\Z/.match s }
no_spaces.each_slice(width).to_a
end
def self.correct_cell_index?(call_index)
/\A#{CELL}\Z/.match call_index
end
def self.column(call_index)
column_letters = /[A-Z]+/.match call_index
column_letters.to_s.chars.map { |c| c.ord - 'A'.ord + 1 }.join.to_i(26)
end
def self.row(call_index)
row_numbers = /[0-9]+/.match call_index
row_numbers.to_s.to_i
end
def self.evaluate_number(raw_cell)
cell_to_f = raw_cell[0] == '=' ? raw_cell[1..-1].to_f : raw_cell.to_f
cell_to_f % 1 == 0 ? cell_to_f.to_i : '%.2f' % cell_to_f
end
end
end

Алекс обнови решението на 09.01.2016 18:32 (преди над 8 години)

class Spreadsheet
attr_accessor :table
def initialize(table = nil)
@table = Array.new
@table = Parser.parse_table(table) if table
end
def empty?
@table.empty?
end
def cell_at(call_index)
unless Parser.correct_cell_index?(call_index)
raise Spreadsheet::Error, "Invalid cell index '#{call_index}'"
end
cell = @table[Parser.row(call_index) - 1][Parser.column(call_index) - 1]
rescue NoMethodError => e
raise Spreadsheet::Error, "Cell '#{call_index}' does not exist"
end
def [](call_index)
raw_cell = cell_at(call_index)
if raw_cell != nil and raw_cell[0] == '='
Error.new.unknown_function_error(raw_cell)
Error.new.invalid_expression_error(raw_cell)
Error.new.argument_number_error(raw_cell)
end
evaluate_cell_data(raw_cell)
end
def to_s
evaluated_table = @table.map do |row|
row.map { |cell| evaluate_cell_data(cell) }
end
evaluated_table.map { |row| row.join("\t") }.join("\n")
end
private
def evaluate_cell_data(raw_cell)
case raw_cell
- when /\A=?#{Parser::NUMBER}\Z/ then Parser.evaluate_number(raw_cell)
- when /\A=#{Parser::CELL}\Z/
- evaluate_cell_data(cell_at(raw_cell[1..-1]))
+ when /\A=#{Parser::NUMBER}\Z/ then Parser.evaluate_number(raw_cell)
+ when /\A=#{Parser::CELL}\Z/ then evaluate_cell(raw_cell)
when /\A#{Parser::FORMULA}\Z/ then evaluate_formula(raw_cell)
- when /\A[^=].*\Z/ then raw_cell
+ else raw_cell
end
end
def evaluate_formula(raw_cell)
match, formula = Parser::FORMULA.match(raw_cell), /=(.*)\(/.match(raw_cell)
- arguments = extract_formula_arguments(match)
+ arguments = Parser.extract_formula_arguments(match)
+ arguments.map! { |argument| evaluate_cell_data(argument).to_f }
accumulated = arguments.reduce do |memo, argument|
Parser::FORMULA_TO_METHOD[formula[1]].to_proc.call(memo, argument)
end
accumulated % 1 == 0 ? accumulated.to_i.to_s : '%.2f' % accumulated
end
- def extract_formula_arguments(match)
- arguments = match[1].gsub(/(=.*\()/,'').chomp(')').split(',')
+ def evaluate_cell(raw_cell)
+ reference_data = cell_at(raw_cell[1..-1]).dup
- arguments.each { |argument| argument.gsub!(/\s+/, '') }
- arguments.each { |argument| argument.insert(0, '=') }
- arguments.map! { |argument| evaluate_cell_data(argument) }
- arguments.map! { |argument| argument.to_f }
+ if /\A#{Parser::NUMBER}\Z/.match reference_data
+ reference_data.insert(0, '=')
+ end
+ evaluate_cell_data(reference_data)
end
class Error < Exception
def unknown_function_error(formula)
function_name = /=(.*)\(/.match formula
if Parser::FORMULA_TO_METHOD[function_name[1]].nil?
raise Spreadsheet::Error, "Unknown function '#{function_name[1]}'"
end
end
def invalid_expression_error(formula)
expression = formula[1..-1]
number = Parser::NUMBER
cell = Parser::CELL
at_least_one = "(((#{number}|#{cell})(\s*,\s*))*(#{number}|#{cell}))"
unless /\A[A-Z]+\(#{at_least_one}\)\Z/.match expression
raise Spreadsheet::Error, "Invalid expression '#{expression}'"
end
end
def argument_number_error(formula)
function_name = /=(.*)\(/.match formula
argument_size = (/\((.*)\)/.match formula).to_s.split(',').size
case function_name[1]
when 'ADD', 'MULTIPLY' then argument_size_error('<', argument_size)
when 'SUBSTRACT', 'MOD', 'DIVIDE'
argument_size_error('!=', argument_size)
end
end
def argument_size_error(operation, argument_size)
message_start = "Wrong number of arguments for 'FOO': "
error_condition = operation.to_sym.to_proc.call(argument_size, 2)
case operation
when '<' then message_end = "expected at least 2, got #{argument_size}"
when '!=' then message_end = "expected 2, got #{argument_size}"
end
raise Spreadsheet::Error, message_start + message_end if error_condition
end
end
class Parser
CELL = "([A-Z]+[1-9]+[0-9]*)"
NUMBER = "(0|-?0\.[0-9]*|-?[1-9][0-9]*\.?[0-9]*)"
MULTIPLE_ARGUMENTS = "(((#{NUMBER}|#{CELL})(\s*,\s*))+(#{NUMBER}|#{CELL}))"
TWO_ARGUMENTS = "((#{NUMBER}|#{CELL})\s*,\s*(#{NUMBER}|#{CELL}))"
ADD = /\A=ADD\(#{MULTIPLE_ARGUMENTS}\)\Z/
MULTIPLY = /\A=MULTIPLY\(#{MULTIPLE_ARGUMENTS}\)\Z/
SUBSTRACT = /\A=SUBSTRACT\(#{TWO_ARGUMENTS}\)\Z/
DIVIDE = /\A=DIVIDE\(#{TWO_ARGUMENTS}\)\Z/
MOD = /\A=MOD\(#{TWO_ARGUMENTS}\)\Z/
FORMULA = /(#{ADD}|#{MULTIPLY}|#{SUBSTRACT}|#{DIVIDE}|#{MOD})/
FORMULA_TO_METHOD = { 'ADD' => '+'.to_sym, 'MULTIPLY' => '*'.to_sym,
'SUBSTRACT' => '-'.to_sym, 'DIVIDE' => '/'.to_sym,
'MOD' => '%'.to_sym,
}
def self.parse_table(table)
split_by_spaces = table.lstrip.rstrip.split(/( {2,}|\t|\n)/)
first_new_line = split_by_spaces.find_index("\n")
width = first_new_line ? (first_new_line + 1) / 2 : split_by_spaces.size
no_spaces = split_by_spaces.select { |s| not /\A(\s*|\t|\n)\Z/.match s }
no_spaces.each_slice(width).to_a
end
def self.correct_cell_index?(call_index)
/\A#{CELL}\Z/.match call_index
end
def self.column(call_index)
column_letters = /[A-Z]+/.match call_index
column_letters.to_s.chars.map { |c| c.ord - 'A'.ord + 1 }.join.to_i(26)
end
def self.row(call_index)
row_numbers = /[0-9]+/.match call_index
row_numbers.to_s.to_i
end
def self.evaluate_number(raw_cell)
- cell_to_f = raw_cell[0] == '=' ? raw_cell[1..-1].to_f : raw_cell.to_f
+ cell_to_f = raw_cell[1..-1].to_f
cell_to_f % 1 == 0 ? cell_to_f.to_i : '%.2f' % cell_to_f
end
+
+ def self.extract_formula_arguments(match)
+ arguments = match[1].gsub(/(=.*\()/,'').chomp(')').split(',')
+
+ arguments.each { |argument| argument.gsub!(/\s+/, '') }
+ arguments.map! { |argument| argument.insert(0, '=') }
+ end
end
end

Алекс обнови решението на 09.01.2016 18:32 (преди над 8 години)

class Spreadsheet
attr_accessor :table
def initialize(table = nil)
@table = Array.new
@table = Parser.parse_table(table) if table
end
def empty?
@table.empty?
end
def cell_at(call_index)
unless Parser.correct_cell_index?(call_index)
raise Spreadsheet::Error, "Invalid cell index '#{call_index}'"
end
cell = @table[Parser.row(call_index) - 1][Parser.column(call_index) - 1]
rescue NoMethodError => e
raise Spreadsheet::Error, "Cell '#{call_index}' does not exist"
end
def [](call_index)
raw_cell = cell_at(call_index)
if raw_cell != nil and raw_cell[0] == '='
Error.new.unknown_function_error(raw_cell)
Error.new.invalid_expression_error(raw_cell)
Error.new.argument_number_error(raw_cell)
end
evaluate_cell_data(raw_cell)
end
def to_s
evaluated_table = @table.map do |row|
row.map { |cell| evaluate_cell_data(cell) }
end
evaluated_table.map { |row| row.join("\t") }.join("\n")
end
private
def evaluate_cell_data(raw_cell)
case raw_cell
when /\A=#{Parser::NUMBER}\Z/ then Parser.evaluate_number(raw_cell)
when /\A=#{Parser::CELL}\Z/ then evaluate_cell(raw_cell)
when /\A#{Parser::FORMULA}\Z/ then evaluate_formula(raw_cell)
else raw_cell
end
end
def evaluate_formula(raw_cell)
match, formula = Parser::FORMULA.match(raw_cell), /=(.*)\(/.match(raw_cell)
arguments = Parser.extract_formula_arguments(match)
arguments.map! { |argument| evaluate_cell_data(argument).to_f }
accumulated = arguments.reduce do |memo, argument|
Parser::FORMULA_TO_METHOD[formula[1]].to_proc.call(memo, argument)
end
accumulated % 1 == 0 ? accumulated.to_i.to_s : '%.2f' % accumulated
end
def evaluate_cell(raw_cell)
reference_data = cell_at(raw_cell[1..-1]).dup
if /\A#{Parser::NUMBER}\Z/.match reference_data
reference_data.insert(0, '=')
end
evaluate_cell_data(reference_data)
end
class Error < Exception
def unknown_function_error(formula)
function_name = /=(.*)\(/.match formula
if Parser::FORMULA_TO_METHOD[function_name[1]].nil?
raise Spreadsheet::Error, "Unknown function '#{function_name[1]}'"
end
end
def invalid_expression_error(formula)
expression = formula[1..-1]
number = Parser::NUMBER
cell = Parser::CELL
at_least_one = "(((#{number}|#{cell})(\s*,\s*))*(#{number}|#{cell}))"
unless /\A[A-Z]+\(#{at_least_one}\)\Z/.match expression
raise Spreadsheet::Error, "Invalid expression '#{expression}'"
end
end
def argument_number_error(formula)
function_name = /=(.*)\(/.match formula
argument_size = (/\((.*)\)/.match formula).to_s.split(',').size
case function_name[1]
when 'ADD', 'MULTIPLY' then argument_size_error('<', argument_size)
when 'SUBSTRACT', 'MOD', 'DIVIDE'
argument_size_error('!=', argument_size)
end
end
def argument_size_error(operation, argument_size)
message_start = "Wrong number of arguments for 'FOO': "
error_condition = operation.to_sym.to_proc.call(argument_size, 2)
case operation
when '<' then message_end = "expected at least 2, got #{argument_size}"
when '!=' then message_end = "expected 2, got #{argument_size}"
end
raise Spreadsheet::Error, message_start + message_end if error_condition
end
end
class Parser
CELL = "([A-Z]+[1-9]+[0-9]*)"
NUMBER = "(0|-?0\.[0-9]*|-?[1-9][0-9]*\.?[0-9]*)"
MULTIPLE_ARGUMENTS = "(((#{NUMBER}|#{CELL})(\s*,\s*))+(#{NUMBER}|#{CELL}))"
TWO_ARGUMENTS = "((#{NUMBER}|#{CELL})\s*,\s*(#{NUMBER}|#{CELL}))"
ADD = /\A=ADD\(#{MULTIPLE_ARGUMENTS}\)\Z/
MULTIPLY = /\A=MULTIPLY\(#{MULTIPLE_ARGUMENTS}\)\Z/
SUBSTRACT = /\A=SUBSTRACT\(#{TWO_ARGUMENTS}\)\Z/
DIVIDE = /\A=DIVIDE\(#{TWO_ARGUMENTS}\)\Z/
MOD = /\A=MOD\(#{TWO_ARGUMENTS}\)\Z/
FORMULA = /(#{ADD}|#{MULTIPLY}|#{SUBSTRACT}|#{DIVIDE}|#{MOD})/
FORMULA_TO_METHOD = { 'ADD' => '+'.to_sym, 'MULTIPLY' => '*'.to_sym,
'SUBSTRACT' => '-'.to_sym, 'DIVIDE' => '/'.to_sym,
'MOD' => '%'.to_sym,
}
def self.parse_table(table)
split_by_spaces = table.lstrip.rstrip.split(/( {2,}|\t|\n)/)
first_new_line = split_by_spaces.find_index("\n")
width = first_new_line ? (first_new_line + 1) / 2 : split_by_spaces.size
no_spaces = split_by_spaces.select { |s| not /\A(\s*|\t|\n)\Z/.match s }
no_spaces.each_slice(width).to_a
end
def self.correct_cell_index?(call_index)
/\A#{CELL}\Z/.match call_index
end
def self.column(call_index)
column_letters = /[A-Z]+/.match call_index
column_letters.to_s.chars.map { |c| c.ord - 'A'.ord + 1 }.join.to_i(26)
end
def self.row(call_index)
row_numbers = /[0-9]+/.match call_index
row_numbers.to_s.to_i
end
def self.evaluate_number(raw_cell)
cell_to_f = raw_cell[1..-1].to_f
cell_to_f % 1 == 0 ? cell_to_f.to_i : '%.2f' % cell_to_f
end
def self.extract_formula_arguments(match)
arguments = match[1].gsub(/(=.*\()/,'').chomp(')').split(',')
arguments.each { |argument| argument.gsub!(/\s+/, '') }
arguments.map! { |argument| argument.insert(0, '=') }
- end
+ end
end
end

Алекс обнови решението на 10.01.2016 12:42 (преди над 8 години)

class Spreadsheet
attr_accessor :table
def initialize(table = nil)
@table = Array.new
- @table = Parser.parse_table(table) if table
+ @table = Parser.parse_table(table) unless /\A\s*\Z/.match table
end
def empty?
@table.empty?
end
def cell_at(call_index)
unless Parser.correct_cell_index?(call_index)
raise Spreadsheet::Error, "Invalid cell index '#{call_index}'"
end
cell = @table[Parser.row(call_index) - 1][Parser.column(call_index) - 1]
rescue NoMethodError => e
raise Spreadsheet::Error, "Cell '#{call_index}' does not exist"
end
def [](call_index)
raw_cell = cell_at(call_index)
if raw_cell != nil and raw_cell[0] == '='
Error.new.unknown_function_error(raw_cell)
Error.new.invalid_expression_error(raw_cell)
Error.new.argument_number_error(raw_cell)
end
evaluate_cell_data(raw_cell)
end
def to_s
evaluated_table = @table.map do |row|
row.map { |cell| evaluate_cell_data(cell) }
end
evaluated_table.map { |row| row.join("\t") }.join("\n")
end
private
def evaluate_cell_data(raw_cell)
case raw_cell
when /\A=#{Parser::NUMBER}\Z/ then Parser.evaluate_number(raw_cell)
when /\A=#{Parser::CELL}\Z/ then evaluate_cell(raw_cell)
when /\A#{Parser::FORMULA}\Z/ then evaluate_formula(raw_cell)
else raw_cell
end
end
def evaluate_formula(raw_cell)
match, formula = Parser::FORMULA.match(raw_cell), /=(.*)\(/.match(raw_cell)
arguments = Parser.extract_formula_arguments(match)
arguments.map! { |argument| evaluate_cell_data(argument).to_f }
accumulated = arguments.reduce do |memo, argument|
Parser::FORMULA_TO_METHOD[formula[1]].to_proc.call(memo, argument)
end
accumulated % 1 == 0 ? accumulated.to_i.to_s : '%.2f' % accumulated
end
def evaluate_cell(raw_cell)
reference_data = cell_at(raw_cell[1..-1]).dup
if /\A#{Parser::NUMBER}\Z/.match reference_data
reference_data.insert(0, '=')
end
evaluate_cell_data(reference_data)
end
class Error < Exception
def unknown_function_error(formula)
function_name = /=(.*)\(/.match formula
if Parser::FORMULA_TO_METHOD[function_name[1]].nil?
raise Spreadsheet::Error, "Unknown function '#{function_name[1]}'"
end
end
def invalid_expression_error(formula)
expression = formula[1..-1]
number = Parser::NUMBER
cell = Parser::CELL
at_least_one = "(((#{number}|#{cell})(\s*,\s*))*(#{number}|#{cell}))"
unless /\A[A-Z]+\(#{at_least_one}\)\Z/.match expression
raise Spreadsheet::Error, "Invalid expression '#{expression}'"
end
end
def argument_number_error(formula)
function_name = /=(.*)\(/.match formula
argument_size = (/\((.*)\)/.match formula).to_s.split(',').size
case function_name[1]
when 'ADD', 'MULTIPLY' then argument_size_error('<', argument_size)
when 'SUBSTRACT', 'MOD', 'DIVIDE'
argument_size_error('!=', argument_size)
end
end
def argument_size_error(operation, argument_size)
message_start = "Wrong number of arguments for 'FOO': "
error_condition = operation.to_sym.to_proc.call(argument_size, 2)
case operation
when '<' then message_end = "expected at least 2, got #{argument_size}"
when '!=' then message_end = "expected 2, got #{argument_size}"
end
raise Spreadsheet::Error, message_start + message_end if error_condition
end
end
class Parser
CELL = "([A-Z]+[1-9]+[0-9]*)"
NUMBER = "(0|-?0\.[0-9]*|-?[1-9][0-9]*\.?[0-9]*)"
MULTIPLE_ARGUMENTS = "(((#{NUMBER}|#{CELL})(\s*,\s*))+(#{NUMBER}|#{CELL}))"
TWO_ARGUMENTS = "((#{NUMBER}|#{CELL})\s*,\s*(#{NUMBER}|#{CELL}))"
ADD = /\A=ADD\(#{MULTIPLE_ARGUMENTS}\)\Z/
MULTIPLY = /\A=MULTIPLY\(#{MULTIPLE_ARGUMENTS}\)\Z/
SUBSTRACT = /\A=SUBSTRACT\(#{TWO_ARGUMENTS}\)\Z/
DIVIDE = /\A=DIVIDE\(#{TWO_ARGUMENTS}\)\Z/
MOD = /\A=MOD\(#{TWO_ARGUMENTS}\)\Z/
FORMULA = /(#{ADD}|#{MULTIPLY}|#{SUBSTRACT}|#{DIVIDE}|#{MOD})/
FORMULA_TO_METHOD = { 'ADD' => '+'.to_sym, 'MULTIPLY' => '*'.to_sym,
'SUBSTRACT' => '-'.to_sym, 'DIVIDE' => '/'.to_sym,
'MOD' => '%'.to_sym,
}
def self.parse_table(table)
split_by_spaces = table.lstrip.rstrip.split(/( {2,}|\t|\n)/)
first_new_line = split_by_spaces.find_index("\n")
width = first_new_line ? (first_new_line + 1) / 2 : split_by_spaces.size
no_spaces = split_by_spaces.select { |s| not /\A(\s*|\t|\n)\Z/.match s }
no_spaces.each_slice(width).to_a
end
def self.correct_cell_index?(call_index)
/\A#{CELL}\Z/.match call_index
end
def self.column(call_index)
column_letters = /[A-Z]+/.match call_index
column_letters.to_s.chars.map { |c| c.ord - 'A'.ord + 1 }.join.to_i(26)
end
def self.row(call_index)
row_numbers = /[0-9]+/.match call_index
row_numbers.to_s.to_i
end
def self.evaluate_number(raw_cell)
cell_to_f = raw_cell[1..-1].to_f
cell_to_f % 1 == 0 ? cell_to_f.to_i : '%.2f' % cell_to_f
end
def self.extract_formula_arguments(match)
arguments = match[1].gsub(/(=.*\()/,'').chomp(')').split(',')
arguments.each { |argument| argument.gsub!(/\s+/, '') }
arguments.map! { |argument| argument.insert(0, '=') }
end
end
end