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

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

Към профила на Мирослав Лалев

Резултати

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

Код

class Spreadsheet
attr_accessor :table
def initialize(data = "")
@table = Table.new
data.strip.split(/\n/).each { |line|
@table.add_row(line.strip.split(/\s\s+|\t/))
}
end
def empty?()
not @table.has_rows?
end
def cell_at(cell_index)
get_cell(cell_index)
end
def [](cell_index)
get_cell(cell_index, true)
end
def to_s
@table.to_s
end
class Error < Exception
end
private
def get_cell(cell_index, evaluate = false)
if match = cell_index.match(Expression::ONLY_CELL_INDEX_PATTERN)
first, second = match.captures
@table.get(first, second.to_i, evaluate)
else
raise Spreadsheet::Error, "Invalid cell index '#{cell_index}'"
end
end
end
class CellIndexFunctions
ALPHABET_LENGTH = 26
FIRST_ALPHABET_CHARACTER = 65
def initialize()
end
def get_letter_at(index)
(index + FIRST_ALPHABET_CHARACTER - 1).chr
end
def convert_to_symbols(number)
if number <= ALPHABET_LENGTH
return get_letter_at(number)
end
convert_to_symbols(Rational(number, ALPHABET_LENGTH).floor) +
get_letter_at(number % ALPHABET_LENGTH)
end
end
class Table
attr_accessor :cells
def initialize()
@cells = {}
@max_index = 0
@max_rows = 0
end
def initialize_column(index)
if @cells[CellIndexFunctions.new.convert_to_symbols(index + 1)].nil?
@cells[CellIndexFunctions.new.convert_to_symbols(index + 1)] = []
end
end
def add_row(row)
row.each.with_index { |element, index|
initialize_column(index)
if @max_index < index then @max_index = index end
@cells[CellIndexFunctions.new.convert_to_symbols(index + 1)].push(element)
}
@max_rows += 1
end
def has_rows?()
!@cells.empty?
end
def get(col, row, evaluate = false)
if @cells[col].nil? || @cells[col][row - 1].nil? || row - 1 < 0
raise Spreadsheet::Error, "Cell '#{col}#{row}' does not exist"
elsif evaluate
evaluate_cell(@cells[col][row - 1])
else
@cells[col][row - 1]
end
end
def evaluate_cell(cell)
Expression.new(self).evaluate(cell)
end
def to_s()
(0...@max_rows).reduce("") { |answer, row|
answer += (1..(@max_index + 1)).reduce(answer) { |line, index|
line += evaluate_cell(@cells[
CellIndexFunctions.new.convert_to_symbols(index)][row]).to_s + "\t"
}.strip + "\n"
}.strip
end
end
class Expression
EXPRESSION_PATTERN = /=(.*)/
RESULT_PATTERN = /(\d+)|(\d+\.\d+)/
ONLY_RESULT_PATTERN = /^#{RESULT_PATTERN}$/
CELL_INDEX_PATTERN = /([A-Z]+)([0-9]+)/
ONLY_CELL_INDEX_PATTERN = /^#{CELL_INDEX_PATTERN}$/
ARGUMENT_PATTERN = /#{RESULT_PATTERN}|#{CELL_INDEX_PATTERN}/
ARGUMENTS_PATTERN = /(?:#{ARGUMENT_PATTERN},\s?)*(?:#{ARGUMENT_PATTERN})/
FORMULA_PATTERN = /^(\w+)\((#{ARGUMENTS_PATTERN})?\)$/
def initialize(table)
@table = table
end
def evaluate(expression)
if match = expression.match(EXPRESSION_PATTERN)
assert_and_match_correct_expression(match.captures[0])
else
expression
end
end
def assert_and_match_correct_expression(expression_value)
match = expression_value.match(ONLY_RESULT_PATTERN) ||
expression_value.match(ONLY_CELL_INDEX_PATTERN) ||
expression_value.match(FORMULA_PATTERN)
if match.nil?
raise Spreadsheet::Error, "Invalid expression #{expression_value}"
else
match_pattern(expression_value)
end
end
def match_pattern(expression_value)
if match = expression_value.match(ONLY_RESULT_PATTERN)
match.captures[0] || match.captures[1]
elsif match = expression_value.match(ONLY_CELL_INDEX_PATTERN)
@table.get(match.captures[0], match.captures[1].to_i, true)
else match = expression_value.match(FORMULA_PATTERN)
assign_value(expression_value, match.captures[0], match.captures[1])
end
end
def assign_value(expression_value, formula_name, arguments)
if arguments.nil? then arguments = "" end
begin
Functions.new.send(formula_name.downcase, arguments.split(/,\s?/).map {
|argument| assert_and_match_correct_expression(argument).to_r
})
rescue NoMethodError => e
raise Spreadsheet::Error, "Unknown function '#{formula_name}'"
end
end
end
class Functions
UNKNOWN_NUMBER = "UNKNOWN_NUMBER"
CALLER = "CALLEE"
ACTUAL = "ACTUAL_AMOUNT"
EXPECTED_ARGUMENTS_ERROR_MESSAGE =
"Wrong number of arguments for #{CALLER}: expected 2, " +
"got #{ACTUAL}"
EXPECTED_AT_LEAST_ARGUMENTS_ERROR_MESSAGE =
"Wrong number of arguments for #{CALLER}: " +
"expected at least 2, got #{ACTUAL}"
def check_and_return(result)
result.denominator == 1 ? result.to_i.to_s : result.to_f.to_s
end
def create_error_message(message, actual, caller)
message.gsub(ACTUAL, actual).gsub(CALLER, caller)
end
def add(arguments)
if (2..(1.0 / 0)).include?(arguments.size)
check_and_return(arguments.reduce(:+))
else
raise Spreadsheet::Error,
create_error_message(EXPECTED_AT_LEAST_ARGUMENTS_ERROR_MESSAGE,
arguments.size.to_s, __callee__.to_s.upcase)
end
end
def multiply(arguments)
if (2..(1.0 / 0)).include?(arguments.size)
check_and_return(arguments.reduce(:*))
else
raise Spreadsheet::Error,
create_error_message(EXPECTED_AT_LEAST_ARGUMENTS_ERROR_MESSAGE,
arguments.size.to_s, __callee__.to_s.upcase)
end
end
def subtract(arguments)
if arguments.size == 2
check_and_return(arguments[0] - arguments[1])
else
raise Spreadsheet::Error,
create_error_message(EXPECTED_ARGUMENTS_ERROR_MESSAGE,
arguments.size.to_s, __callee__.to_s.upcase)
end
end
def divide(arguments)
if arguments.size == 2
check_and_return(arguments[0] / arguments[1])
else
raise Spreadsheet::Error,
create_error_message(EXPECTED_ARGUMENTS_ERROR_MESSAGE,
arguments.size.to_s, __callee__.to_s.upcase)
end
end
def mod(arguments)
if arguments.size == 2
check_and_return(arguments[0] % arguments[1])
else
raise Spreadsheet::Error,
create_error_message(EXPECTED_ARGUMENTS_ERROR_MESSAGE,
arguments.size.to_s, __callee__.to_s.upcase)
end
end
end

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

.......FFF........F...F..F..F..FFF.F...F

Failures:

  1) Spreadsheet#to_s returns multi-cell, multiline tables as a string
     Failure/Error: expect(Spreadsheet.new("foo\tbar\nbaz\tlarodi").to_s).to eq "foo\tbar\nbaz\tlarodi"
       
       expected: "foo\tbar\nbaz\tlarodi"
            got: "foo\tbar\nfoo\tbar\nbaz\tlarodi"
       
       (compared using ==)
       
       Diff:
       @@ -1,3 +1,4 @@
        foo	bar
       +foo	bar
        baz	larodi
     # /tmp/d20160121-5693-lldiry/spec.rb:36: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 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\nfoo\tbar\t42\nbaz\tlarodi\t100"
       
       (compared using ==)
       
       Diff:
       @@ -1,3 +1,4 @@
        foo	bar	42
       +foo	bar	42
        baz	larodi	100
     # /tmp/d20160121-5693-lldiry/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)>'

  3) Spreadsheet#to_s returns the evaluated spreadsheet as a table
     Failure/Error: expect(sheet.to_s).to eq \
       
       expected: "foo\t10\t2.1\t15\nbar\t11\t2.2\t5\nbaz\t12\t2.3\t27.60"
            got: "foo\t10\t2.1\t15\nfoo\t10\t2.1\t15\nbar\t11\t2.2\t5\nfoo\t10\t2.1\t15\nfoo\t10\t2.1\t15\nbar\t11\t2.2\t5\nbaz\t12\t2.3\t27.6"
       
       (compared using ==)
       
       Diff:
       
       @@ -1,4 +1,8 @@
        foo	10	2.1	15
       +foo	10	2.1	15
        bar	11	2.2	5
       -baz	12	2.3	27.60
       +foo	10	2.1	15
       +foo	10	2.1	15
       +bar	11	2.2	5
       +baz	12	2.3	27.6
     # /tmp/d20160121-5693-lldiry/spec.rb:50: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/, got #<Spreadsheet::Error: Wrong number of arguments for ADD: expected at least 2, got 1> with backtrace:
         # /tmp/d20160121-5693-lldiry/solution.rb:198:in `add'
         # /tmp/d20160121-5693-lldiry/solution.rb:165:in `assign_value'
         # /tmp/d20160121-5693-lldiry/solution.rb:158:in `match_pattern'
         # /tmp/d20160121-5693-lldiry/solution.rb:148:in `assert_and_match_correct_expression'
         # /tmp/d20160121-5693-lldiry/solution.rb:134:in `evaluate'
         # /tmp/d20160121-5693-lldiry/solution.rb:104:in `evaluate_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:97:in `get'
         # /tmp/d20160121-5693-lldiry/solution.rb:36:in `get_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:21:in `[]'
         # /tmp/d20160121-5693-lldiry/spec.rb:119:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-lldiry/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-lldiry/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/, got #<Spreadsheet::Error: Wrong number of arguments for MULTIPLY: expected at least 2, got 1> with backtrace:
         # /tmp/d20160121-5693-lldiry/solution.rb:208:in `multiply'
         # /tmp/d20160121-5693-lldiry/solution.rb:165:in `assign_value'
         # /tmp/d20160121-5693-lldiry/solution.rb:158:in `match_pattern'
         # /tmp/d20160121-5693-lldiry/solution.rb:148:in `assert_and_match_correct_expression'
         # /tmp/d20160121-5693-lldiry/solution.rb:134:in `evaluate'
         # /tmp/d20160121-5693-lldiry/solution.rb:104:in `evaluate_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:97:in `get'
         # /tmp/d20160121-5693-lldiry/solution.rb:36:in `get_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:21:in `[]'
         # /tmp/d20160121-5693-lldiry/spec.rb:149:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-lldiry/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-lldiry/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 SUBTRACT: expected 2, got 1> with backtrace:
         # /tmp/d20160121-5693-lldiry/solution.rb:218:in `subtract'
         # /tmp/d20160121-5693-lldiry/solution.rb:165:in `assign_value'
         # /tmp/d20160121-5693-lldiry/solution.rb:158:in `match_pattern'
         # /tmp/d20160121-5693-lldiry/solution.rb:148:in `assert_and_match_correct_expression'
         # /tmp/d20160121-5693-lldiry/solution.rb:134:in `evaluate'
         # /tmp/d20160121-5693-lldiry/solution.rb:104:in `evaluate_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:97:in `get'
         # /tmp/d20160121-5693-lldiry/solution.rb:36:in `get_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:21:in `[]'
         # /tmp/d20160121-5693-lldiry/spec.rb:171:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-lldiry/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-lldiry/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 DIVIDE: expected 2, got 1> with backtrace:
         # /tmp/d20160121-5693-lldiry/solution.rb:228:in `divide'
         # /tmp/d20160121-5693-lldiry/solution.rb:165:in `assign_value'
         # /tmp/d20160121-5693-lldiry/solution.rb:158:in `match_pattern'
         # /tmp/d20160121-5693-lldiry/solution.rb:148:in `assert_and_match_correct_expression'
         # /tmp/d20160121-5693-lldiry/solution.rb:134:in `evaluate'
         # /tmp/d20160121-5693-lldiry/solution.rb:104:in `evaluate_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:97:in `get'
         # /tmp/d20160121-5693-lldiry/solution.rb:36:in `get_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:21:in `[]'
         # /tmp/d20160121-5693-lldiry/spec.rb:195:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-lldiry/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-lldiry/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 MOD: expected 2, got 1> with backtrace:
         # /tmp/d20160121-5693-lldiry/solution.rb:238:in `mod'
         # /tmp/d20160121-5693-lldiry/solution.rb:165:in `assign_value'
         # /tmp/d20160121-5693-lldiry/solution.rb:158:in `match_pattern'
         # /tmp/d20160121-5693-lldiry/solution.rb:148:in `assert_and_match_correct_expression'
         # /tmp/d20160121-5693-lldiry/solution.rb:134:in `evaluate'
         # /tmp/d20160121-5693-lldiry/solution.rb:104:in `evaluate_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:97:in `get'
         # /tmp/d20160121-5693-lldiry/solution.rb:36:in `get_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:21:in `[]'
         # /tmp/d20160121-5693-lldiry/spec.rb:219:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-lldiry/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-lldiry/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#[] adds floating point numbers with ADD
     Failure/Error: expect(Spreadsheet.new('10  =ADD(A1, 1.1)')['B1']).to eq '11.10'
       
       expected: "11.10"
            got: "11.1"
       
       (compared using ==)
     # /tmp/d20160121-5693-lldiry/spec.rb:229:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  10) Spreadsheet#[] subtracts floating point numbers with SUBTRACT
     Failure/Error: expect(Spreadsheet.new('10  =SUBTRACT(A1, 1.1)')['B1']).to eq '8.90'
       
       expected: "8.90"
            got: "8.9"
       
       (compared using ==)
     # /tmp/d20160121-5693-lldiry/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)>'

  11) Spreadsheet#[] divides floating point numbers with DIVIDE
     Failure/Error: expect(Spreadsheet.new('10  =DIVIDE(A1, 4)')['B1']).to eq '2.50'
       
       expected: "2.50"
            got: "2.5"
       
       (compared using ==)
     # /tmp/d20160121-5693-lldiry/spec.rb:244:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  12) 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-lldiry/solution.rb:146:in `assert_and_match_correct_expression'
         # /tmp/d20160121-5693-lldiry/solution.rb:134:in `evaluate'
         # /tmp/d20160121-5693-lldiry/solution.rb:104:in `evaluate_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:97:in `get'
         # /tmp/d20160121-5693-lldiry/solution.rb:36:in `get_cell'
         # /tmp/d20160121-5693-lldiry/solution.rb:21:in `[]'
         # /tmp/d20160121-5693-lldiry/spec.rb:266:in `block (4 levels) in <top (required)>'
         # /tmp/d20160121-5693-lldiry/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-lldiry/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.04035 seconds
40 examples, 12 failures

Failed examples:

rspec /tmp/d20160121-5693-lldiry/spec.rb:35 # Spreadsheet#to_s returns multi-cell, multiline tables as a string
rspec /tmp/d20160121-5693-lldiry/spec.rb:39 # Spreadsheet#to_s splits cells by two or more spaces
rspec /tmp/d20160121-5693-lldiry/spec.rb:43 # Spreadsheet#to_s returns the evaluated spreadsheet as a table
rspec /tmp/d20160121-5693-lldiry/spec.rb:118 # Spreadsheet#[] raises an exception for less than two arguments passed to ADD
rspec /tmp/d20160121-5693-lldiry/spec.rb:148 # Spreadsheet#[] raises an exception for less than two arguments to MULTIPLY
rspec /tmp/d20160121-5693-lldiry/spec.rb:170 # Spreadsheet#[] raises an exception when SUBTRACT is called with a wrong number of arguments
rspec /tmp/d20160121-5693-lldiry/spec.rb:194 # Spreadsheet#[] raises an exception when DIVIDE is called with a wrong number of arguments
rspec /tmp/d20160121-5693-lldiry/spec.rb:218 # Spreadsheet#[] raises an exception when MOD is called with a wrong number of arguments
rspec /tmp/d20160121-5693-lldiry/spec.rb:228 # Spreadsheet#[] adds floating point numbers with ADD
rspec /tmp/d20160121-5693-lldiry/spec.rb:233 # Spreadsheet#[] subtracts floating point numbers with SUBTRACT
rspec /tmp/d20160121-5693-lldiry/spec.rb:243 # Spreadsheet#[] divides floating point numbers with DIVIDE
rspec /tmp/d20160121-5693-lldiry/spec.rb:265 # Spreadsheet#[] raises an exception for invalid expressions

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

Мирослав обнови решението на 11.01.2016 14:51 (преди над 8 години)

+class Spreadsheet
+
+ attr_accessor :table
+
+ def initialize(data = "")
+ @table = Table.new
+ data.strip.split(/\n/).each { |line|
+ @table.add_row(line.strip.split(/\s\s+|\t/))
+ }
+ end
+
+ def empty?()
+ not @table.has_rows?
+ end
+
+ def cell_at(cell_index)
+ get_cell(cell_index)
+ end
+
+ def [](cell_index)
+ get_cell(cell_index, true)
+ end
+
+ def to_s
+ @table.to_s
+ end
+
+ class Error < Exception
+ end
+
+ private
+
+ def get_cell(cell_index, evaluate = false)
+ if match = cell_index.match(Expression::ONLY_CELL_INDEX_PATTERN)
+ first, second = match.captures
+ @table.get(first, second.to_i, evaluate)
+ else
+ raise Spreadsheet::Error, "Invalid cell index '#{cell_index}'"
+ end
+ end
+end
+
+class CellIndexFunctions
+
+ ALPHABET_LENGTH = 26
+ FIRST_ALPHABET_CHARACTER = 65
+
+ def initialize()
+ end
+
+ def get_letter_at(index)
+ (index + FIRST_ALPHABET_CHARACTER - 1).chr
+ end
+
+ def convert_to_symbols(number)
+ if number <= ALPHABET_LENGTH
+ return get_letter_at(number)
+ end
+ convert_to_symbols(Rational(number, ALPHABET_LENGTH).floor) +
+ get_letter_at(number % ALPHABET_LENGTH)
+ end
+end
+
+class Table
+
+ attr_accessor :cells
+
+ def initialize()
+ @cells = {}
+ @max_index = 0
+ @max_rows = 0
+ end
+
+ def initialize_column(index)
+ if @cells[CellIndexFunctions.new.convert_to_symbols(index + 1)].nil?
+ @cells[CellIndexFunctions.new.convert_to_symbols(index + 1)] = []
+ end
+ end
+
+ def add_row(row)
+ row.each.with_index { |element, index|
+ initialize_column(index)
+ if @max_index < index then @max_index = index end
+ @cells[CellIndexFunctions.new.convert_to_symbols(index + 1)].push(element)
+ }
+ @max_rows += 1
+ end
+
+ def has_rows?()
+ !@cells.empty?
+ end
+
+ def get(col, row, evaluate = false)
+ if @cells[col].nil? || @cells[col][row - 1].nil? || row - 1 < 0
+ raise Spreadsheet::Error, "Cell '#{col}#{row}' does not exist"
+ elsif evaluate
+ evaluate_cell(@cells[col][row - 1])
+ else
+ @cells[col][row - 1]
+ end
+ end
+
+ def evaluate_cell(cell)
+ Expression.new(self).evaluate(cell)
+ end
+
+ def to_s()
+ (0...@max_rows).reduce("") { |answer, row|
+ answer += (1..(@max_index + 1)).reduce(answer) { |line, index|
+ line += evaluate_cell(@cells[
+ CellIndexFunctions.new.convert_to_symbols(index)][row]).to_s + "\t"
+ }.strip + "\n"
+ }.strip
+ end
+end
+
+class Expression
+
+ EXPRESSION_PATTERN = /=(.*)/
+ RESULT_PATTERN = /(\d+)|(\d+\.\d+)/
+ ONLY_RESULT_PATTERN = /^#{RESULT_PATTERN}$/
+ CELL_INDEX_PATTERN = /([A-Z]+)([0-9]+)/
+ ONLY_CELL_INDEX_PATTERN = /^#{CELL_INDEX_PATTERN}$/
+ ARGUMENT_PATTERN = /#{RESULT_PATTERN}|#{CELL_INDEX_PATTERN}/
+ ARGUMENTS_PATTERN = /(?:#{ARGUMENT_PATTERN},\s?)*(?:#{ARGUMENT_PATTERN})/
+ FORMULA_PATTERN = /^(\w+)\((#{ARGUMENTS_PATTERN})?\)$/
+
+ def initialize(table)
+ @table = table
+ end
+
+ def evaluate(expression)
+ if match = expression.match(EXPRESSION_PATTERN)
+ assert_and_match_correct_expression(match.captures[0])
+ else
+ expression
+ end
+ end
+
+ def assert_and_match_correct_expression(expression_value)
+ match = expression_value.match(ONLY_RESULT_PATTERN) ||
+ expression_value.match(ONLY_CELL_INDEX_PATTERN) ||
+ expression_value.match(FORMULA_PATTERN)
+
+ if match.nil?
+ raise Spreadsheet::Error, "Invalid expression #{expression_value}"
+ else
+ match_pattern(expression_value)
+ end
+ end
+
+ def match_pattern(expression_value)
+ if match = expression_value.match(ONLY_RESULT_PATTERN)
+ match.captures[0] || match.captures[1]
+ elsif match = expression_value.match(ONLY_CELL_INDEX_PATTERN)
+ @table.get(match.captures[0], match.captures[1].to_i, true)
+ else match = expression_value.match(FORMULA_PATTERN)
+ assign_value(expression_value, match.captures[0], match.captures[1])
+ end
+ end
+
+ def assign_value(expression_value, formula_name, arguments)
+ if arguments.nil? then arguments = "" end
+ begin
+ Functions.new.send(formula_name.downcase, arguments.split(/,\s?/).map {
+ |argument| assert_and_match_correct_expression(argument).to_r
+ })
+ rescue NoMethodError => e
+ raise Spreadsheet::Error, "Unknown function '#{formula_name}'"
+ end
+ end
+end
+
+class Functions
+
+ UNKNOWN_NUMBER = "UNKNOWN_NUMBER"
+ CALLER = "CALLEE"
+ ACTUAL = "ACTUAL_AMOUNT"
+ EXPECTED_ARGUMENTS_ERROR_MESSAGE =
+ "Wrong number of arguments for #{CALLER}: expected 2, " +
+ "got #{ACTUAL}"
+ EXPECTED_AT_LEAST_ARGUMENTS_ERROR_MESSAGE =
+ "Wrong number of arguments for #{CALLER}: " +
+ "expected at least 2, got #{ACTUAL}"
+
+ def check_and_return(result)
+ result.denominator == 1 ? result.to_i.to_s : result.to_f.to_s
+ end
+
+ def create_error_message(message, actual, caller)
+ message.gsub(ACTUAL, actual).gsub(CALLER, caller)
+ end
+
+ def add(arguments)
+ if (2..(1.0 / 0)).include?(arguments.size)
+ check_and_return(arguments.reduce(:+))
+ else
+ raise Spreadsheet::Error,
+ create_error_message(EXPECTED_AT_LEAST_ARGUMENTS_ERROR_MESSAGE,
+ arguments.size.to_s, __callee__.to_s.upcase)
+ end
+ end
+
+ def multiply(arguments)
+ if (2..(1.0 / 0)).include?(arguments.size)
+ check_and_return(arguments.reduce(:*))
+ else
+ raise Spreadsheet::Error,
+ create_error_message(EXPECTED_AT_LEAST_ARGUMENTS_ERROR_MESSAGE,
+ arguments.size.to_s, __callee__.to_s.upcase)
+ end
+ end
+
+ def subtract(arguments)
+ if arguments.size == 2
+ check_and_return(arguments[0] - arguments[1])
+ else
+ raise Spreadsheet::Error,
+ create_error_message(EXPECTED_ARGUMENTS_ERROR_MESSAGE,
+ arguments.size.to_s, __callee__.to_s.upcase)
+ end
+ end
+
+ def divide(arguments)
+ if arguments.size == 2
+ check_and_return(arguments[0] / arguments[1])
+ else
+ raise Spreadsheet::Error,
+ create_error_message(EXPECTED_ARGUMENTS_ERROR_MESSAGE,
+ arguments.size.to_s, __callee__.to_s.upcase)
+ end
+ end
+
+ def mod(arguments)
+ if arguments.size == 2
+ check_and_return(arguments[0] % arguments[1])
+ else
+ raise Spreadsheet::Error,
+ create_error_message(EXPECTED_ARGUMENTS_ERROR_MESSAGE,
+ arguments.size.to_s, __callee__.to_s.upcase)
+ end
+ end
+end
+
+sheet = Spreadsheet.new <<-Table
+ 1 asd =ADD(A2, B2)
+ =A1 5
+ =ADD(1,3) =MOD(13, 7) =ADD(A3, B3)
+Table
+p sheet.to_s

Мирослав обнови решението на 11.01.2016 14:52 (преди над 8 години)

class Spreadsheet
attr_accessor :table
def initialize(data = "")
@table = Table.new
data.strip.split(/\n/).each { |line|
@table.add_row(line.strip.split(/\s\s+|\t/))
}
end
def empty?()
not @table.has_rows?
end
def cell_at(cell_index)
get_cell(cell_index)
end
def [](cell_index)
get_cell(cell_index, true)
end
def to_s
@table.to_s
end
class Error < Exception
end
private
def get_cell(cell_index, evaluate = false)
if match = cell_index.match(Expression::ONLY_CELL_INDEX_PATTERN)
first, second = match.captures
@table.get(first, second.to_i, evaluate)
else
raise Spreadsheet::Error, "Invalid cell index '#{cell_index}'"
end
end
end
class CellIndexFunctions
ALPHABET_LENGTH = 26
FIRST_ALPHABET_CHARACTER = 65
def initialize()
end
def get_letter_at(index)
(index + FIRST_ALPHABET_CHARACTER - 1).chr
end
def convert_to_symbols(number)
if number <= ALPHABET_LENGTH
return get_letter_at(number)
end
convert_to_symbols(Rational(number, ALPHABET_LENGTH).floor) +
get_letter_at(number % ALPHABET_LENGTH)
end
end
class Table
attr_accessor :cells
def initialize()
@cells = {}
@max_index = 0
@max_rows = 0
end
def initialize_column(index)
if @cells[CellIndexFunctions.new.convert_to_symbols(index + 1)].nil?
@cells[CellIndexFunctions.new.convert_to_symbols(index + 1)] = []
end
end
def add_row(row)
row.each.with_index { |element, index|
initialize_column(index)
if @max_index < index then @max_index = index end
@cells[CellIndexFunctions.new.convert_to_symbols(index + 1)].push(element)
}
@max_rows += 1
end
def has_rows?()
!@cells.empty?
end
def get(col, row, evaluate = false)
if @cells[col].nil? || @cells[col][row - 1].nil? || row - 1 < 0
raise Spreadsheet::Error, "Cell '#{col}#{row}' does not exist"
elsif evaluate
evaluate_cell(@cells[col][row - 1])
else
@cells[col][row - 1]
end
end
def evaluate_cell(cell)
Expression.new(self).evaluate(cell)
end
def to_s()
(0...@max_rows).reduce("") { |answer, row|
answer += (1..(@max_index + 1)).reduce(answer) { |line, index|
line += evaluate_cell(@cells[
CellIndexFunctions.new.convert_to_symbols(index)][row]).to_s + "\t"
}.strip + "\n"
}.strip
end
end
class Expression
EXPRESSION_PATTERN = /=(.*)/
RESULT_PATTERN = /(\d+)|(\d+\.\d+)/
ONLY_RESULT_PATTERN = /^#{RESULT_PATTERN}$/
CELL_INDEX_PATTERN = /([A-Z]+)([0-9]+)/
ONLY_CELL_INDEX_PATTERN = /^#{CELL_INDEX_PATTERN}$/
ARGUMENT_PATTERN = /#{RESULT_PATTERN}|#{CELL_INDEX_PATTERN}/
ARGUMENTS_PATTERN = /(?:#{ARGUMENT_PATTERN},\s?)*(?:#{ARGUMENT_PATTERN})/
FORMULA_PATTERN = /^(\w+)\((#{ARGUMENTS_PATTERN})?\)$/
def initialize(table)
@table = table
end
def evaluate(expression)
if match = expression.match(EXPRESSION_PATTERN)
assert_and_match_correct_expression(match.captures[0])
else
expression
end
end
def assert_and_match_correct_expression(expression_value)
match = expression_value.match(ONLY_RESULT_PATTERN) ||
expression_value.match(ONLY_CELL_INDEX_PATTERN) ||
expression_value.match(FORMULA_PATTERN)
if match.nil?
raise Spreadsheet::Error, "Invalid expression #{expression_value}"
else
match_pattern(expression_value)
end
end
def match_pattern(expression_value)
if match = expression_value.match(ONLY_RESULT_PATTERN)
match.captures[0] || match.captures[1]
elsif match = expression_value.match(ONLY_CELL_INDEX_PATTERN)
@table.get(match.captures[0], match.captures[1].to_i, true)
else match = expression_value.match(FORMULA_PATTERN)
assign_value(expression_value, match.captures[0], match.captures[1])
end
end
def assign_value(expression_value, formula_name, arguments)
if arguments.nil? then arguments = "" end
begin
Functions.new.send(formula_name.downcase, arguments.split(/,\s?/).map {
|argument| assert_and_match_correct_expression(argument).to_r
})
rescue NoMethodError => e
raise Spreadsheet::Error, "Unknown function '#{formula_name}'"
end
end
end
class Functions
UNKNOWN_NUMBER = "UNKNOWN_NUMBER"
CALLER = "CALLEE"
ACTUAL = "ACTUAL_AMOUNT"
EXPECTED_ARGUMENTS_ERROR_MESSAGE =
"Wrong number of arguments for #{CALLER}: expected 2, " +
"got #{ACTUAL}"
EXPECTED_AT_LEAST_ARGUMENTS_ERROR_MESSAGE =
"Wrong number of arguments for #{CALLER}: " +
"expected at least 2, got #{ACTUAL}"
def check_and_return(result)
result.denominator == 1 ? result.to_i.to_s : result.to_f.to_s
end
def create_error_message(message, actual, caller)
message.gsub(ACTUAL, actual).gsub(CALLER, caller)
end
def add(arguments)
if (2..(1.0 / 0)).include?(arguments.size)
check_and_return(arguments.reduce(:+))
else
raise Spreadsheet::Error,
create_error_message(EXPECTED_AT_LEAST_ARGUMENTS_ERROR_MESSAGE,
arguments.size.to_s, __callee__.to_s.upcase)
end
end
def multiply(arguments)
if (2..(1.0 / 0)).include?(arguments.size)
check_and_return(arguments.reduce(:*))
else
raise Spreadsheet::Error,
create_error_message(EXPECTED_AT_LEAST_ARGUMENTS_ERROR_MESSAGE,
arguments.size.to_s, __callee__.to_s.upcase)
end
end
def subtract(arguments)
if arguments.size == 2
check_and_return(arguments[0] - arguments[1])
else
raise Spreadsheet::Error,
create_error_message(EXPECTED_ARGUMENTS_ERROR_MESSAGE,
arguments.size.to_s, __callee__.to_s.upcase)
end
end
def divide(arguments)
if arguments.size == 2
check_and_return(arguments[0] / arguments[1])
else
raise Spreadsheet::Error,
create_error_message(EXPECTED_ARGUMENTS_ERROR_MESSAGE,
arguments.size.to_s, __callee__.to_s.upcase)
end
end
def mod(arguments)
if arguments.size == 2
check_and_return(arguments[0] % arguments[1])
else
raise Spreadsheet::Error,
create_error_message(EXPECTED_ARGUMENTS_ERROR_MESSAGE,
arguments.size.to_s, __callee__.to_s.upcase)
end
end
-end
-
+end
-sheet = Spreadsheet.new <<-Table
- 1 asd =ADD(A2, B2)
- =A1 5
- =ADD(1,3) =MOD(13, 7) =ADD(A3, B3)
-Table
-p sheet.to_s