Решение на Трета задача от Екатерина Горанова

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

Към профила на Екатерина Горанова

Резултати

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

Код

module RBFS
module Boolean
def self.from_string(string)
string == 'true'
end
def self.===(other)
other.is_a? TrueClass or other.is_a? FalseClass
end
end
module Number
def self.from_string(string)
string.include?('.') ? string.to_f : string.to_i
end
def self.===(other)
other.is_a? Integer or other.is_a? Float
end
end
class TypeManager
def self.get_type(data)
case data
when NilClass then :nil
when Number then :number
when Symbol then :symbol
when Boolean then :boolean
when String then :string
end
end
def self.convert_string_to_type(type, string)
case type.to_sym
when :string then string
when :symbol then string.to_sym
when :number then Number.from_string(string)
when :boolean then Boolean.from_string(string)
end
end
end
class Serializer
class << self
def serialize(*args)
args.map { |arg| "#{arg}:" }.join('').chop
end
def serialize_many(many)
many_serialized = many.map do |name, unit|
serialize(name, unit.serialize.size, unit.serialize)
end
serialize(many.count, many_serialized.join(''))
end
def parse_file(string_data)
File.new(TypeManager.convert_string_to_type(*string_data.split(':')))
end
def parse_directory(string_data)
dir = Directory.new
if not string_data.empty?
restore(dir, :file, string_data)
restore(dir, :directory, string_data)
end
dir
end
private
def restore(dir, obj_type, string_data)
extract(string_data).to_i.times do
data = extract_name_and_length(string_data)
object = parse_object(obj_type, string_data, data[:length])
dir.add_object(obj_type, data[:name], object)
end
end
def parse_object(type, string_data, length = nil)
case type
when :file then parse_file(string_data.slice!(0..length.pred))
when :directory then parse_directory(string_data)
end
end
def extract(string_data)
string_data.slice!(/[^:]+:/).chop
end
def extract_name_and_length(string_data)
{ name: extract(string_data), length: extract(string_data).to_i }
end
end
end
class File
attr_reader :data, :data_type
def data=(data)
@data = data
@data_type = TypeManager.get_type(data)
end
def initialize(data = nil)
@data = data
@data_type = TypeManager.get_type(data)
end
def serialize
Serializer.serialize(@data_type, @data)
end
def self.parse(string_data)
Serializer.parse_file(string_data)
end
end
class Directory
attr_reader :files, :directories
def initialize
@files = {}
@directories = {}
end
def add_file(name, file)
@files[name] = file unless name.include? ':'
end
def add_directory(name, directory = Directory.new)
@directories[name] = directory unless name.include? ':'
end
def add_object(type, name, object)
method("add_#{type}").call(name, object)
end
def [](name)
@directories[name] or @files[name]
end
def serialize
"#{Serializer.serialize_many(@files)}#{Serializer.serialize_many(@directories)}"
end
def self.parse(string_data)
Serializer.parse_directory(string_data)
end
end
end

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

........................F...F...............

Failures:

  1) RBFS File data type nil can be parsed
     Failure/Error: file = RBFS::File.parse('nil:')
     ArgumentError:
       wrong number of arguments (1 for 2)
     # /tmp/d20141111-26053-1t0j697/solution.rb:33:in `convert_string_to_type'
     # /tmp/d20141111-26053-1t0j697/solution.rb:57:in `parse_file'
     # /tmp/d20141111-26053-1t0j697/solution.rb:114:in `parse'
     # /tmp/d20141111-26053-1t0j697/spec.rb:230: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 File data type string can parse a string with colons
     Failure/Error: file = RBFS::File.parse('string:Hay :)')
     ArgumentError:
       wrong number of arguments (3 for 2)
     # /tmp/d20141111-26053-1t0j697/solution.rb:33:in `convert_string_to_type'
     # /tmp/d20141111-26053-1t0j697/solution.rb:57:in `parse_file'
     # /tmp/d20141111-26053-1t0j697/solution.rb:114:in `parse'
     # /tmp/d20141111-26053-1t0j697/spec.rb:255: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.04336 seconds
44 examples, 2 failures

Failed examples:

rspec /tmp/d20141111-26053-1t0j697/spec.rb:229 # RBFS File data type nil can be parsed
rspec /tmp/d20141111-26053-1t0j697/spec.rb:254 # RBFS File data type string can parse a string with colons

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

Екатерина обнови решението на 31.10.2014 16:18 (преди около 10 години)

+module RBFS
+ module Boolean
+ def Boolean.create_boolean_from_string(string)
+ string == 'true'
+ end
+ end
+
+ class ::TrueClass
+ include Boolean
+ end
+
+ class ::FalseClass
+ include Boolean
+ end
+
+ module Number
+ def create_number_from_string(string)
+ (string.include? '.') ? string.to_f : string.to_i
+ end
+ end
+
+ class ::Integer
+ include Number
+ end
+
+ class ::Float
+ include Number
+ end
+
+ class File
+ TYPES = {
+ :nil => NilClass,
+ :number => Number,
+ :symbol => Symbol,
+ :boolean => Boolean,
+ :string => String
+ }
+
+ attr_reader :data, :data_type
+
+ def data=(new_data)
+ @data = new_data
+ get_data_type
+ end
+
+ def initialize(data = nil)
+ @data = data
+ get_data_type
+ end
+
+ def serialize
+ "#{@data_type.to_s}:#{@data.to_s}"
+ end
+
+ def File.parse(string_data)
+ File.new(convert_data_to_type(*string_data.split(':')))
+ end
+
+ private
+
+ def get_data_type
+ if NilClass === @data then @data_type = name
+ elsif Number === @data then @data_type = name
+ elsif Symbol === @data then @data_type = name
+ elsif String === @data then @data_type = name
+ elsif Boolean === @data then @data_type = name
+ end
+ end
+
+ def File.convert_data_to_type(type, data)
+ case type.to_sym
+ when :string then data
+ when :symbol then data.to_sym
+ when :number then Number.create_number_from_string(data)
+ when :boolean then Boolean.create_boolean_from_string(data)
+ end
+ end
+ end
+
+ module DirectorySerializer
+ def serialize_unit(name, unit)
+ "#{name}:#{unit.serialize.size}:#{unit.serialize}"
+ end
+
+ def DirectorySerializer.parse_directory(string_data)
+ new_directory = Directory.new
+ return new_directory if string_data == '0:0:'
+ string_data = parse_files(new_directory, string_data)
+ parse_directories(new_directory, string_data)
+ new_directory
+ end
+
+ def DirectorySerializer.parse_files(dir, string_data)
+ files_count, string_data = string_data.split(':', 2)
+ files_count.to_i.times do
+ file_name, str_length, string_data = string_data.split(':', 3)
+ dir.add_file(file_name, File.parse(string_data.slice!(0..(str_length.to_i - 1))))
+ end
+ string_data
+ end
+
+ def DirectorySerializer.parse_directories(dir, string_data)
+ dirs_count, string_data = string_data.split(':', 2)
+ dirs_count.to_i.times do
+ dir_name, dir_size, string_data = string_data.split(':', 3)
+ dir.add_directory(dir_name, Directory.parse(string_data))
+ end
+ string_data
+ end
+ end
+
+ class Directory
+ include DirectorySerializer
+ attr_reader :files, :directories
+
+ def initialize
+ @files, @directories = {}, {}
+ end
+
+ def add_file(name, file)
+ @files[name] = file unless name.include? ':'
+ end
+
+ def add_directory(name, directory = Directory.new)
+ @directories[name] = directory unless name.include? ':'
+ end
+
+ def [](name)
+ @directories[name] or @files[name]
+ end
+
+ def serialize
+ "#{@files.count}:#{serialize_files}#{@directories.count}:#{serialize_directories}"
+ end
+
+ def Directory.parse(string_data)
+ DirectorySerializer.parse_directory(string_data)
+ end
+
+ private
+
+ def serialize_files
+ @files.inject('') { |output, file_info| output << serialize_unit(*file_info) }
+ end
+
+ def serialize_directories
+ @directories.inject('') { |output, file_info| output << serialize_unit(*file_info) }
+ end
+ end
+end

Малко не особено незадълбочени коментари по стил и дреболии из кода и съвсем малко "архитектурни" забележки:

  • Каква е причината да monkey patch-ваш TrueClass и FalseClass, като включваш Boolean вътре? Не виждам къде го ползваш това. А и е лоша идея. Самият модул не е лоша идея. Ако се monkey patch-ва, може да се направи String#to_b или String#to_boolean, който прави това. Така ще може да ползваш 'true'.to_b. Това понякога се прави, но е глезотия и е по-добре да се избягва.
  • Самия метод аз бих го кръстил from_string. Тъй като е модул-метод и ще го викаш само с модула, ще се ползва много ясно, ето така: Boolean.from_string('true'). Ето това е хубав начин да имаш такава функционалност.
  • Същите бележки и за Number#create_number_from_string
  • get_data_type си плаче за @data_type = case @data .... Ако това е междинен вариант на кода, окей. Иначе не виждам никакъв смисъл от всички тези еднакви изрази там.
  • Името на този метод е лошо. Аз бих го кръстил infer_data_type, set_data_type или нещо такова. Той не get-ва, а set-ва.
  • Интересен факт е, че интерполацията в Ruby автоматично вика to_s на обектите. Тоест, serialize може да се напише така: "#{@data_type}:#{@data}". Даже и така, но аз не харесвам това изписване: "#@data_type:#@data".
  • Когато ключовете ти в речник са само символи, ползвай съкратения синтаксис (редове 32-36), т.е.:

      TYPES = {
        nil:    NilClass,
        number: Number,
        ...
      }
    

    И оставяй запетайка след последния елемент в речници и списъци, които си дефинирала на повече от един ред.

  • Отмествай тялото на case едно ниво навътре (редове 72-75).

  • Нов материал: за да дефинираш класови/модул методи, може да ползваш и def self.foo() ... end. Тоест, вмето името на класа или на модула, ползваш self. Това се предпочита по обясними причини :)
  • Паралелно присвояване няма нужда да се ползва в случаи като този на ред 117. По-добре разпиши присвояването на два реда. По-просто и по-четимо.
  • serialize_files бих го написал като @files.map { |file_info| serialize_unit(*file_info) }.join(''). Същото и за директориите.
  • Хубаво е да има празен ред между 113 и 114.
  • На ред 18 скобите не са сложени правилно; трябва да са около аргумента на include?, т.е. string.include?('.') ? ... : ....

Нещо не ми харесват нещата в DirectorySerializer модула. Имената на променливите, кодът, гъстотата на символите... Мисля, че може да се пренапише. Обърква ме и публичния му интерфейс. Има един инстанционен метод и няколко модул-метода. Защо не го направиш на клас? Инстанцирай си го, ползвай му публичните и private методите и така. Инстанционният метод просто го сложи в Directory, не го преизползваш другаде, като гледам. Аз бих пробвал пренаписване на тази част от кода, с DirectorySerializer-а.

Екатерина обнови решението на 04.11.2014 20:42 (преди около 10 години)

module RBFS
module Boolean
- def Boolean.create_boolean_from_string(string)
+ def self.from_string(string)
string == 'true'
end
- end
- class ::TrueClass
- include Boolean
+ def self.===(other)
+ other.is_a? TrueClass or other.is_a? FalseClass
+ end
end
- class ::FalseClass
- include Boolean
- end
-
module Number
- def create_number_from_string(string)
- (string.include? '.') ? string.to_f : string.to_i
+ def self.from_string(string)
+ string.include?('.') ? string.to_f : string.to_i
end
- end
- class ::Integer
- include Number
+ def self.===(other)
+ other.is_a? Integer or other.is_a? Float
+ end
end
- class ::Float
- include Number
+ class TypeManager
+ def self.get_type(data)
+ case data
+ when NilClass then :nil
+ when Number then :number
+ when Symbol then :symbol
+ when Boolean then :boolean
+ when String then :string
+ end
+ end
+
+ def self.convert_string_to_type(type, string)
+ case type.to_sym
+ when :string then string
+ when :symbol then string.to_sym
+ when :number then Number.from_string(string)
+ when :boolean then Boolean.from_string(string)
+ end
+ end
end
- class File
- TYPES = {
- :nil => NilClass,
- :number => Number,
- :symbol => Symbol,
- :boolean => Boolean,
- :string => String
- }
+ class Serializer
+ class << self
+ def serialize(*args)
+ args.map { |arg| "#{arg}:" }.join('').chop
+ end
- attr_reader :data, :data_type
+ def serialize_many(many)
+ many_serialized = many.map do |name, unit|
+ serialize(name, unit.serialize.size, unit.serialize)
+ end
+ serialize(many.count, many_serialized.join(''))
+ end
- def data=(new_data)
- @data = new_data
- get_data_type
- end
+ def parse_file(string_data)
+ File.new(TypeManager.convert_string_to_type(*string_data.split(':')))
+ end
- def initialize(data = nil)
- @data = data
- get_data_type
- end
+ def parse_directory(string_data)
+ dir = Directory.new
+ if not string_data.empty?
+ restore(dir, :file, string_data)
+ restore(dir, :directory, string_data)
+ end
+ dir
+ end
- def serialize
- "#{@data_type.to_s}:#{@data.to_s}"
- end
+ private
- def File.parse(string_data)
- File.new(convert_data_to_type(*string_data.split(':')))
- end
+ def restore(dir, obj_type, string_data)
+ extract(string_data).to_i.times do
+ data = extract_name_and_length(string_data)
+ object = parse_object(obj_type, string_data, data[:length])
+ dir.add_object(obj_type, data[:name], object)
+ end
+ end
- private
+ def parse_object(type, string_data, length = nil)
+ case type
+ when :file then parse_file(string_data.slice!(0..length.pred))
+ when :directory then parse_directory(string_data)
+ end
+ end
- def get_data_type
- if NilClass === @data then @data_type = name
- elsif Number === @data then @data_type = name
- elsif Symbol === @data then @data_type = name
- elsif String === @data then @data_type = name
- elsif Boolean === @data then @data_type = name
+ def extract(string_data)
+ string_data.slice!(/[^:]+:/).chop
end
- end
- def File.convert_data_to_type(type, data)
- case type.to_sym
- when :string then data
- when :symbol then data.to_sym
- when :number then Number.create_number_from_string(data)
- when :boolean then Boolean.create_boolean_from_string(data)
+ def extract_name_and_length(string_data)
+ { name: extract(string_data), length: extract(string_data).to_i }
end
end
end
- module DirectorySerializer
- def serialize_unit(name, unit)
- "#{name}:#{unit.serialize.size}:#{unit.serialize}"
+ class File
+ attr_reader :data, :data_type
+
+ def data=(data)
+ @data = data
+ @data_type = TypeManager.get_type(data)
end
- def DirectorySerializer.parse_directory(string_data)
- new_directory = Directory.new
- return new_directory if string_data == '0:0:'
- string_data = parse_files(new_directory, string_data)
- parse_directories(new_directory, string_data)
- new_directory
+ def initialize(data = nil)
+ @data = data
+ @data_type = TypeManager.get_type(data)
end
- def DirectorySerializer.parse_files(dir, string_data)
- files_count, string_data = string_data.split(':', 2)
- files_count.to_i.times do
- file_name, str_length, string_data = string_data.split(':', 3)
- dir.add_file(file_name, File.parse(string_data.slice!(0..(str_length.to_i - 1))))
- end
- string_data
+ def serialize
+ Serializer.serialize(@data_type, @data)
end
- def DirectorySerializer.parse_directories(dir, string_data)
- dirs_count, string_data = string_data.split(':', 2)
- dirs_count.to_i.times do
- dir_name, dir_size, string_data = string_data.split(':', 3)
- dir.add_directory(dir_name, Directory.parse(string_data))
- end
- string_data
+ def self.parse(string_data)
+ Serializer.parse_file(string_data)
end
end
class Directory
- include DirectorySerializer
attr_reader :files, :directories
def initialize
- @files, @directories = {}, {}
+ @files = {}
+ @directories = {}
end
def add_file(name, file)
@files[name] = file unless name.include? ':'
end
def add_directory(name, directory = Directory.new)
@directories[name] = directory unless name.include? ':'
end
+ def add_object(type, name, object)
+ method("add_#{type}").call(name, object)
+ end
+
def [](name)
@directories[name] or @files[name]
end
def serialize
- "#{@files.count}:#{serialize_files}#{@directories.count}:#{serialize_directories}"
+ "#{Serializer.serialize_many(@files)}#{Serializer.serialize_many(@directories)}"
end
- def Directory.parse(string_data)
- DirectorySerializer.parse_directory(string_data)
- end
-
- private
-
- def serialize_files
- @files.inject('') { |output, file_info| output << serialize_unit(*file_info) }
- end
-
- def serialize_directories
- @directories.inject('') { |output, file_info| output << serialize_unit(*file_info) }
+ def self.parse(string_data)
+ Serializer.parse_directory(string_data)
end
end
end