Осма задача
- Краен срок:
- 11.01.2016 17:30
- Точки:
- 6
Срокът за предаване на решения е отминал
Spreadsheets
Днес съществуват поне няколко софтуерни пакета, които позволяват обработка на таблични документи, тип "spreadsheets" – Microsoft Excel, Google Sheets, Open/LibreOffice Calc, Numbers под OS X и т.н. Принципът им на действие е сравнително прост и интуитивен, като същевременно позволяват голяма гъвкавост и имат забележително широко поле на приложение.
В тази задача ще имате за цел да имплементирате част от функционалността на едно spreadsheet приложение.
Клас Spreadsheet
Класът Spreadsheet
ще е основната входна точка на програмния ни интерфейс.
Той трябва да предоставя следните методи:
-
Конструктор, който приема един опционален аргумент. Ако се извика без аргументи, създава празна таблица (sheet). Ако се подаде аргумент, той трябва да е текстов низ, представляващ таблични данни в разновидност на TSV (tab-separated values). В този текстов вид, всеки ред от низа представлява ред от табличните данни. Редовете трябва да са разделени със символа
"\n"
. В рамките на един ред, отделните клетки, формиращи колони, могат да са разделени по два начина:- Със символа
"\t"
(tab). - С два или повече последователни интервала.
Празни редове в началото или в края на подадената като низ таблица трябва да бъдат премахвани. Считат се за празни и редовете, които съдържат само whitespace (интервали, табове и нови редове).
При конструиране на таблица от низ, whitespace-ът в началото и в края на всеки ред от низа трябва да бъде премахнат. Същото се отнася и за whitespace-а около всяка клетка.
Ето пример за конструиране на таблица с два реда и две колони:
sheet = Spreadsheet.new("cell1\tcell2\ncell3\tcell4")
Алтернативен запис на същата таблица, използваща HEREDOC низове и интервали за разделител между клетките:
sheet = Spreadsheet.new <<-TABLE cell1 cell2 cell3 cell4 TABLE
Следното също трябва да се възприема като таблица, идентична на горните две:
sheet = Spreadsheet.new ' cell1 cell2 cell3 cell4 '
Възможните стойности на клетките са текст, числа и формули. За тях ще дадем информация по-долу.
На конструктора на Spreadsheet няма да подаваме таблици, в които има редове с различен брой колони. Всички редове ще имат еднакъв брой колони, таблиците ще са правоъгълни.
- Със символа
Метод
#empty?
, връщащ истинна стойност, ако инстанцията наSpreadsheet
представлява празна таблица – такива са таблици с нула клетки.-
Метод
#cell_at(cell_index)
, връщащ "суровата", неизчислена стойност на клетката, идентифицирана от подадения наcell_at
аргумент.cell_index
е низ, идентифициращ желаната клетка в координати, подобни на тези, използвани и в други spreadsheet програми. Индексът се състои от букви и цифри, като буквите задават номера на колоната, а цифрите – номера на реда. Буквите са само главни букви от английската азбука, "A" до "Z". Буква "A" отговаря на първа колона, "B" – на втора колона и т.н. до "Z", отговаряща за 26-та колона. След "Z" следва "AA", "AB" и т.н., след това "BA", "BB", ..., до "ZZ", след което следва "AAA" и така нататък до безкрай.Ето няколко примера за индекси на клетки:
-
"A1"
– първата клетка в таблицата, на първия ред и в първата колона. -
"B1"
– втората клетка на първия ред. -
"C1"
– третата клетка на първия ред. -
"A2"
– първата клетка на втория ред. -
"B4"
– втората клетка на четвъртия ред. -
"AB2"
– 28-мата клетка на втория ред. -
"BA42"
– 53-тата клетка на 42-рия ред.
И пример за употребата на
#cell_at
:sheet = Spreadsheet.new ' cell1 cell2 cell3 cell4 ' sheet.cell_at("A1") # => "cell1" sheet.cell_at("B2") # => "cell4"
Ако на
cell_at
се подаде невалиден индекс, трябва да се предизвика изключение. Изключението трябва да е от типSpreadsheet::Error
и да е с текст"Invalid cell index 'индекс'"
. Например:begin Spreadsheet.new("foo").cell_at('FOO') rescue Spreadsheet::Error => e e.message # => "Invalid cell index 'FOO'" end
Ако на
cell_at
се подаде индекс на несъществуваща клетка, изключението трябва да е с текст"Cell 'ABC123' does not exist"
, къдетоABC123
е индексът, подаден на метода.Всички изключения, които методите
Spreadsheet
ще искаме да хвърлят, трябва да са от типSpreadsheet::Error
.Ако не хвърли изключение, резултатът от
cell_at
трябва винаги да е низ. -
-
Метод
#[](cell_index)
. Този метод е много подобен наcell_at
. Ако подадем невалиден индекс, или индекс на несъществуваща клетка, очакваме да получим същите грешки, както и приcell_at
. Разликата между двата метода идва при клетките, които съдържат формули (изрази). В тези случаи,cell_at
трябва да връща самия израз, в оригиналния си вид, докато[]
трябва да връща изчислената стойност на израза.Изразите са клетки, започващи със символа
=
. След символа=
и преди началото на израза може да има нула или повече интервала. Следните типове изрази трябва да се поддържат:- Обикновени числа. Например,
=42
трябва да се оцени на42
. Трябва да се поддържат както цели, така и числа с плаваща запетая. -
Референции към други клетки. Например,
=B2
трябва да се оцени на стойността на клеткатаB2
. Това реално трябва да е еквивалент наsheet['B2']
. -
Формули. Формулите имат проста структура – име и нула или повече
параметъра. Например, клетка със стойност
=ADD(2, 2)
трябва да се оцени като "4". Вижте повече за формулите в следващата секция.
Ако не хвърли изключение, резултатът от
[]
трябва винаги да е низ, дори да е число, резултат от изчисление на формула. - Обикновени числа. Например,
-
Метод
#to_s
, който връща "изчислената" таблица като низ, в TSV формат. За разлика от конструктора, методътto_s
връща винаги валиден TSV формат, със следните особености:- Редовете са разделени със символа
"\n"
. Няма символ за нов ред след последния ред в таблицата. - Клетките в рамките на един ред са разделени с таб –
"\t"
.
Например:
sheet = Spreadsheet.new <<-TABLE cell1 cell2 cell3 cell4 cell5\tcell6 TABLE sheet.to_s # => "cell1\tcell2\ncell3\tcell4\ncell5\tcell6"
Тук спазваме една важна максима при писането на софтуер, обработващ някаква форма на данни (99% от софтуера на света) – бъдете толерантни към формата на входните данни и стриктни към формата на данните, които продуцирате като резултат. Изходът на
to_s
винаги следва стриктно един и същ формат.Ако в някоя клетка има формула, то тя трябва да е "изчислена" в резултата на
to_s
. Съответно,to_s
трябва да хвърля същите грешки, които хвърлят и методите, описани по-горе, занимаващи се с изчисление на формули. - Редовете са разделени със символа
Формули (функции)
По-долу ще използваме "формула" и "функция" като синоними.
Формулите, поддържани от нашето spreadsheet приложение, имат много проста
структура – знак за равенство, последван от име на формулата и нула или повече
параметъра, оградени в скоби.
Всяка клетка, чието съдържание започва с =
се счита за съдържаща формула.
Имената на формулите са винаги на латиница и винаги с главни букви. Формулите не могат да се влагат. Общият им вид е следният:
=FORMULA(param, param, param, param)
Параметрите на формулите могат да бъдат само числа или референции към други клетки. Формулите не могат да имат за параметри други формули. Забележете обаче, че референциите към други клетки могат да съдържат други формули, които трябва да бъдат изчислени, преди да бъдат подадени като аргумент на текущата формула.
Параметрите на формулите са разделени със запетая, като преди и след запетаята може да има нула или повече whitespace символа.
Някои формули имат фиксиран брой аргументи, други формули имат променлив брой аргументи.
Ако клетка съдържа формула, която е непозната, при опит за оценка на формулата, трябва да се предизвика изключение със следния текст:
begin
Spreadsheet.new('=LARODI(1, 2)')['A1']
rescue Spreadsheet::Error => e
e.message # => "Unknown function 'LARODI'"
end
Ако клетка съдържа грешка в "синтаксиса" на формулата, следва да се хвърли изключение със следния текст:
begin
Spreadsheet.new('=ADD(1, 2')['A1']
rescue Spreadsheet::Error => e
e.message # => "Invalid expression 'ADD(1, 2'"
end
Ако формулата FOO очаква точно N параметъра, но ѝ бъдат подадени M, тогава трябва да се хвърли изключение с текст:
"Wrong number of arguments for 'FOO': expected N, got M"
Ако формулата FOO очаква поне N параметъра, но ѝ бъдат подадени M, където M < N, тогава изключението трябва да е с текст:
"Wrong number of arguments for 'FOO': expected at least N, got M"
Напомняме, че всички изключения, хвърляни от методите на Spreadsheet
, трябва
да са от тип Spreadsheet::Error
.
Всички формули, които класът Spreadsheet
трябва да поддържа, са математически
функции и се предполага, че работят с числа. Приемете, че на формулите няма да
подаваме низове или референции към клетки, които се оценяват като нещо различно
от число.
Трябва да имплементирате следните функции:
-
ADD
– приема два или повече аргумента и връща сбора им. Аргументите, както е споменато и по-горе, могат да са числа, или референции към други клетки. -
MULTIPLY
– приема два или повече аргумента и връща произведението им. -
SUBTRACT
– приема точно два аргумента и връща разликата на първия минус втория. -
DIVIDE
– приема точно два аргумента, разделя първия на втория и връща резултата. -
MOD
– приема точно два аргумента и връща остатъка от делението на първия аргумент на втория (аналогично на "оператора"%
в Ruby).
Работа с числа
Всички числови операции трябва да могат да работят както с цели числа, така и с числа с плаваща запетая. Когато делите числа, дори да са целочислени, резултатът трябва да може да е число с плаваща запетая.
Ако резултатът е точно число, без дробна част, то дробната част трябва да се
пропусне, т.е. 2.0
трябва да се показва като "2"
.
Ако резултатът от операцията има дробна част, закръгляте до втори знак и винаги
показвате с точност до два знака. Например, 4.2
трябва да се показва като
"4.20"
. Припомняме, че крайният резултат на всички изрази, следователно и
формули, е винаги низ.
Примерен тест
Примерните тестове се намират в GitHub хранилището с домашните. За информация как да ги изпълните, погледнете README-то на хранилището.
Ограничения
Тази задача има следните ограничения:
- Най-много 80 символа на ред
- Най-много 6 реда на метод
- Най-много 2 нива на влагане
- Най-много 4 аргумента на метод
Ако искате да проверите дали задачата ви спазва ограниченията, следвайте инструкциите в описанието на хранилището за домашните.
Няма да приемаме решения, които не спазват ограниченията. Изпълнявайте rubocop
редовно, докато пишете кода. Ако смятате, че rubocop
греши по някакъв начин,
пишете ни на fmi@ruby.bg, заедно с прикачен код или линк към такъв като
private gist. Ако пуснете кода си публично (например във форумите), ще смятаме
това за преписване.