Решение на Трета задача от Йоана Тодорова

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

Към профила на Йоана Тодорова

Резултати

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

Код

module RBFS
class Parser
def initialize(string_data)
@string_data = string_data
end
def parse_list
objects_count, @string_data = @string_data.split(':', 2)
objects_count.to_i.times do
name, length, rest = @string_data.split(':', 3)
yield name, rest[0...length.to_i]
@string_data = rest[length.to_i..-1]
end
end
end
class File
attr_accessor :data
def initialize(data = nil)
@data = data
end
def data_type
case @data
when String then :string
when Symbol then :symbol
when Fixnum, Float then :number
when TrueClass, FalseClass then :boolean
when NilClass then :nil
end
end
def serialize
"#{data_type}:#{data}"
end
def self.parse(string_data)
data_type, data = string_data.split(':', 2)
data = case data_type
when 'string' then data
when 'symbol' then data.to_sym
when 'number' then data.to_f
when 'boolean' then data == 'true'
end
File.new(data)
end
end
class Directory
attr_reader :files, :directories
def initialize
@files = {}
@directories = {}
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] || @files[name]
end
def serialize
"#{serialize_list(@files)}#{serialize_list(@directories)}"
end
def self.parse(string_data)
directory = Directory.new
parser = Parser.new(string_data)
parser.parse_list do |name, data|
directory.add_file(name, File.parse(data))
end
parser.parse_list do |name, data|
directory.add_directory(name, Directory.parse(data))
end
directory
end
private
def serialize_list(objects)
serialized_objects = objects.map do |name, object|
serialized_object = object.serialize
"#{name}:#{serialized_object.length}:#{serialized_object}"
end
"#{objects.count}:#{serialized_objects.join('')}"
end
end
end

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

............................................

Finished in 0.04208 seconds
44 examples, 0 failures

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

Йоана обнови решението на 02.11.2014 01:26 (преди около 10 години)

+module RBFS
+ class DirectoryParser
+ def initialize(string_data)
+ @string_data = string_data
+ end
+
+ def parse
+ objects_count, @string_data = *@string_data.match(/(\d+?):(.*)$/)[1..-1]
+ objects_count.to_i.times do
+ name, length, rest = @string_data.match(/(.+?):(\d+?):(.*)$/)[1..-1]
+ yield name, rest[0..length.to_i - 1]
+ @string_data = rest[length.to_i..-1]
+ end
+ self
+ end
+
+ alias :parse_files :parse
+ alias :parse_directories :parse
+ end
+
+ module Serializer
+ def self.serialize(objects)
+ "#{objects.count}:" +
+ objects.map do |name, object|
+ serialized_object = object.serialize
+ "#{name}:#{serialized_object.length}:#{serialized_object}"
+ end.join
+ end
+ end
+
+ class File
+ attr_accessor :data
+
+ def initialize(content = nil)
+ @data = content
+ end
+
+ def data_type
+ case @data
+ when String then :string
+ when Symbol then :symbol
+ when Fixnum, Float then :number
+ when TrueClass, FalseClass then :boolean
+ when NilClass then :nil
+ end
+ end
+
+ def serialize
+ "#{data_type}:#{data}"
+ end
+
+ def self.parse(string_data)
+ matched = string_data.match(/(?<data_type>\w+?):(?<data>.*)/)
+ data = case matched[:data_type]
+ when 'string' then matched[:data]
+ when 'symbol' then matched[:data][1..-1].to_sym
+ when 'number' then matched[:data].to_f
+ when 'boolean' then matched[:data] == 'true'
+ end
+ File.new(data)
+ end
+ end
+
+ class Directory
+ attr_reader :files, :directories
+
+ def initialize
+ @files, @directories = {}, {}
+ end
+
+ def add_file(name, file)
+ @files[name] = file if valid_name?(name)
+ self
+ end
+
+ def add_directory(name, directory = Directory.new)
+ @directories[name] = directory if valid_name?(name)
+ self
+ end
+
+ def [](name)
+ @directories[name] || @files[name]
+ end
+
+ def serialize
+ [@files, @directories].map do |objects|
+ Serializer.serialize(objects)
+ end.join
+ end
+
+ def self.parse(string_data)
+ directory = Directory.new
+ DirectoryParser.new(string_data)
+ .parse_files { |name, data| directory.add_file(name, File.parse(data)) }
+ .parse_directories do |name, data|
+ directory.add_directory(name, Directory.parse(data))
+ end
+ directory
+ end
+
+ private
+
+ def valid_name?(name)
+ ! name.match(/:/)
+ end
+ end
+end

Здравей :)

Браво, решението ти е доста добро! Парсването обаче не работи за един от примерите в условието на задачата.

Имам и някои идеи за подобрение :)

  • Не е нужно да се използват регулярни изрази - всичко може да стане без тях и ще е по-ясен кода. Ако има конкретен случай, за който смяташ, че методът split няма да се справи - разгледай документацията му. Освен това [1..-1] върху обекта, който връща match е много объркващо, поне за мен.
  • Присвояването на един ред в Directory#initialize не се използва често. Предпочита се да са си на отделни редове. Причината за това е, че ако станат повече променливите, ще трябва да ги разделиш или ще стане трудно за четене (ще трябва да се броят елементи). Пък и се създава един ненужен масив.
  • Няма нужда от проверката if valid_name?. Няма да тестваме с невалидни данни. (Иначе, ако трябваше да се проверява, по-добрият вариант би бил да се хвърли някакво изключение, вместо да се игнорира.)
  • За условието не е нужно add_file и add_directory да връщат self. Предполагам, че си го направила, за да можеш да си ги chain-ваш. Не казвам, че трябва да ги махнеш. Хубаво е, че си се замислила за това как ще се използва. :) Помисли и над този начин на използване, ако го нямаше self:

    root = Directory.new
    rbfs = root.add_directory('rbfs')
    
    # Do something with `rbfs`
    

    Това няма да го тестваме и можеш да си избереш как да го направиш, но винаги има плюсове и минуси - на единия и на другия подход.

  • Харесва ми, че си се сетила за оператора || в #[]. Directory#serialize е доста хитро използване на map.
  • Хубаво е винаги на join да даваш разделителя експлицитно. Има глобална променлива ($,), чрез която може да се задава разделителят по подразбиране и която може да счупи кода ти, ако е сетната на нещо различно от празен низ.
  • Chain-ването в Directory.parse ми е малко странно. Защо просто не си запазиш парсера в една променлива и да използваш нея? Не си спестила редове и няма да има нужда да връщаш self. Също така блоковете на parse_files и parse_directories е хубаво да ги направиш по един и същ начин, според мен ще е по-подредено. В случая - и двата с do.
  • Името на класа, който използваш за парсване е DirectoryParser, само че парсва и файлове. Само Parser няма ли да е по-подходящо?
  • Метода DirectoryParser#parse може да го кръстиш например parse_list или parse_array, така че да не се налага да му правиш синоними, за да изглежда по-четимо. Той реално не се интересува какви са нещата вътре.
  • rest[0..length.to_i - 1] може да се напише като rest[0...length.to_i].
  • И последно - какво получаваш ако serialize е класов метод в модул, а не например private метод в Directory? Не го използваш на друго място.

ПП. Решението ти е по-кратко от моето. :)

Благодаря за забележките и най-вече, че ме подсети за split :)

Относно методът serialize - сметнах, че логиката трябва да се изнесе в отделен модул, но тъй като наистина не го ползвам на друго място, го направих private на Directory.

Йоана обнови решението на 02.11.2014 22:35 (преди около 10 години)

module RBFS
- class DirectoryParser
+ class Parser
def initialize(string_data)
@string_data = string_data
end
- def parse
- objects_count, @string_data = *@string_data.match(/(\d+?):(.*)$/)[1..-1]
+ def parse_list
+ objects_count, @string_data = @string_data.split(':', 2)
objects_count.to_i.times do
- name, length, rest = @string_data.match(/(.+?):(\d+?):(.*)$/)[1..-1]
- yield name, rest[0..length.to_i - 1]
+ name, length, rest = @string_data.split(':', 3)
+ yield name, rest[0...length.to_i]
@string_data = rest[length.to_i..-1]
end
- self
end
-
- alias :parse_files :parse
- alias :parse_directories :parse
end
- module Serializer
- def self.serialize(objects)
- "#{objects.count}:" +
- objects.map do |name, object|
- serialized_object = object.serialize
- "#{name}:#{serialized_object.length}:#{serialized_object}"
- end.join
- end
- end
-
class File
attr_accessor :data
- def initialize(content = nil)
- @data = content
+ def initialize(data = nil)
+ @data = data
end
def data_type
case @data
when String then :string
when Symbol then :symbol
when Fixnum, Float then :number
when TrueClass, FalseClass then :boolean
when NilClass then :nil
end
end
def serialize
"#{data_type}:#{data}"
end
def self.parse(string_data)
- matched = string_data.match(/(?<data_type>\w+?):(?<data>.*)/)
- data = case matched[:data_type]
- when 'string' then matched[:data]
- when 'symbol' then matched[:data][1..-1].to_sym
- when 'number' then matched[:data].to_f
- when 'boolean' then matched[:data] == 'true'
+ data_type, data = string_data.split(':', 2)
+ data = case data_type
+ when 'string' then data
+ when 'symbol' then data[1..-1].to_sym
+ when 'number' then data.to_f
+ when 'boolean' then data == 'true'
end
File.new(data)
end
end
class Directory
attr_reader :files, :directories
def initialize
- @files, @directories = {}, {}
+ @files = {}
+ @directories = {}
end
def add_file(name, file)
- @files[name] = file if valid_name?(name)
- self
+ @files[name] = file
end
def add_directory(name, directory = Directory.new)
- @directories[name] = directory if valid_name?(name)
- self
+ @directories[name] = directory
end
def [](name)
@directories[name] || @files[name]
end
def serialize
[@files, @directories].map do |objects|
- Serializer.serialize(objects)
- end.join
+ serialize_list(objects)
+ end.join('')
end
def self.parse(string_data)
directory = Directory.new
- DirectoryParser.new(string_data)
- .parse_files { |name, data| directory.add_file(name, File.parse(data)) }
- .parse_directories do |name, data|
- directory.add_directory(name, Directory.parse(data))
- end
+ parser = Parser.new(string_data)
+ parser.parse_list do |name, data|
+ directory.add_file(name, File.parse(data))
+ end
+ parser.parse_list do |name, data|
+ directory.add_directory(name, Directory.parse(data))
+ end
directory
end
private
- def valid_name?(name)
- ! name.match(/:/)
+ def serialize_list(objects)
+ "#{objects.count}:" +
+ objects.map do |name, object|
+ serialized_object = object.serialize
+ "#{name}:#{serialized_object.length}:#{serialized_object}"
+ end.join('')
end
end
end

Супер! Става все по-добро :)

Все още, обаче, не работи за един от примерите в условието на задачата.

И ми е малко странно конкатенирането в serialize_list - ако запазиш нещото от map-a в променлива и я използваш в резултата дали няма да изглежда по-добре? :)

Йоана обнови решението на 03.11.2014 10:43 (преди около 10 години)

module RBFS
class Parser
def initialize(string_data)
@string_data = string_data
end
def parse_list
+ p "string_data => #{@string_data}"
objects_count, @string_data = @string_data.split(':', 2)
objects_count.to_i.times do
name, length, rest = @string_data.split(':', 3)
yield name, rest[0...length.to_i]
@string_data = rest[length.to_i..-1]
end
end
end
class File
attr_accessor :data
def initialize(data = nil)
@data = data
end
def data_type
case @data
when String then :string
when Symbol then :symbol
when Fixnum, Float then :number
when TrueClass, FalseClass then :boolean
when NilClass then :nil
end
end
def serialize
"#{data_type}:#{data}"
end
def self.parse(string_data)
data_type, data = string_data.split(':', 2)
data = case data_type
when 'string' then data
- when 'symbol' then data[1..-1].to_sym
+ when 'symbol' then data.to_sym
when 'number' then data.to_f
when 'boolean' then data == 'true'
end
File.new(data)
end
end
class Directory
attr_reader :files, :directories
def initialize
@files = {}
@directories = {}
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] || @files[name]
end
def serialize
- [@files, @directories].map do |objects|
- serialize_list(objects)
- end.join('')
+ "#{serialize_list(@files)}#{serialize_list(@directories)}"
end
def self.parse(string_data)
directory = Directory.new
parser = Parser.new(string_data)
parser.parse_list do |name, data|
directory.add_file(name, File.parse(data))
end
parser.parse_list do |name, data|
directory.add_directory(name, Directory.parse(data))
end
directory
end
private
def serialize_list(objects)
- "#{objects.count}:" +
- objects.map do |name, object|
- serialized_object = object.serialize
- "#{name}:#{serialized_object.length}:#{serialized_object}"
- end.join('')
+ serialized_objects = objects.map do |name, object|
+ serialized_object = object.serialize
+ "#{name}:#{serialized_object.length}:#{serialized_object}"
+ end
+ "#{objects.count}:#{serialized_objects.join('')}"
end
end
end

Йоана обнови решението на 03.11.2014 10:44 (преди около 10 години)

module RBFS
class Parser
def initialize(string_data)
@string_data = string_data
end
def parse_list
- p "string_data => #{@string_data}"
objects_count, @string_data = @string_data.split(':', 2)
objects_count.to_i.times do
name, length, rest = @string_data.split(':', 3)
yield name, rest[0...length.to_i]
@string_data = rest[length.to_i..-1]
end
end
end
class File
attr_accessor :data
def initialize(data = nil)
@data = data
end
def data_type
case @data
when String then :string
when Symbol then :symbol
when Fixnum, Float then :number
when TrueClass, FalseClass then :boolean
when NilClass then :nil
end
end
def serialize
"#{data_type}:#{data}"
end
def self.parse(string_data)
data_type, data = string_data.split(':', 2)
data = case data_type
when 'string' then data
when 'symbol' then data.to_sym
when 'number' then data.to_f
when 'boolean' then data == 'true'
end
File.new(data)
end
end
class Directory
attr_reader :files, :directories
def initialize
@files = {}
@directories = {}
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] || @files[name]
end
def serialize
"#{serialize_list(@files)}#{serialize_list(@directories)}"
end
def self.parse(string_data)
directory = Directory.new
parser = Parser.new(string_data)
parser.parse_list do |name, data|
directory.add_file(name, File.parse(data))
end
parser.parse_list do |name, data|
directory.add_directory(name, Directory.parse(data))
end
directory
end
private
def serialize_list(objects)
serialized_objects = objects.map do |name, object|
serialized_object = object.serialize
"#{name}:#{serialized_object.length}:#{serialized_object}"
end
"#{objects.count}:#{serialized_objects.join('')}"
end
end
end