Решение на Осма задача от Станимира Влаева

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

Към профила на Станимира Влаева

Резултати

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

Код

class Spreadsheet
class Error < StandardError
class InvalidCellIndexError
end
end
def initialize(table)
split_to_cells(table)
end
def empty?
@cells.length == 0
end
def cell_at(index)
cell = @cells.find { |cell| cell.at?(index) }
raise Error, "Cell '#{index}' does not exist" if cell.nil?
cell.content
end
def [](index)
cell = @cells.find { |cell| cell.at?(index) }
raise Error, "Cell '#{index}' does not exist" if cell.nil?
cell.value
end
def to_s
table = ''
@rows.each_with_index do |row, index|
table << row_to_s(row)
table << "\n" unless index == @rows.length - 1
end
table
end
private
def row_to_s(row)
table = ''
row.each_with_index do |cell, index|
table << cell.value
table << "\t" unless index == row.length - 1
end
table
end
def split_to_cells(table)
@rows = []
table.strip.
split(/\n/).
each_with_index { |row_data, index| @rows << new_row(row_data, index) }
@cells = @rows.flatten
end
def new_row(data, index)
data.strip.
split(/\t| /).
map.
with_index do |cell_content, column_index|
address = Address.new(index + 1, column_index + 1)
Cell.new(self, address, cell_content.strip)
end
end
class Cell
attr_reader :content
def initialize(parent, address, content)
@parent = parent
@address = address
@content = content
end
def value
calculate_value @content
end
def at?(index)
@address.index == index
end
private
def calculate_value(content)
formula = Formula.new(content, @parent)
formula.value
end
end
class Address
KEYS_COUNT = 26
def initialize(*args)
if args.length == 2
@row = args.first.to_i
@column = args.last.to_i
elsif args.length == 1
@index = args.first
end
end
def index
@index.nil? ? column_index << @row.to_s : @index
end
def ==(index)
raise Error::InvalidCellIndexError, "Invalid cell index \
'#{index}'" unless valid?(index)
self.index == index
end
def valid?(index = @index)
parts = index.split(/(\d+)/)
parts.length == 2 and valid_column? parts.first and valid_row? parts.last
end
private
def valid_column?(column_index)
column_index.
each_char.
all? { |ch| 'A' <= ch.to_s and ch.to_s <= 'Z' }
end
def valid_row?(row)
not row.match(/^\d+$/).nil?
end
def column_index
col, index = @column, ''
while col > 0
letter_index = (col / KEYS_COUNT > 0) ? (col / KEYS_COUNT) : col
index << column_key(letter_index)
col -= letter_index * KEYS_COUNT
end
index
end
# Get the nth letter in the aplhabet
def column_key(n)
('A'.ord + n - 1).chr
end
end
module Formulas
def add(*args)
check_number_of_arguments(2, args, __callee__.to_s)
format_result(args.reduce(:+))
end
def multiply(*args)
check_number_of_arguments(2, args, __callee__.to_s)
format_result(args.reduce(:*))
end
def take_out(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
format_result(args.first - args.last)
end
def divide(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
format_result(args.first / args.last)
end
def mod(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
format_result(args.first % args.last)
end
def check_number_of_arguments(required, args, formula_name)
given = args.flatten!.count
raise Error, "Wrong number of arguments for '#{formula_name}':\
expected at least #{required}, got #{given}" unless given >= required
end
def check_exact_number_of_arguments(required, args, formula_name)
given = args.flatten!.count
raise Error, "Wrong number of arguments for '#{formula_name}':\
expected #{required}, got #{given}" unless given == required
end
def format_result(number)
number == number.to_i ? number.to_i.to_s : ('%.2f' % number)
end
end
class Formula
FORMULA_NAME_START_INDEX = 1
include Formulas
def initialize(content, table)
@content = content
@table = table
end
def value
if formula?
address = Address.new(@content[1..-1])
address.valid? ? @table[address.index] : self.send(method_name, args)
else
@content
end
rescue NoMethodError
raise Error, "Unknown function '#{name}'"
end
private
def formula?
@content[0] == '='
end
def method_name
case name
when 'ADD' then 'add'
when 'MULTIPLY' then 'multiply'
when 'SUBSTRACT' then 'take_out'
when 'DIVIDE' then 'divide'
when 'MOD' then 'mod'
end
end
def name
name_end_index = @content.index('(')
raise Error, "Invalid expression '#{@content}'" if name_end_index.nil?
@content.slice(FORMULA_NAME_START_INDEX..(name_end_index - 1)).
to_s
end
def args
name_end_index = @content.index('(')
args = []
@content.slice((name_end_index + 1)..-2).
split(',').
map { |argument| args << parse(argument) }
args
end
def parse(argument)
argument.strip!
result = argument_to_float(argument, argument.split('.'))
begin
result = @table[argument].to_f if result.nil?
rescue Error::InvalidCellIndexError
raise Error, "Invalid expression '#{@content}'"
end
result.nil? ? raise(Error, "Invalid expression '#{@content}'") : result
end
def argument_to_float(argument, parts)
if parts.length == 1
argument.to_f if parts.first.match(/^\d+$/)
elsif parts.length == 2
characteristic = parts.first.match(/^\d+$/)
mantissa = parts.last.match(/^\d+$/)
argument.to_f if (characteristic and mantissa)
end
end
end
end

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

FF..F...F...F.....F...FFFF..F..F.F...F.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-1xgk6kg/solution.rb:8:in `initialize'
     # /tmp/d20160121-5693-1xgk6kg/spec.rb:4:in `new'
     # /tmp/d20160121-5693-1xgk6kg/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-1xgk6kg/solution.rb:8:in `initialize'
     # /tmp/d20160121-5693-1xgk6kg/spec.rb:10:in `new'
     # /tmp/d20160121-5693-1xgk6kg/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-1xgk6kg/solution.rb:8:in `initialize'
     # /tmp/d20160121-5693-1xgk6kg/spec.rb:24:in `new'
     # /tmp/d20160121-5693-1xgk6kg/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#to_s splits cells by two or more spaces
     Failure/Error: expect(Spreadsheet.new("foo  bar   42\nbaz    larodi  100").to_s).to eq "foo\tbar\t42\nbaz\tlarodi\t100"
       
       expected: "foo\tbar\t42\nbaz\tlarodi\t100"
            got: "foo\tbar\t42\nbaz\t\tlarodi\t100"
       
       (compared using ==)
       
       Diff:
       @@ -1,3 +1,3 @@
        foo	bar	42
       -baz	larodi	100
       +baz		larodi	100
     # /tmp/d20160121-5693-1xgk6kg/spec.rb:40: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 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-1xgk6kg/solution.rb:8:in `initialize'
         # /tmp/d20160121-5693-1xgk6kg/spec.rb:75:in `new'
         # /tmp/d20160121-5693-1xgk6kg/spec.rb:75:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1xgk6kg/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-1xgk6kg/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)>'

  6) 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 'add':   expected at least 2, got 1> with backtrace:
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:175:in `check_number_of_arguments'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:149:in `add'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:203:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:87:in `calculate_value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:76:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:25:in `[]'
         # /tmp/d20160121-5693-1xgk6kg/spec.rb:119:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1xgk6kg/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-1xgk6kg/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)>'

  7) 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 'multiply':   expected at least 2, got 1> with backtrace:
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:175:in `check_number_of_arguments'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:154:in `multiply'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:203:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:87:in `calculate_value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:76:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:25:in `[]'
         # /tmp/d20160121-5693-1xgk6kg/spec.rb:149:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1xgk6kg/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-1xgk6kg/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)>'

  8) Spreadsheet#[] subtracts two numbers with SUBTRACT
     Failure/Error: expect(sheet['A1']).to eq('2')
     TypeError:
       nil is not a symbol nor a string
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:203:in `value'
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:87:in `calculate_value'
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:76:in `value'
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:25:in `[]'
     # /tmp/d20160121-5693-1xgk6kg/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)>'

  9) Spreadsheet#[] subtracts numbers via cell references
     Failure/Error: expect(sheet['D1']).to eq('4')
     TypeError:
       nil is not a symbol nor a string
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:203:in `value'
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:87:in `calculate_value'
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:76:in `value'
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:25:in `[]'
     # /tmp/d20160121-5693-1xgk6kg/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)>'

  10) 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 #<TypeError: nil is not a symbol nor a string> with backtrace:
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:203:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:87:in `calculate_value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:76:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:25:in `[]'
         # /tmp/d20160121-5693-1xgk6kg/spec.rb:171:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1xgk6kg/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-1xgk6kg/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)>'

  11) 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 'divide':   expected 2, got 1> with backtrace:
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:181:in `check_exact_number_of_arguments'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:164:in `divide'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:203:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:87:in `calculate_value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:76:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:25:in `[]'
         # /tmp/d20160121-5693-1xgk6kg/spec.rb:195:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1xgk6kg/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-1xgk6kg/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)>'

  12) 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 'mod':   expected 2, got 1> with backtrace:
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:181:in `check_exact_number_of_arguments'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:169:in `mod'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:203:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:87:in `calculate_value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:76:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:25:in `[]'
         # /tmp/d20160121-5693-1xgk6kg/spec.rb:219:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1xgk6kg/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-1xgk6kg/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)>'

  13) Spreadsheet#[] subtracts floating point numbers with SUBTRACT
     Failure/Error: expect(Spreadsheet.new('10  =SUBTRACT(A1, 1.1)')['B1']).to eq '8.90'
     TypeError:
       nil is not a symbol nor a string
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:203:in `value'
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:87:in `calculate_value'
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:76:in `value'
     # /tmp/d20160121-5693-1xgk6kg/solution.rb:25:in `[]'
     # /tmp/d20160121-5693-1xgk6kg/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)>'

  14) Spreadsheet#[] raises an exception for unknown functions
     Failure/Error: expect { Spreadsheet.new('=FOO(42)  100')['A1'] }.to raise_error(
       expected Spreadsheet::Error with message matching /Unknown function 'FOO'/, got #<TypeError: nil is not a symbol nor a string> with backtrace:
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:203:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:87:in `calculate_value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:76:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:25:in `[]'
         # /tmp/d20160121-5693-1xgk6kg/spec.rb:254:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1xgk6kg/spec.rb:254: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-1xgk6kg/spec.rb:254: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#[] 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-1xgk6kg/solution.rb:229:in `name'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:218:in `method_name'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:203:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:87:in `calculate_value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:76:in `value'
         # /tmp/d20160121-5693-1xgk6kg/solution.rb:25:in `[]'
         # /tmp/d20160121-5693-1xgk6kg/spec.rb:266:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-1xgk6kg/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-1xgk6kg/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.0375 seconds
40 examples, 15 failures

Failed examples:

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

История (4 версии и 3 коментара)

Станимира обнови решението на 08.01.2016 11:23 (преди над 8 години)

+class Spreadsheet
+
+ class Error < StandardError
+ class InvalidCellIndexError
+ end
+ end
+
+ def initialize(table)
+ split_to_cells(table)
+ end
+
+ def empty?
+ @cells.length == 0
+ end
+
+ def cell_at(index)
+ cell = @cells.find { |cell| cell.at?(index) }
+ raise Error, "Cell '#{index}' does not exist" if cell.nil?
+ cell.content
+ end
+
+ def [](index)
+ cell = @cells.find { |cell| cell.at?(index) }
+ raise Error, "Cell '#{index}' does not exist" if cell.nil?
+ cell.value
+ end
+
+ def to_s
+ table = ''
+ @rows.each_with_index do |row, index|
+ table << row_to_s(row)
+ table << "\n" unless index == @rows.length - 1
+ end
+ table
+ end
+
+ private
+
+ def row_to_s(row)
+ table = ''
+ row.each_with_index do |cell, index|
+ table << cell.value
+ table << "\t" unless index == row.length - 1
+ end
+ table
+ end
+
+ def split_to_cells(table)
+ @rows = []
+ table.strip.
+ split(/\n/).
+ each_with_index { |row_data, index| @rows << new_row(row_data, index) }
+
+ @cells = @rows.flatten
+ end
+
+ def new_row(data, index)
+ data.strip.
+ split(/\t| /).
+ map.
+ with_index do |cell_content, column_index|
+ address = Address.new(index + 1, column_index + 1)
+ Cell.new(self, address, cell_content.strip)
+ end
+ end
+
+ class Cell
+ def initialize(parent, address, content)
+ @parent = parent
+ @address = address
+ @content = content
+ end
+
+ def value
+ calculate_value @content
+ end
+
+ def at?(index)
+ @address.index == index
+ end
+
+ private
+
+ def calculate_value(content)
+ formula = Formula.new(content, @parent)
+ formula.value
+ end
+ end
+
+ class Address
+ KEYS_COUNT = 26
+
+ def initialize(row, column)
+ @row = row
+ @column = column
+ end
+
+ def index
+ column_index << @row.to_s
+ end
+
+ def ==(index)
+ raise Error::InvalidCellIndexError, "Invalid cell index \
+'#{index}'" unless valid?(index)
+ self.index == index
+ end
+
+ private
+
+ def valid?(index)
+ parts = index.split(/(\d+)/)
+ parts.length == 2 and valid_column? parts.first and valid_row? parts.last
+ end
+
+ def valid_column?(column_index)
+ column_index.
+ each_char.
+ all? { |ch| 'A' <= ch.to_s and ch.to_s <= 'Z' }
+ end
+
+ def valid_row?(row)
+ not row.match(/^\d+$/).nil?
+ end
+
+ def column_index
+ col, index = @column, ''
+
+ while col > 0
+ letter_index = (col / KEYS_COUNT > 0) ? (col / KEYS_COUNT) : col
+ index << column_key(letter_index)
+ col -= letter_index * KEYS_COUNT
+ end
+ index
+ end
+
+ # Get the nth letter in the aplhabet
+ def column_key(n)
+ ('A'.ord + n - 1).chr
+ end
+ end
+
+ module Formulas
+ def add(*args)
+ check_number_of_arguments(2, args, __callee__.to_s)
+ args.reduce(:+)
+ end
+
+ def multiply(*args)
+ check_number_of_arguments(2, args, __callee__.to_s)
+ args.reduce(:*)
+ end
+
+ def take_out(*args)
+ check_exact_number_of_arguments(2, args, __callee__.to_s)
+ args.first - args.last
+ end
+
+ def divide(*args)
+ check_exact_number_of_arguments(2, args, __callee__.to_s)
+ args.first / args.last
+ end
+
+ def mod(*args)
+ check_exact_number_of_arguments(2, args, __callee__.to_s)
+ args.first % args.last
+ end
+
+ def check_number_of_arguments(required, args, formula_name)
+ given = args.flatten!.count
+ raise Error, "Wrong number of arguments for '#{formula_name}':\
+ expected at least #{required}, got #{given}" unless given >= required
+ end
+
+ def check_exact_number_of_arguments(required, args, formula_name)
+ given = args.flatten!.count
+ raise Error, "Wrong number of arguments for '#{formula_name}':\
+ expected #{required}, got #{given}" unless given == required
+ end
+ end
+
+ class Formula
+ FORMULA_NAME_START_INDEX = 1
+
+ include Formulas
+
+ def initialize(content, spreadsheet)
+ @content = content
+ @spreadsheet = spreadsheet
+ end
+
+ def value
+ formula? ? format_result(self.send(method_name, arguments)) : @content
+ rescue NoMethodError
+ raise Error, "Unknown function '#{name}'"
+ end
+
+ private
+
+ def formula?
+ @content[0] == '='
+ end
+
+ def format_result(number)
+ number == number.to_i ? number.to_i.to_s : ('%.2f' % number)
+ end
+
+ def method_name
+ case name
+ when 'ADD' then 'add'
+ when 'MULTIPLY' then 'multiply'
+ when 'SUBSTRACT' then 'take_out'
+ when 'DIVIDE' then 'divide'
+ when 'MOD' then 'mod'
+ end
+ end
+
+ def name
+ name_end_index = @content.index('(')
+ raise Error, "Invalid expression '#{@content}'" if name_end_index.nil?
+ @content.slice(FORMULA_NAME_START_INDEX..(name_end_index - 1)).
+ to_s
+ end
+
+ def arguments
+ name_end_index = @content.index('(')
+ args = []
+ @content.slice((name_end_index + 1)..-2).
+ split(',').
+ map { |argument| args << parse(argument) }
+
+ args
+ end
+
+ def parse(argument)
+ argument.strip!
+ result = argument.to_f if argument.match(/^\d+$/)
+ begin
+ result = Spreadsheet[argument] if result.nil?
+ rescue Error::InvalidCellIndexError
+ raise Error, "Invalid expression '#{@content}'"
+ end
+ result.nil? ? raise(Error, "Invalid expression '#{@content}'") : result
+ end
+ end
+end

Станимира обнови решението на 09.01.2016 12:15 (преди над 8 години)

class Spreadsheet
class Error < StandardError
class InvalidCellIndexError
end
end
def initialize(table)
split_to_cells(table)
end
def empty?
@cells.length == 0
end
def cell_at(index)
cell = @cells.find { |cell| cell.at?(index) }
raise Error, "Cell '#{index}' does not exist" if cell.nil?
cell.content
end
def [](index)
cell = @cells.find { |cell| cell.at?(index) }
raise Error, "Cell '#{index}' does not exist" if cell.nil?
cell.value
end
def to_s
table = ''
@rows.each_with_index do |row, index|
table << row_to_s(row)
table << "\n" unless index == @rows.length - 1
end
table
end
private
def row_to_s(row)
table = ''
row.each_with_index do |cell, index|
table << cell.value
table << "\t" unless index == row.length - 1
end
table
end
def split_to_cells(table)
@rows = []
table.strip.
split(/\n/).
each_with_index { |row_data, index| @rows << new_row(row_data, index) }
@cells = @rows.flatten
end
def new_row(data, index)
data.strip.
split(/\t| /).
map.
with_index do |cell_content, column_index|
address = Address.new(index + 1, column_index + 1)
Cell.new(self, address, cell_content.strip)
end
end
class Cell
def initialize(parent, address, content)
@parent = parent
@address = address
@content = content
end
def value
calculate_value @content
end
def at?(index)
@address.index == index
end
private
def calculate_value(content)
formula = Formula.new(content, @parent)
formula.value
end
end
class Address
KEYS_COUNT = 26
def initialize(row, column)
@row = row
@column = column
end
def index
column_index << @row.to_s
end
def ==(index)
raise Error::InvalidCellIndexError, "Invalid cell index \
'#{index}'" unless valid?(index)
self.index == index
end
private
def valid?(index)
parts = index.split(/(\d+)/)
parts.length == 2 and valid_column? parts.first and valid_row? parts.last
end
def valid_column?(column_index)
column_index.
each_char.
all? { |ch| 'A' <= ch.to_s and ch.to_s <= 'Z' }
end
def valid_row?(row)
not row.match(/^\d+$/).nil?
end
def column_index
col, index = @column, ''
while col > 0
letter_index = (col / KEYS_COUNT > 0) ? (col / KEYS_COUNT) : col
index << column_key(letter_index)
col -= letter_index * KEYS_COUNT
end
index
end
# Get the nth letter in the aplhabet
def column_key(n)
('A'.ord + n - 1).chr
end
end
module Formulas
def add(*args)
check_number_of_arguments(2, args, __callee__.to_s)
args.reduce(:+)
end
def multiply(*args)
check_number_of_arguments(2, args, __callee__.to_s)
args.reduce(:*)
end
def take_out(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
args.first - args.last
end
def divide(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
args.first / args.last
end
def mod(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
args.first % args.last
end
def check_number_of_arguments(required, args, formula_name)
given = args.flatten!.count
raise Error, "Wrong number of arguments for '#{formula_name}':\
expected at least #{required}, got #{given}" unless given >= required
end
def check_exact_number_of_arguments(required, args, formula_name)
given = args.flatten!.count
raise Error, "Wrong number of arguments for '#{formula_name}':\
expected #{required}, got #{given}" unless given == required
end
end
class Formula
FORMULA_NAME_START_INDEX = 1
include Formulas
def initialize(content, spreadsheet)
@content = content
@spreadsheet = spreadsheet
end
def value
formula? ? format_result(self.send(method_name, arguments)) : @content
rescue NoMethodError
raise Error, "Unknown function '#{name}'"
end
private
def formula?
@content[0] == '='
end
def format_result(number)
number == number.to_i ? number.to_i.to_s : ('%.2f' % number)
end
def method_name
case name
when 'ADD' then 'add'
when 'MULTIPLY' then 'multiply'
when 'SUBSTRACT' then 'take_out'
when 'DIVIDE' then 'divide'
when 'MOD' then 'mod'
end
end
def name
name_end_index = @content.index('(')
raise Error, "Invalid expression '#{@content}'" if name_end_index.nil?
@content.slice(FORMULA_NAME_START_INDEX..(name_end_index - 1)).
to_s
end
def arguments
name_end_index = @content.index('(')
args = []
@content.slice((name_end_index + 1)..-2).
split(',').
map { |argument| args << parse(argument) }
args
end
def parse(argument)
argument.strip!
result = argument.to_f if argument.match(/^\d+$/)
begin
- result = Spreadsheet[argument] if result.nil?
+ result = spreadsheet[argument] if result.nil?
rescue Error::InvalidCellIndexError
raise Error, "Invalid expression '#{@content}'"
end
result.nil? ? raise(Error, "Invalid expression '#{@content}'") : result
end
end
end

Станимира обнови решението на 09.01.2016 18:20 (преди над 8 години)

class Spreadsheet
class Error < StandardError
class InvalidCellIndexError
end
end
def initialize(table)
split_to_cells(table)
end
def empty?
@cells.length == 0
end
def cell_at(index)
cell = @cells.find { |cell| cell.at?(index) }
raise Error, "Cell '#{index}' does not exist" if cell.nil?
cell.content
end
def [](index)
cell = @cells.find { |cell| cell.at?(index) }
raise Error, "Cell '#{index}' does not exist" if cell.nil?
cell.value
end
def to_s
table = ''
@rows.each_with_index do |row, index|
table << row_to_s(row)
table << "\n" unless index == @rows.length - 1
end
table
end
private
def row_to_s(row)
table = ''
row.each_with_index do |cell, index|
table << cell.value
table << "\t" unless index == row.length - 1
end
table
end
def split_to_cells(table)
@rows = []
table.strip.
split(/\n/).
each_with_index { |row_data, index| @rows << new_row(row_data, index) }
@cells = @rows.flatten
end
def new_row(data, index)
data.strip.
split(/\t| /).
map.
with_index do |cell_content, column_index|
address = Address.new(index + 1, column_index + 1)
Cell.new(self, address, cell_content.strip)
end
end
class Cell
def initialize(parent, address, content)
@parent = parent
@address = address
@content = content
end
def value
calculate_value @content
end
def at?(index)
@address.index == index
end
private
def calculate_value(content)
formula = Formula.new(content, @parent)
formula.value
end
end
class Address
KEYS_COUNT = 26
- def initialize(row, column)
- @row = row
- @column = column
+ def initialize(*args)
+ if args.length == 2
+ @row = args.first.to_i
+ @column = args.last.to_i
+ elsif args.length == 1
+ @index = args.first
+ end
end
def index
- column_index << @row.to_s
+ @index.nil? ? column_index << @row.to_s : @index
end
def ==(index)
raise Error::InvalidCellIndexError, "Invalid cell index \
'#{index}'" unless valid?(index)
self.index == index
end
- private
-
- def valid?(index)
+ def valid?(index = @index)
parts = index.split(/(\d+)/)
parts.length == 2 and valid_column? parts.first and valid_row? parts.last
end
+ private
+
def valid_column?(column_index)
column_index.
each_char.
all? { |ch| 'A' <= ch.to_s and ch.to_s <= 'Z' }
end
def valid_row?(row)
not row.match(/^\d+$/).nil?
end
def column_index
col, index = @column, ''
while col > 0
letter_index = (col / KEYS_COUNT > 0) ? (col / KEYS_COUNT) : col
index << column_key(letter_index)
col -= letter_index * KEYS_COUNT
end
index
end
# Get the nth letter in the aplhabet
def column_key(n)
('A'.ord + n - 1).chr
end
end
module Formulas
def add(*args)
check_number_of_arguments(2, args, __callee__.to_s)
- args.reduce(:+)
+ format_result(args.reduce(:+))
end
def multiply(*args)
check_number_of_arguments(2, args, __callee__.to_s)
- args.reduce(:*)
+ format_result(args.reduce(:*))
end
def take_out(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
- args.first - args.last
+ format_result(args.first - args.last)
end
def divide(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
- args.first / args.last
+ format_result(args.first / args.last)
end
def mod(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
- args.first % args.last
+ format_result(args.first % args.last)
end
def check_number_of_arguments(required, args, formula_name)
given = args.flatten!.count
raise Error, "Wrong number of arguments for '#{formula_name}':\
expected at least #{required}, got #{given}" unless given >= required
end
def check_exact_number_of_arguments(required, args, formula_name)
given = args.flatten!.count
raise Error, "Wrong number of arguments for '#{formula_name}':\
expected #{required}, got #{given}" unless given == required
end
+
+ def format_result(number)
+ number == number.to_i ? number.to_i.to_s : ('%.2f' % number)
+ end
end
class Formula
FORMULA_NAME_START_INDEX = 1
include Formulas
- def initialize(content, spreadsheet)
+ def initialize(content, table)
@content = content
- @spreadsheet = spreadsheet
+ @table = table
end
def value
- formula? ? format_result(self.send(method_name, arguments)) : @content
+ if formula?
+ address = Address.new(@content[1..-1])
+ address.valid? ? @table[address.index] : self.send(method_name, args)
+ else
+ @content
+ end
rescue NoMethodError
raise Error, "Unknown function '#{name}'"
end
private
def formula?
@content[0] == '='
end
- def format_result(number)
- number == number.to_i ? number.to_i.to_s : ('%.2f' % number)
- end
-
def method_name
case name
when 'ADD' then 'add'
when 'MULTIPLY' then 'multiply'
when 'SUBSTRACT' then 'take_out'
when 'DIVIDE' then 'divide'
when 'MOD' then 'mod'
end
end
def name
name_end_index = @content.index('(')
raise Error, "Invalid expression '#{@content}'" if name_end_index.nil?
@content.slice(FORMULA_NAME_START_INDEX..(name_end_index - 1)).
to_s
end
- def arguments
+ def args
name_end_index = @content.index('(')
args = []
@content.slice((name_end_index + 1)..-2).
split(',').
map { |argument| args << parse(argument) }
args
end
def parse(argument)
argument.strip!
result = argument.to_f if argument.match(/^\d+$/)
begin
- result = spreadsheet[argument] if result.nil?
+ result = @table[argument].to_f if result.nil?
rescue Error::InvalidCellIndexError
raise Error, "Invalid expression '#{@content}'"
end
result.nil? ? raise(Error, "Invalid expression '#{@content}'") : result
end
end
end

Станимира обнови решението на 10.01.2016 20:45 (преди над 8 години)

class Spreadsheet
class Error < StandardError
class InvalidCellIndexError
end
end
def initialize(table)
split_to_cells(table)
end
def empty?
@cells.length == 0
end
def cell_at(index)
cell = @cells.find { |cell| cell.at?(index) }
raise Error, "Cell '#{index}' does not exist" if cell.nil?
cell.content
end
def [](index)
cell = @cells.find { |cell| cell.at?(index) }
raise Error, "Cell '#{index}' does not exist" if cell.nil?
cell.value
end
def to_s
table = ''
@rows.each_with_index do |row, index|
table << row_to_s(row)
table << "\n" unless index == @rows.length - 1
end
table
end
private
def row_to_s(row)
table = ''
row.each_with_index do |cell, index|
table << cell.value
table << "\t" unless index == row.length - 1
end
table
end
def split_to_cells(table)
@rows = []
table.strip.
split(/\n/).
each_with_index { |row_data, index| @rows << new_row(row_data, index) }
@cells = @rows.flatten
end
def new_row(data, index)
data.strip.
split(/\t| /).
map.
with_index do |cell_content, column_index|
address = Address.new(index + 1, column_index + 1)
Cell.new(self, address, cell_content.strip)
end
end
class Cell
+ attr_reader :content
def initialize(parent, address, content)
@parent = parent
@address = address
@content = content
end
def value
calculate_value @content
end
def at?(index)
@address.index == index
end
private
def calculate_value(content)
formula = Formula.new(content, @parent)
formula.value
end
end
class Address
KEYS_COUNT = 26
def initialize(*args)
if args.length == 2
@row = args.first.to_i
@column = args.last.to_i
elsif args.length == 1
@index = args.first
end
end
def index
@index.nil? ? column_index << @row.to_s : @index
end
def ==(index)
raise Error::InvalidCellIndexError, "Invalid cell index \
'#{index}'" unless valid?(index)
self.index == index
end
def valid?(index = @index)
parts = index.split(/(\d+)/)
parts.length == 2 and valid_column? parts.first and valid_row? parts.last
end
private
def valid_column?(column_index)
column_index.
each_char.
all? { |ch| 'A' <= ch.to_s and ch.to_s <= 'Z' }
end
def valid_row?(row)
not row.match(/^\d+$/).nil?
end
def column_index
col, index = @column, ''
while col > 0
letter_index = (col / KEYS_COUNT > 0) ? (col / KEYS_COUNT) : col
index << column_key(letter_index)
col -= letter_index * KEYS_COUNT
end
index
end
# Get the nth letter in the aplhabet
def column_key(n)
('A'.ord + n - 1).chr
end
end
module Formulas
def add(*args)
check_number_of_arguments(2, args, __callee__.to_s)
format_result(args.reduce(:+))
end
def multiply(*args)
check_number_of_arguments(2, args, __callee__.to_s)
format_result(args.reduce(:*))
end
def take_out(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
format_result(args.first - args.last)
end
def divide(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
format_result(args.first / args.last)
end
def mod(*args)
check_exact_number_of_arguments(2, args, __callee__.to_s)
format_result(args.first % args.last)
end
def check_number_of_arguments(required, args, formula_name)
given = args.flatten!.count
raise Error, "Wrong number of arguments for '#{formula_name}':\
expected at least #{required}, got #{given}" unless given >= required
end
def check_exact_number_of_arguments(required, args, formula_name)
given = args.flatten!.count
raise Error, "Wrong number of arguments for '#{formula_name}':\
expected #{required}, got #{given}" unless given == required
end
def format_result(number)
number == number.to_i ? number.to_i.to_s : ('%.2f' % number)
end
end
class Formula
FORMULA_NAME_START_INDEX = 1
include Formulas
def initialize(content, table)
@content = content
@table = table
end
def value
if formula?
address = Address.new(@content[1..-1])
address.valid? ? @table[address.index] : self.send(method_name, args)
else
@content
end
rescue NoMethodError
raise Error, "Unknown function '#{name}'"
end
private
def formula?
@content[0] == '='
end
def method_name
case name
when 'ADD' then 'add'
when 'MULTIPLY' then 'multiply'
when 'SUBSTRACT' then 'take_out'
when 'DIVIDE' then 'divide'
when 'MOD' then 'mod'
end
end
def name
name_end_index = @content.index('(')
raise Error, "Invalid expression '#{@content}'" if name_end_index.nil?
@content.slice(FORMULA_NAME_START_INDEX..(name_end_index - 1)).
to_s
end
def args
name_end_index = @content.index('(')
args = []
@content.slice((name_end_index + 1)..-2).
split(',').
map { |argument| args << parse(argument) }
args
end
def parse(argument)
argument.strip!
- result = argument.to_f if argument.match(/^\d+$/)
+ result = argument_to_float(argument, argument.split('.'))
begin
result = @table[argument].to_f if result.nil?
rescue Error::InvalidCellIndexError
raise Error, "Invalid expression '#{@content}'"
end
result.nil? ? raise(Error, "Invalid expression '#{@content}'") : result
+ end
+
+ def argument_to_float(argument, parts)
+ if parts.length == 1
+ argument.to_f if parts.first.match(/^\d+$/)
+ elsif parts.length == 2
+ characteristic = parts.first.match(/^\d+$/)
+ mantissa = parts.last.match(/^\d+$/)
+ argument.to_f if (characteristic and mantissa)
+ end
end
end
end