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

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

Към профила на Анджелин Неделчев

Резултати

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

Код

class Spreadsheet
class Error < StandardError
end
def initialize(sheet = nil)
return unless sheet
@cells = []
@utilities = SheetUtilities.new(@cells)
@utilities.parse_sheet(sheet)
end
def empty?
@cells.empty?
end
def cell_at(cell_index)
cell = @utilities.get_by_cell_index(cell_index)
raise Error, "Cell '#{cell_index}' does not exist." unless cell
cell.to_s
end
def [](cell_index)
@utilities.calculate_expression(cell_at(cell_index))
end
def to_s
tab = ""
@cells.each do |row|
row.each { |cell| tab << "#{@utilities.calculate_expression(cell)}\t" }
tab.chop!
tab << "\n"
end
tab.chop!
end
end
class SheetUtilities
def initialize(cells)
@cells = cells
end
def parse_sheet(sheet)
sheet.strip.split("\n").each do |row|
next if row.empty?
delimiter = /#{Regexp.escape(row.include?("\t") ? "\t" : " ")}+/
current = []
row.strip.split(delimiter).each { |cell| current << cell.strip }
@cells << current
end
end
def parse_col(col)
index = 0
if col.size > 1
col[0..col.size - 2].each_char do |c|
index += (c.ord - ('A'.ord - 1)) * ('Z'.ord - 'A'.ord + 1)
end
end
index += col[col.size - 1].ord - ('A'.ord - 1)
index.to_i - 1
end
def extract_args(arguments)
arguments.split(',').map do |argument|
if argument =~ /[A-Z]+[0-9]+/
argument = get_by_cell_index(argument)
end
argument = argument.strip.to_f
end
end
def parse_formula(expression)
return $1 if expression.match(/^\=([-+]?\d+\.?\d*)+$/)
if expression.match(/^\=(\w+\d+)$/)
return calculate_expression(get_by_cell_index($1))
end
if expression.match(/(\w+)\(((\s*[-+]?[0-9A-Z]\.?\s*,?)+)+\)/)
return Formulas.get_formula($1).calculate(*extract_args($2))
end
false
end
def get_by_cell_index(cell_index)
cell_index.scan(/([A-Z]+)([0-9]+)/)
raise Spreadsheet::Error, "Invalid cell index '#{cell_index}'." unless $1
col = parse_col($1)
row = $2.to_i - 1
@cells[row][col] rescue nil
end
def calculate_expression(expression)
return expression if expression[0] != '='
calculation = parse_formula(expression)
unless calculation
raise Spreadsheet::Error, "Invalid expression '#{expression}'"
end
calculation.to_s
end
end
module Formulas
def self.get_formula(formula)
object = const_get(formula.downcase.capitalize).new rescue nil
raise Spreadsheet::Error, "Unknown function '#{formula}'" unless object
object
end
class Formula
LESS = "Wrong number of arguments for 'FOO': expected at least %s, got %s"
MORE = "Wrong number of arguments for 'FOO': expected %s, got %s"
attr_accessor :arguments_count
def calculate(*args)
check_arguments(args) unless arguments_count == 0
calculation = algorithm(*args).to_f
(calculation % 1 == 0.0) ? calculation.to_i : format('%.2f', calculation)
end
def algorithm(*args)
raise StandardError, 'base class method should not be called'
end
def check_arguments(args)
if args.count < @arguments_count
raise Spreadsheet::Error, LESS % [@arguments_count, args.count]
end
if args.count > @arguments_count
raise Spreadsheet::Error, MORE % [@arguments_count, args.count]
end
end
end
class Add < Formula
def initialize
@arguments_count = 0
end
def algorithm(*args)
args.reduce { |a, b| a + b }
end
end
class Multiply < Formula
def initialize
@arguments_count = 0
end
def algorithm(*args)
args.reduce { |a, b| a * b }
end
end
class Subtract < Formula
def initialize
@arguments_count = 2
end
def algorithm(*args)
args.first - args.last
end
end
class Divide < Formula
def initialize
@arguments_count = 2
end
def algorithm(*args)
args.first / args.last
end
end
class Mod < Formula
def initialize
@arguments_count = 2
end
def algorithm(*args)
args.first % args.last
end
end
end

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

.F..F.......F.....F...F..F..F..F....F.FF

Failures:

  1) Spreadsheet#new creates a blank sheet when no arguments are passed
     Failure/Error: expect(Spreadsheet.new).to be_empty
     NoMethodError:
       undefined method `empty?' for nil:NilClass
     # /tmp/d20160121-5693-jk5fag/solution.rb:13:in `empty?'
     # /tmp/d20160121-5693-jk5fag/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)>'

  2) Spreadsheet#to_s returns blank tables as blank strings
     Failure/Error: expect(Spreadsheet.new.to_s).to eq ''
     NoMethodError:
       undefined method `each' for nil:NilClass
     # /tmp/d20160121-5693-jk5fag/solution.rb:29:in `to_s'
     # /tmp/d20160121-5693-jk5fag/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)>'

  3) 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 `get_by_cell_index' for nil:NilClass> with backtrace:
         # /tmp/d20160121-5693-jk5fag/solution.rb:17:in `cell_at'
         # /tmp/d20160121-5693-jk5fag/solution.rb:24:in `[]'
         # /tmp/d20160121-5693-jk5fag/spec.rb:75:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-jk5fag/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-jk5fag/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)>'

  4) 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/ but nothing was raised
     # /tmp/d20160121-5693-jk5fag/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)>'

  5) 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/ but nothing was raised
     # /tmp/d20160121-5693-jk5fag/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)>'

  6) 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: Wrong number of arguments for 'FOO': expected at least 2, got 1> with backtrace:
         # /tmp/d20160121-5693-jk5fag/solution.rb:135:in `check_arguments'
         # /tmp/d20160121-5693-jk5fag/solution.rb:124:in `calculate'
         # /tmp/d20160121-5693-jk5fag/solution.rb:84:in `parse_formula'
         # /tmp/d20160121-5693-jk5fag/solution.rb:102:in `calculate_expression'
         # /tmp/d20160121-5693-jk5fag/solution.rb:24:in `[]'
         # /tmp/d20160121-5693-jk5fag/spec.rb:171:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-jk5fag/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-jk5fag/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)>'

  7) 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 at least 2, got 1> with backtrace:
         # /tmp/d20160121-5693-jk5fag/solution.rb:135:in `check_arguments'
         # /tmp/d20160121-5693-jk5fag/solution.rb:124:in `calculate'
         # /tmp/d20160121-5693-jk5fag/solution.rb:84:in `parse_formula'
         # /tmp/d20160121-5693-jk5fag/solution.rb:102:in `calculate_expression'
         # /tmp/d20160121-5693-jk5fag/solution.rb:24:in `[]'
         # /tmp/d20160121-5693-jk5fag/spec.rb:195:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-jk5fag/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-jk5fag/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)>'

  8) 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 at least 2, got 1> with backtrace:
         # /tmp/d20160121-5693-jk5fag/solution.rb:135:in `check_arguments'
         # /tmp/d20160121-5693-jk5fag/solution.rb:124:in `calculate'
         # /tmp/d20160121-5693-jk5fag/solution.rb:84:in `parse_formula'
         # /tmp/d20160121-5693-jk5fag/solution.rb:102:in `calculate_expression'
         # /tmp/d20160121-5693-jk5fag/solution.rb:24:in `[]'
         # /tmp/d20160121-5693-jk5fag/spec.rb:219:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-jk5fag/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-jk5fag/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)>'

  9) 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'
       
       expected: "1"
            got: "0"
       
       (compared using ==)
     # /tmp/d20160121-5693-jk5fag/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)>'

  10) Spreadsheet#[] raises an exception for missing cells passed as function arguments
     Failure/Error: expect { Spreadsheet.new('=ADD(1, B4)  100')['A1'] }.to raise_error(
       expected Spreadsheet::Error with message matching /Cell 'B4' does not exist/, got #<NoMethodError: undefined method `strip' for nil:NilClass> with backtrace:
         # /tmp/d20160121-5693-jk5fag/solution.rb:73:in `block in extract_args'
         # /tmp/d20160121-5693-jk5fag/solution.rb:69:in `map'
         # /tmp/d20160121-5693-jk5fag/solution.rb:69:in `extract_args'
         # /tmp/d20160121-5693-jk5fag/solution.rb:84:in `parse_formula'
         # /tmp/d20160121-5693-jk5fag/solution.rb:102:in `calculate_expression'
         # /tmp/d20160121-5693-jk5fag/solution.rb:24:in `[]'
         # /tmp/d20160121-5693-jk5fag/spec.rb:260:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-jk5fag/spec.rb:260: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-jk5fag/spec.rb:260: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 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-jk5fag/solution.rb:104:in `calculate_expression'
         # /tmp/d20160121-5693-jk5fag/solution.rb:24:in `[]'
         # /tmp/d20160121-5693-jk5fag/spec.rb:266:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-jk5fag/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-jk5fag/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.03246 seconds
40 examples, 11 failures

Failed examples:

rspec /tmp/d20160121-5693-jk5fag/spec.rb:9 # Spreadsheet#new creates a blank sheet when no arguments are passed
rspec /tmp/d20160121-5693-jk5fag/spec.rb:23 # Spreadsheet#to_s returns blank tables as blank strings
rspec /tmp/d20160121-5693-jk5fag/spec.rb:74 # Spreadsheet#[] raises an exception for non-existant cells
rspec /tmp/d20160121-5693-jk5fag/spec.rb:118 # Spreadsheet#[] raises an exception for less than two arguments passed to ADD
rspec /tmp/d20160121-5693-jk5fag/spec.rb:148 # Spreadsheet#[] raises an exception for less than two arguments to MULTIPLY
rspec /tmp/d20160121-5693-jk5fag/spec.rb:170 # Spreadsheet#[] raises an exception when SUBTRACT is called with a wrong number of arguments
rspec /tmp/d20160121-5693-jk5fag/spec.rb:194 # Spreadsheet#[] raises an exception when DIVIDE is called with a wrong number of arguments
rspec /tmp/d20160121-5693-jk5fag/spec.rb:218 # Spreadsheet#[] raises an exception when MOD is called with a wrong number of arguments
rspec /tmp/d20160121-5693-jk5fag/spec.rb:249 # Spreadsheet#[] evaluates deeply-nested cell references
rspec /tmp/d20160121-5693-jk5fag/spec.rb:259 # Spreadsheet#[] raises an exception for missing cells passed as function arguments
rspec /tmp/d20160121-5693-jk5fag/spec.rb:265 # Spreadsheet#[] raises an exception for invalid expressions

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

Анджелин обнови решението на 09.01.2016 00:08 (преди над 8 години)

+class Spreadsheet
+ class Error < StandardError
+ end
+
+ def initialize(sheet = nil)
+ return unless sheet
+ @cells = []
+ SheetUtilities.parse_sheet(@cells, sheet)
+ end
+
+ def empty?
+ @cells.empty?
+ end
+
+ def cell_at(cell_index)
+ cell = get_by_cell_index(cell_index)
+
+ raise Error, "Cell '#{cell_index}' does not exist." unless cell
+ cell
+ end
+
+ def [](cell_index)
+ calculate_expression(cell_at(cell_index))
+ end
+
+ def to_s
+ tab = ""
+ @cells.each do |row|
+ row.each { |cell| tab << "#{calculate_expression(cell)}\t" }
+ tab.chop!
+ tab << "\n"
+ end
+ tab.chop!
+ end
+
+ private
+
+ def calculate_expression(expression)
+ return expression if expression[0] != '='
+ expression.match(/(\w+)\(((\s*[0-9A-Z]\s*,?)+)+/)
+
+ raise Error, "Invalid expression '#{expression}'" unless $1
+ formula = Formulas.get_formula($1)
+
+ args = formula_to_args($2)
+ formula.calculate(*args).to_s
+ end
+
+ def formula_to_args(arguments)
+ arguments.split(',').map do |argument|
+ argument = get_by_cell_index(argument) if argument =~ /[A-Z]+[0-9]+/
+ argument = argument.strip.to_i
+ end
+ end
+
+ def get_by_cell_index(cell_index)
+ cell_index.scan(/([A-Z]+)([0-9]+)/)
+ raise Error, "Invalid cell index '#{cell_index}'." unless $1
+ col = SheetUtilities.parse_col($1)
+ row = $2.to_i - 1
+
+ @cells[row][col] rescue nil
+ end
+end
+
+class SheetUtilities
+ def self.parse_sheet(cells, sheet)
+ sheet.strip.split("\n").each do |row|
+ next if row.empty?
+ delimiter = /#{Regexp.escape(row.include?("\t") ? "\t" : " ")}+/
+
+ current = []
+ row.strip.split(delimiter).each { |cell| current << cell.strip }
+ cells << current
+ end
+ end
+
+ def self.parse_col(col)
+ index = 0
+
+ if col.size > 1
+ col[0..col.size - 2].each_char do |c|
+ index += (c.ord - ('A'.ord - 1)) * ('Z'.ord - 'A'.ord + 1)
+ end
+ end
+
+ index += col[col.size - 1].ord - ('A'.ord - 1)
+ index.to_i - 1
+ end
+end
+
+module Formulas
+ def self.get_formula(formula)
+ object = const_get(formula.downcase.capitalize).new rescue nil
+ raise Spreadsheet::Error, "Unknown formula #{formula}" unless object
+ object
+ end
+
+ class Formula
+ LESS = "Wrong number of arguments for 'FOO': expected at least %s, got %s"
+ MORE = "Wrong number of arguments for 'FOO': expected %s, got %s"
+
+ attr_accessor :arguments_count
+
+ def calculate(*args)
+ raise StandardError, 'base class method should not be called'
+ end
+
+ def check_arguments(args)
+ if args.count < @arguments_count
+ raise Spreadsheet::Error, LESS % [@arguments_count, args.count]
+ end
+
+ if args.count > @arguments_count
+ raise Spreadsheet::Error, MORE % [@arguments_count, args.count]
+ end
+ end
+ end
+
+ class Add < Formula
+ def initialize
+ @arguments_count = 0
+ end
+
+ def calculate(*args)
+ p args
+ args.reduce { |a, b| a + b }
+ end
+ end
+
+ class Multiply < Formula
+ def initialize
+ @arguments_count = 0
+ end
+
+ def calculate(*args)
+ args.reduce { |a, b| a * b }
+ end
+ end
+
+ class Subtract < Formula
+ def initialize
+ @arguments_count = 2
+ end
+
+ def calculate(*args)
+ check_arguments(args)
+ args.first - args.last
+ end
+ end
+
+ class Divide < Formula
+ def initialize
+ @arguments_count = 2
+ end
+
+ def calculate(*args)
+ check_arguments(args)
+ args.first.to_f / args.last
+ end
+ end
+
+ class Mod < Formula
+ def initialize
+ @arguments_count = 2
+ end
+
+ def calculate(*args)
+ check_arguments(args)
+ args.first % args.last
+ end
+ end
+end

Анджелин обнови решението на 09.01.2016 15:59 (преди над 8 години)

class Spreadsheet
class Error < StandardError
end
def initialize(sheet = nil)
return unless sheet
@cells = []
- SheetUtilities.parse_sheet(@cells, sheet)
+ @utilities = SheetUtilities.new(@cells)
+ @utilities.parse_sheet(sheet)
end
def empty?
@cells.empty?
end
def cell_at(cell_index)
- cell = get_by_cell_index(cell_index)
+ cell = @utilities.get_by_cell_index(cell_index)
raise Error, "Cell '#{cell_index}' does not exist." unless cell
- cell
+ cell.to_s
end
def [](cell_index)
- calculate_expression(cell_at(cell_index))
+ @utilities.calculate_expression(cell_at(cell_index))
end
def to_s
tab = ""
@cells.each do |row|
- row.each { |cell| tab << "#{calculate_expression(cell)}\t" }
+ row.each { |cell| tab << "#{@utilities.calculate_expression(cell)}\t" }
+
tab.chop!
tab << "\n"
end
tab.chop!
end
-
- private
-
- def calculate_expression(expression)
- return expression if expression[0] != '='
- expression.match(/(\w+)\(((\s*[0-9A-Z]\s*,?)+)+/)
-
- raise Error, "Invalid expression '#{expression}'" unless $1
- formula = Formulas.get_formula($1)
-
- args = formula_to_args($2)
- formula.calculate(*args).to_s
- end
-
- def formula_to_args(arguments)
- arguments.split(',').map do |argument|
- argument = get_by_cell_index(argument) if argument =~ /[A-Z]+[0-9]+/
- argument = argument.strip.to_i
- end
- end
-
- def get_by_cell_index(cell_index)
- cell_index.scan(/([A-Z]+)([0-9]+)/)
- raise Error, "Invalid cell index '#{cell_index}'." unless $1
- col = SheetUtilities.parse_col($1)
- row = $2.to_i - 1
-
- @cells[row][col] rescue nil
- end
end
class SheetUtilities
- def self.parse_sheet(cells, sheet)
+ def initialize(cells)
+ @cells = cells
+ end
+
+ def parse_sheet(sheet)
sheet.strip.split("\n").each do |row|
next if row.empty?
delimiter = /#{Regexp.escape(row.include?("\t") ? "\t" : " ")}+/
current = []
row.strip.split(delimiter).each { |cell| current << cell.strip }
- cells << current
+ @cells << current
end
end
- def self.parse_col(col)
+ def parse_col(col)
index = 0
if col.size > 1
col[0..col.size - 2].each_char do |c|
index += (c.ord - ('A'.ord - 1)) * ('Z'.ord - 'A'.ord + 1)
end
end
index += col[col.size - 1].ord - ('A'.ord - 1)
index.to_i - 1
end
+
+ def extract_args(arguments)
+ arguments.split(',').map do |argument|
+ if argument =~ /[A-Z]+[0-9]+/
+ argument = get_by_cell_index(argument)
+ end
+ argument = argument.strip.to_f
+ end
+ end
+
+ def parse_formula(expression)
+ return $1 if expression.match(/^\=([-+]?\d+\.?\d*)+$/)
+ if expression.match(/^\=(\w+\d+)$/)
+ return calculate_expression(get_by_cell_index($1))
+ end
+
+ if expression.match(/(\w+)\(((\s*[-+]?[0-9A-Z]\.?\s*,?)+)+\)/)
+ return Formulas.get_formula($1).calculate(*extract_args($2))
+ end
+ false
+ end
+
+ def get_by_cell_index(cell_index)
+ cell_index.scan(/([A-Z]+)([0-9]+)/)
+ raise Spreadsheet::Error, "Invalid cell index '#{cell_index}'." unless $1
+
+ col = parse_col($1)
+ row = $2.to_i - 1
+
+ @cells[row][col] rescue nil
+ end
+
+ def calculate_expression(expression)
+ return expression if expression[0] != '='
+
+ calculation = parse_formula(expression)
+ unless calculation
+ raise Spreadsheet::Error, "Invalid expression '#{expression}'"
+ end
+ calculation.to_s
+ end
end
module Formulas
def self.get_formula(formula)
object = const_get(formula.downcase.capitalize).new rescue nil
- raise Spreadsheet::Error, "Unknown formula #{formula}" unless object
+ raise Spreadsheet::Error, "Unknown function '#{formula}'" unless object
object
end
class Formula
LESS = "Wrong number of arguments for 'FOO': expected at least %s, got %s"
MORE = "Wrong number of arguments for 'FOO': expected %s, got %s"
attr_accessor :arguments_count
def calculate(*args)
+ check_arguments(args) unless arguments_count == 0
+ calculation = algorithm(*args).to_f
+ (calculation % 1 == 0.0) ? calculation.to_i : format('%.2f', calculation)
+ end
+
+ def algorithm(*args)
raise StandardError, 'base class method should not be called'
end
def check_arguments(args)
if args.count < @arguments_count
raise Spreadsheet::Error, LESS % [@arguments_count, args.count]
end
if args.count > @arguments_count
raise Spreadsheet::Error, MORE % [@arguments_count, args.count]
end
end
end
class Add < Formula
def initialize
@arguments_count = 0
end
- def calculate(*args)
- p args
+ def algorithm(*args)
args.reduce { |a, b| a + b }
end
end
class Multiply < Formula
def initialize
@arguments_count = 0
end
- def calculate(*args)
+ def algorithm(*args)
args.reduce { |a, b| a * b }
end
end
class Subtract < Formula
def initialize
@arguments_count = 2
end
- def calculate(*args)
- check_arguments(args)
+ def algorithm(*args)
args.first - args.last
end
end
class Divide < Formula
def initialize
@arguments_count = 2
end
- def calculate(*args)
- check_arguments(args)
- args.first.to_f / args.last
+ def algorithm(*args)
+ args.first / args.last
end
end
class Mod < Formula
def initialize
@arguments_count = 2
end
- def calculate(*args)
- check_arguments(args)
+ def algorithm(*args)
args.first % args.last
end
end
end