Решение на Трета задача от Герасим Станчев

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

Към профила на Герасим Станчев

Резултати

  • 6 точки от тестове
  • 0 бонус точки
  • 6 точки общо
  • 42 успешни тест(а)
  • 2 неуспешни тест(а)

Код

module RBFS
class RBFS::File
attr_reader :data_type, :data
def init_data_type
@data_type = case @data
when NilClass then @data_type = :nil
when String then @data_type = :string
when Symbol then @data_type = :symbol
when Numeric then @data_type = :number
when TrueClass, FalseClass then @data_type = :boolean
end
end
def initialize(data = nil, data_type = nil)
@data = data
if data_type.nil?
init_data_type
else
@data_type = data_type
end
end
def data=(data)
@data = data
init_data_type
end
def serialize
"#{@data_type}:#{@data.to_s}"
end
def self.parse(file_data)
case file_data.split(':', 2)[0]
when 'nil' then File.new nil
when 'string' then File.new file_data.split(':', 2)[1]
when 'symbol' then File.new file_data.split(':', 2)[1].to_sym
when 'number' then File.new file_data.split(':', 2)[1].to_f
when 'boolean' then File.new file_data.split(':', 2)[1] == 'true'
end
end
end
class RBFS::Directory
attr_accessor :directories, :files
def initialize(directories = {}, files = {})
@directories = directories
@files = files
end
def add_file(name, file)
@files[name] = file
end
def add_directory(name, directory = Directory.new)
@directories[name] = directory
end
def [](name)
@directories[name].nil? ? @files[name] : @directories[name]
end
def serialize_files_or_directories(object)
object.map do|object_name, object_content|
serialized_object = object_content.serialize
"#{object_name}:#{serialized_object.length}:#{serialized_object}"
end
.join
end
def serialize
"#{@files.size}:#{serialize_files_or_directories @files}" \
"#{@directories.size}:#{serialize_files_or_directories @directories}"
end
def self.parse_files_or_directories(dir_data, type)
data, slash = {}, dir_data.split(':', 2)
1.upto(slash[0].to_i) do
slash[2] = slash[1].split(':', 3)
data[slash[2][0]] = type.new(slash[2][2].slice(0, slash[2][1].to_i)
.split(':')[1], slash[2][2].split(':')[0].to_sym)
slash[1] = slash[2][2].slice(slash[2][1].to_i, slash[2][2].length)
end
data[slash[2][0]] = parse(slash[2][2]) unless type != Directory or slash[2].nil?
[data, slash[1]]
end
def self.parse(dir_data)
directories, files = {}, {}
files, dir_data = parse_files_or_directories(dir_data, File)
directories, dir_data = parse_files_or_directories(dir_data, Directory)
directory = Directory.new directories, files
end
end
end

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

............FF..............................

Failures:

  1) RBFS Directory serialization ::parse can parse directory trees without files
     Failure/Error: expect(parsed_directory['dir1']['dir2']).to be_an RBFS::Directory
       expected nil to be a kind of RBFS::Directory
     # /tmp/d20141111-26053-dykzs9/spec.rb:120:in `block (5 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) RBFS Directory serialization ::parse can parse directories recursively
     Failure/Error: expect(rbfs_directory['solution.rb'].data ).to eq :hidden
     NoMethodError:
       undefined method `data' for "solution.rb":String
     # /tmp/d20141111-26053-dykzs9/spec.rb:133:in `block (5 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.04214 seconds
44 examples, 2 failures

Failed examples:

rspec /tmp/d20141111-26053-dykzs9/spec.rb:116 # RBFS Directory serialization ::parse can parse directory trees without files
rspec /tmp/d20141111-26053-dykzs9/spec.rb:124 # RBFS Directory serialization ::parse can parse directories recursively

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

Герасим обнови решението на 03.11.2014 17:23 (преди около 10 години)

+module RBFS
+ class RBFS::File
+ attr_reader :data_type, :data
+
+ def retrieve_data(data)
+ case data
+ when ::NilClass then @data_type = :nil
+ when ::String then @data_type = :string
+ when ::Symbol then @data_type = :symbol
+ when ::Numeric then @data_type = :number
+ when ::TrueClass, ::FalseClass then @data_type = :boolean
+ end
+ end
+
+ def initialize(data = nil)
+ retrieve_data data
+ @data = data
+ end
+
+ def data=(data)
+ retrieve_data data
+ @data = data
+ end
+
+ def serialize
+ "#{@data_type}:#{@data.to_s}"
+ end
+
+ def self.parse(file_data)
+ case file_data.split(':', 2)[0]
+ when 'nil' then File.new nil
+ when 'string' then File.new file_data.split(':', 2)[1]
+ when 'symbol' then File.new file_data.split(':', 2)[1].to_sym
+ when 'number' then File.new file_data.split(':', 2)[1].to_f
+ when 'boolean' then File.new file_data.split(':', 2)[1] == 'true'
+ end
+ end
+ end
+
+ class RBFS::Directory
+ attr_accessor :directories, :files
+
+ def initialize
+ @directories = {}
+ @files = {}
+ end
+
+ def add_file(name, file)
+ return if name[0] == ':'
+ @files[name] = file
+ end
+
+ def add_directory(name, directory = Directory.new)
+ return if name[0] == ':'
+ @directories[name] = directory
+ end
+
+ def [](name)
+ @directories[name].nil? ? @files[name] : @directories[name]
+ end
+
+ def serialize_files
+ @files.map do |file_name, content|
+ "#{file_name}:#{content.data.to_s.length + content.data_type.length + 1}:" \
+ "#{content.data_type}:#{content.data}"
+ end
+ end
+
+ def serialize_directories
+ files_format, dir_format = '', ''
+ @files.each do |file|
+ files_format << "#{file[0]}:#{file[1].serialize.size}:#{file[1].serialize}"
+ end
+ files_format << '0:' if @directories.empty?
+ @directories.each do |directory|
+ new_dir = directory[1].serialize_directories
+ dir_format << "#{@directories.size}:#{directory[0]}:#{new_dir.length}:#{new_dir}"
+ end
+ "#{@files.size}:#{files_format}" + dir_format
+ end
+
+ def serialize
+ return '0:0:' if @directories.empty? and @files.empty?
+ return serialize_directories if @directories.empty?
+ result = ''
+ @directories.each { result << serialize_directories }
+ result
+ end
+ end
+end

В имплементацията липсва само метода parse на RBFS::Directory. Но останалите методи трябва да работят коректно. Използвам ли лоши практики? И относно парсирането на стринг за директория:

  • Трябва да връщам нов обект от тип RBFS::Directory ?
  • Как трябва да се пази името на файл/директория, подаден на директорията - през add_file/add_directory ?
  • Нали мога да изнеса част от сериализациите в модул, тъй като няма да ми стигне само един метод за парсирането (а в момента имам 7)?

Здравей :)

Първо да отговоря на въпросите ти:

  • Да, трябва да връщаш нов обект от тип RBFS::Directory.
  • Начинът, по който пазиш имената (като ключове на хешове) ми изглежда добре. Притеснява ли те нещо в него?
  • Освен в модул, може да ги изнесеш и в клас. :) Все пак гледай класът/модулът да има някакъв смисъл - да има своята добре отделена задача, не просто място, където да си сложиш останалите методи.

Сега малко други коментари:

  • Когато дефинираш клас, който се намира в модул - няма нужда да пишеш името на модула пред класа. Той ще бъде дефиниран в модула и в двата случая.
  • ::Class се използва само ако имаш клас със същото име в модула, в който се намираш (или някой от "родителите" му).
  • Името на метода retrieve_data не съответства на съдържанието му. Помисли за по-добро име. Освен това, when-а в него плаче да изнесеш присвояването извън него. Напомням, че всички подобни конструкции в Ruby са изрази и връщат стойност. И последно за този метод - защо му е аргументът data, след като може да използва @data?
  • Във File.parse правиш сплит два пъти. Можеш да си запазиш резултата, най-добре чрез "разопаковане" на масива, и да го използваш на двете места. Освен това можеш да елиминираш повторенията на File.new.
  • Не е нужно да проверяваш дали в името няма :. Обещахме, че няма да подаваме невалидни данни. :) Освен това, ако искахме такава проверка, би било добре да се хвърли изключение, не да се "премълчава".

Малко ми е трудно да парсна (pun intended) това за сериализирането:

  • В serialize_files можеш да преизползваш File#serialize, няма нужда директорията да знае за точния формат на файловете. Променливата content няма ли да е по-добре да се казва file?
  • Сега осъзнавам, че serialize_files не го използваш.
  • Има нещо объркано при сериализирането. serialize вика serialize_directories, който всеки път обхожда едни и същи файлове и директории.
  • Защо са ти случаите за 0 файлове и/или 0 директории? Ако направиш сериализирането чрез обхождане/map-ване (опа, подсказах :) ) то естествено ще се получи, без тези проверки.
  • Ето малко препоръки как може да изчистиш частта със сериализацията:
    • Не използвай конкатенация на низове - направи го с map и join.
    • Това, което прави serialize_directories може да го прави самия serialize.
    • Форматът за файловете и директориите е един и същ. "#{entity_count}:#{entities}", където entities е #{name}:#{serialized_string}:#{serialized_string}. Използвай рекурсивно резултата от File#serialize и Directory#serialize.

Ако нещо не ти е ясно по цялата книга, която изписах - питай. :)

Благодаря за отговорите!

Относно отговорите на въпросите ми:

  • Ок.
  • Объркваща е концепцията, но мисля, че сега ще се справя.
  • Да, ще измисля елегантен начин за подреждането на функциите, а не просто „тъпчене“.

  • Относно when-овете в retrieve_data - не се сещам как мога да изнеса присвояването извън тях. Мога да напиша when-овете постфиксно, но не знам дали ще има особена разлика. Освен това, подредбата не е най-добра, защото съм ги писал когато ограничението за редове на метод бе 6. Ще рефакторирам.

  • Ъъъъ, wut. Ще ми трябва време да възприема всичката информация. Рекурсията в Ruby не изглежда толкова ясна, колкото в други езици. От началото предполагах, че мога да направя цялата сериализация в serialize, но стана прекалено дълго.

За case-a - линк към слайд. Този е за if, но предполагам вече схващаш какво имам предвид. Друг вариант е да си направиш private метод, в който да имаш само case-a и да присвояваш резултата от метода на @data_type.

Аз не виждам разлика между рекурсията в Ruby и рекурсията в други езици.

Кажи кое от нещата не ти е ясно и ще се опитам да обясня по-добре. :)

Aхаааааааааааааааааааааа, сега ми се изясни. Хитро! :)

Рекурсивният подход си е един и същ в повечето езици - факт. Но концепцията не ми изглежда изчистена и удобна за ползване, като в други (чисто функционални) езици. След като реша задачата, ще ти дам мнение отново.

Благодаря за вниманието и ще питам, когато си поблъскам главата над някой проблем час-два, но ще рефакторирам в следващите 2-3 дни и ще се опитам да изчистя цялата концепция в главата си. : -)

def serialize_files
  @files.map do|file| 
    serialized_file = file[1].serialize
    file[0].to_s + ':' + serialized_file.length.to_s + ':' + serialized_file
  end
end

def serialize
  return '0:0:' if @directories.empty? and @files.empty?
  files_format = serialize_files.join
  files_format << '0:' if @directories.empty?
  "#{@files.size}:#{files_format}" + \
    @directories.map do |directory|
      new_dir  = directory[1].serialize
      "#{@directories.size}:#{directory[0]}:#{new_dir.length}:#{new_dir}"
    end
    .join
end

Определено сега е по-добре. :) Map & join are a bliss!


Имам проблем с парсирането. Дефинирам метода без имплементация:

def parse(dir_data)

end

и при пускане на примерните тестове получавам грешка:

1) RBFS Directory serialization ::parse can parse

Failure/Error: parsed_directory = RBFS::Directory.parse(simple_serialized_string)

NoMethodError: undefined method 'parse' for RBFS::Directory:Class

# ./sample_spec.rb:44:in block (5 levels) in <top (required)>'


Kaкво пропускам? Ако искаш, мога да кача останалата част от кода, ако е нужно.

Като оправиш грешката направо си обнови решението :)

  • @files.map do|file| защо не е @files.map do |file_name, file| ?
  • file[0].to_s + ':' + serialized_file.length.to_s + ':' + serialized_file --?--> "#{file_name}:#{serialized_file.length}:#{serialized_file}"
  • Все още не разбирам защо са ти тези проверки return '0:0:' if @directories.empty? and @files.empty? и files_format << '0:' if @directories.empty?. Нулата ще се яви естествено ако нямаш файлове или директории, защото масива ти ще е празен.
  • Прегледай пак примера в условието - броя на директориите трябва да го има веднъж преди да са изредени.
  • Това

    @files.map do|file| 
      serialized_file = file[1].serialize
      file[0].to_s + ':' + serialized_file.length.to_s + ':' + serialized_file
    end
    

    и това

    @directories.map do |directory|
      new_dir  = directory[1].serialize
      "#{@directories.size}:#{directory[0]}:#{new_dir.length}:#{new_dir}"
    end
    

    като си оправиш грешката във второто ще ти излязат едни и същи. Обедини си ги в някакъв метод, който да викаш два пъти - за файловете и за директориите.

  • Toчка за теб.
  • За 0:1:directory1:40:0:1:directory2:22:1:README:9:number:420: ми се чупи - не се добавя '0:' в края. А проверката за наличие на файлове и директории е за да не се счупи при "#{@files.size}:#{files_format}". Не се сещам за по-добра интерпретация в момента.
  • Тъкмо приключвам с „по-добрата имплементация“. Чак се ядосвам колко приятно и удобно изглежда..

def serialize_files_or_directories(object)
  object.map do|object_name, object_content|
    serialized_object = object_content.serialize
    "#{object_name}:#{serialized_object.length}:#{serialized_object}"
  end
  .join
end

def serialize
"#{@files.size}:#{serialize_files_or_directories @files}" \
"#{@directories.size}:#{serialize_files_or_directories @directories}"
end

Добър пример за duck typing?

Герасим обнови решението на 09.11.2014 20:27 (преди около 10 години)

module RBFS
class RBFS::File
attr_reader :data_type, :data
- def retrieve_data(data)
- case data
- when ::NilClass then @data_type = :nil
- when ::String then @data_type = :string
- when ::Symbol then @data_type = :symbol
- when ::Numeric then @data_type = :number
- when ::TrueClass, ::FalseClass then @data_type = :boolean
- end
+ def init_data_type
+ @data_type = case @data
+ when NilClass then @data_type = :nil
+ when String then @data_type = :string
+ when Symbol then @data_type = :symbol
+ when Numeric then @data_type = :number
+ when TrueClass, FalseClass then @data_type = :boolean
+ end
end
- def initialize(data = nil)
- retrieve_data data
+ def initialize(data = nil, data_type = nil)
@data = data
+ if data_type.nil?
+ init_data_type
+ else
+ @data_type = data_type
+ end
end
def data=(data)
- retrieve_data data
@data = data
+ init_data_type
end
def serialize
"#{@data_type}:#{@data.to_s}"
end
def self.parse(file_data)
case file_data.split(':', 2)[0]
when 'nil' then File.new nil
when 'string' then File.new file_data.split(':', 2)[1]
when 'symbol' then File.new file_data.split(':', 2)[1].to_sym
when 'number' then File.new file_data.split(':', 2)[1].to_f
when 'boolean' then File.new file_data.split(':', 2)[1] == 'true'
end
end
end
class RBFS::Directory
attr_accessor :directories, :files
- def initialize
- @directories = {}
- @files = {}
+ def initialize(directories = {}, files = {})
+ @directories = directories
+ @files = files
end
def add_file(name, file)
- return if name[0] == ':'
@files[name] = file
end
def add_directory(name, directory = Directory.new)
- return if name[0] == ':'
@directories[name] = directory
end
def [](name)
@directories[name].nil? ? @files[name] : @directories[name]
end
- def serialize_files
- @files.map do |file_name, content|
- "#{file_name}:#{content.data.to_s.length + content.data_type.length + 1}:" \
- "#{content.data_type}:#{content.data}"
+ def serialize_files_or_directories(object)
+ object.map do|object_name, object_content|
+ serialized_object = object_content.serialize
+ "#{object_name}:#{serialized_object.length}:#{serialized_object}"
end
+ .join
end
- def serialize_directories
- files_format, dir_format = '', ''
- @files.each do |file|
- files_format << "#{file[0]}:#{file[1].serialize.size}:#{file[1].serialize}"
+ def serialize
+ "#{@files.size}:#{serialize_files_or_directories @files}" \
+ "#{@directories.size}:#{serialize_files_or_directories @directories}"
+ end
+
+ def self.parse_files_or_directories(dir_data, type)
+ data, slash = {}, dir_data.split(':', 2)
+ 1.upto(slash[0].to_i) do
+ slash[2] = slash[1].split(':', 3)
+ data[slash[2][0]] = type.new(slash[2][2].slice(0, slash[2][1].to_i)
+ .split(':')[1], slash[2][2].split(':')[0].to_sym)
+ slash[1] = slash[2][2].slice(slash[2][1].to_i, slash[2][2].length)
end
- files_format << '0:' if @directories.empty?
- @directories.each do |directory|
- new_dir = directory[1].serialize_directories
- dir_format << "#{@directories.size}:#{directory[0]}:#{new_dir.length}:#{new_dir}"
- end
- "#{@files.size}:#{files_format}" + dir_format
+ data[slash[2][0]] = parse(slash[2][2]) unless type != Directory or slash[2].nil?
+ [data, slash[1]]
end
- def serialize
- return '0:0:' if @directories.empty? and @files.empty?
- return serialize_directories if @directories.empty?
- result = ''
- @directories.each { result << serialize_directories }
- result
+ def self.parse(dir_data)
+ directories, files = {}, {}
+ files, dir_data = parse_files_or_directories(dir_data, File)
+ directories, dir_data = parse_files_or_directories(dir_data, Directory)
+ directory = Directory.new directories, files
end
end
end