Решение на Трета задача от Александър Петков

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

Към профила на Александър Петков

Резултати

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

Код

module RBFS
class File
attr_reader :data_type, :data
def initialize(data=nil)
self.data = data
end
def data=(value)
@data = value
determine_type(value)
end
def serialize
"#{@data_type}:#{@data}"
end
def self.parse(string_data)
type, data = string_data.split(':', 2)
data = case type
when 'number' then parse_number(data)
when 'symbol' then data.to_sym
when 'boolean' then data == 'true'
when 'nil' then nil
else data
end
File.new(data)
end
private
def determine_type(data)
@data_type = case data
when Fixnum, Float then :number
when TrueClass, FalseClass then :boolean
when String then :string
when Symbol then :symbol
else :nil
end
end
def self.parse_number(string_number)
if string_number.include?('.')
string_number.to_f
else
string_number.to_i
end
end
end
class Directory
attr_reader :files, :directories
def initialize
@files = {}
@directories = {}
@serializer = proc { |entity_name, _| single_serialization(entity_name) }
end
def add_file(name, file)
@files[name] = file
end
def add_directory(name, directory=Directory.new)
@directories[name] = directory
end
def [](name)
if files.has_key? name
@files[name]
else
@directories[name] || Directory.new
end
end
def serialize
[@files, @directories].map do |entity_store|
"#{entity_store.size}:#{entity_store.map(&@serializer).join}"
end.join
end
def self.parse(string_data)
parsed_directory = Directory.new
files_count, string_data = string_data.split(':', 2)
files, string_data = basic_file_parse(files_count.to_i, string_data, File)
files.each { |name, file| parsed_directory.add_file(name, file) }
directories_count, string_data = string_data.split(':', 2)
directories, _ = basic_file_parse(directories_count.to_i, string_data, Directory)
directories.each { |name, dir| parsed_directory.add_directory(name, dir) }
parsed_directory
end
private
def single_serialization(entity_name)
basic_serialization = self[entity_name].serialize
"#{entity_name}:#{basic_serialization.size}:#{basic_serialization}"
end
def self.basic_file_parse(files_count, string_data, file_type)
files = []
files_count.times do
name, data_length, string_data = string_data.split(':', 3)
data = string_data.slice!(0..data_length.to_i - 1)
files << [name, file_type.parse(data)]
end
[files, string_data]
end
end
end

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

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

Failures:

  1) RBFS Directory #[] returns nil if directory or file doesnt exist
     Failure/Error: expect(directory['home']['another_user']).to be_nil
       expected: nil
            got: #<RBFS::Directory:0xb9f6fd4c @files={}, @directories={}, @serializer=#<Proc:0xb9f6fcfc@/tmp/d20141111-26053-golvzk/solution.rb:59>>
     # /tmp/d20141111-26053-golvzk/spec.rb:190:in `block (4 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 #[] is case-sensitive
     Failure/Error: expect(directory['HOME']).to be_nil
       expected: nil
            got: #<RBFS::Directory:0xb9ed67f0 @files={}, @directories={}, @serializer=#<Proc:0xb9ed67a0@/tmp/d20141111-26053-golvzk/solution.rb:59>>
     # /tmp/d20141111-26053-golvzk/spec.rb:194:in `block (4 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.04392 seconds
44 examples, 2 failures

Failed examples:

rspec /tmp/d20141111-26053-golvzk/spec.rb:189 # RBFS Directory #[] returns nil if directory or file doesnt exist
rspec /tmp/d20141111-26053-golvzk/spec.rb:193 # RBFS Directory #[] is case-sensitive

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

Александър обнови решението на 09.11.2014 13:56 (преди около 10 години)

+module RBFS
+
+ class File
+ attr_reader :data_type, :data
+
+ def initialize(data=nil)
+ self.data = data
+ end
+
+ def determine_type(data)
+ @data_type =
+ if [Fixnum, Float].include? data.class then :number
+ elsif [TrueClass, FalseClass].include? data.class then :boolean
+ elsif data.class == String then :string
+ elsif data.class == Symbol then :symbol
+ else :nil
+ end
+ end
+
+ def self.parse_number(string_number)
+ if string_number.include?('.')
+ string_number.to_f
+ else
+ string_number.to_i
+ end
+ end
+
+ def data=(value)
+ @data = value
+ determine_type(value)
+ end
+
+ def serialize
+ "#{@data_type.to_s}:#{@data.to_s}"
+ end
+
+ def self.parse(string_data)
+ type, data = string_data.split(':', 2)
+ data = case type
+ when 'number' then parse_number(data)
+ when 'symbol' then data.to_sym
+ when 'boolean' then data == 'true'
+ when 'nil' then nil
+ else data
+ 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=nil)
+ @directories[name] = directory || Directory.new
+ end
+
+ def [](name)
+ if files.has_key? name
+ @files[name]
+ else
+ @directories[name]
+ end
+ end
+
+ def serialize_single_file(name)
+ basic_serialization = @files[name].serialize
+ "#{name}:#{basic_serialization.size}:#{basic_serialization}"
+ end
+
+ def serialize
+ all_files = @files.map { |name, _| serialize_single_file(name) }.join('')
+ serialize_files = "#{@files.size}:#{all_files}"
+
+ all_directories = @directories.map do |name, directory|
+ "#{name}:#{directory.serialize.size}:#{directory.serialize}"
+ end
+
+ "#{serialize_files}#{@directories.size}:#{all_directories.join}"
+ end
+
+ def self.basicFileParse(files_count, string_data, file_type)
+ files = []
+
+ files_count.times do
+ name, data_length, string_data = string_data.split(':', 3)
+ data = string_data.slice!(0..data_length.to_i - 1)
+ files << [name, file_type.parse(data)]
+ end
+
+ [files, string_data]
+ end
+
+ def self.parse(string_data)
+ parsed_directory = Directory.new
+
+ files_count, string_data = string_data.split(':', 2)
+ files, string_data = basicFileParse(files_count.to_i, string_data, File)
+ files.each { |name, file| parsed_directory.add_file(name, file) }
+
+ directories_count, string_data = string_data.split(':', 2)
+ directories, _ = basicFileParse(directories_count.to_i, string_data, Directory)
+ directories.each { |name, dir| parsed_directory.add_directory(name, dir) }
+
+ parsed_directory
+ end
+ end
+end

parse в Directory може да изглежда доста по-чисто, ако се наруши ограничението за 1 ниво на вложеност :) Съществуването на self.basicFileParse и повторението в self.parse сa резултат именно на "пригаждане" на кода, за да премине ограничението. Уви, за по-генерална смяна на подхода не мисля, че ще намеря достатъчно време. :)

Здравей :)

Ето малко коментари:

  • Някои методи (determine_type, parse_number, serialize_single_file и basicFileParse) са част от вътрешната имплементация на класовете, но са публични.
  • В determine_type ще е много по-чисто и прегледно, ако използваш case. Напомням, че с case можеш да сравняваш директно с класове, без методи като .class или .is_a?. Това е следствие от това, че Ruby сравнява в case чрез ===, а Class === object е валидно нещо и е същото като object.is_a?(Class).
  • Няма нужда да извикваш .to_s щом използваш интерполация.
  • Стойностите по подразбиране на аргументи в Ruby може да са всякакви изрази (може дори да са дефиниции на методи :) ), включително и Directory.new. Може да го използваш в add_directory.
  • От друга страна това - directory || Directory.new може да го използваш в [].
  • serialize_single_file и basicFileParse имат подвеждащи имена - говорят за файлове, но работят и за директории. Втория трябва да е със snake_case, не camelCase. :)
  • Всъщност с горната точка подсказах, че може да обединиш сериализирането на файлове и директории, както правиш в парсването. :)
  • Съществуването на self.basicFileParse е хубаво, значи ограничението си е свършило работата. :)

Ето идея за не-чак-толкова-по-генерална смяна на подхода. :)

Предлагам нов клас Parser, който да приема низа, който ще се парсва, в initialize и има метод parse_list, който yield-ва двойки (например entity_name и entity_data), които представляват имената и низовете за отделните файлове и директории. Много подобно на това, което правиш сега с basic_file_parse. Разликата е, че метода в Parser, вместо да връща остатъка от низа, може да пази вътрешно състояние с него. Реално няма да има генерални промени по кода ти, а parse ще съдържа две извиквания на методи с по един блок.

ПП. Решението ти е доста добро, ако поправиш дреболиите по-горе ще стане супер. :)

Александър обнови решението на 10.11.2014 15:28 (преди около 10 години)

module RBFS
class File
attr_reader :data_type, :data
def initialize(data=nil)
self.data = data
end
- def determine_type(data)
- @data_type =
- if [Fixnum, Float].include? data.class then :number
- elsif [TrueClass, FalseClass].include? data.class then :boolean
- elsif data.class == String then :string
- elsif data.class == Symbol then :symbol
- else :nil
- end
- end
-
- def self.parse_number(string_number)
- if string_number.include?('.')
- string_number.to_f
- else
- string_number.to_i
- end
- end
-
def data=(value)
@data = value
determine_type(value)
end
def serialize
- "#{@data_type.to_s}:#{@data.to_s}"
+ "#{@data_type}:#{@data}"
end
def self.parse(string_data)
type, data = string_data.split(':', 2)
data = case type
when 'number' then parse_number(data)
when 'symbol' then data.to_sym
when 'boolean' then data == 'true'
when 'nil' then nil
else data
end
File.new(data)
end
+
+ private
+
+ def determine_type(data)
+ @data_type = case data
+ when Fixnum, Float then :number
+ when TrueClass, FalseClass then :boolean
+ when String then :string
+ when Symbol then :symbol
+ else :nil
+ end
+ end
+
+ def self.parse_number(string_number)
+ if string_number.include?('.')
+ string_number.to_f
+ else
+ string_number.to_i
+ end
+ end
end
class Directory
attr_reader :files, :directories
def initialize
@files = {}
@directories = {}
+
+ @serializer = proc { |entity_name, _| single_serialization(entity_name) }
end
def add_file(name, file)
@files[name] = file
end
- def add_directory(name, directory=nil)
- @directories[name] = directory || Directory.new
+ def add_directory(name, directory=Directory.new)
+ @directories[name] = directory
end
def [](name)
if files.has_key? name
@files[name]
else
- @directories[name]
+ @directories[name] || Directory.new
end
end
- def serialize_single_file(name)
- basic_serialization = @files[name].serialize
- "#{name}:#{basic_serialization.size}:#{basic_serialization}"
+ def serialize
+ [@files, @directories].map do |entity_store|
+ "#{entity_store.size}:#{entity_store.map(&@serializer).join}"
+ end.join
end
- def serialize
- all_files = @files.map { |name, _| serialize_single_file(name) }.join('')
- serialize_files = "#{@files.size}:#{all_files}"
+ def self.parse(string_data)
+ parsed_directory = Directory.new
- all_directories = @directories.map do |name, directory|
- "#{name}:#{directory.serialize.size}:#{directory.serialize}"
- end
+ files_count, string_data = string_data.split(':', 2)
+ files, string_data = basic_file_parse(files_count.to_i, string_data, File)
+ files.each { |name, file| parsed_directory.add_file(name, file) }
- "#{serialize_files}#{@directories.size}:#{all_directories.join}"
+ directories_count, string_data = string_data.split(':', 2)
+ directories, _ = basic_file_parse(directories_count.to_i, string_data, Directory)
+ directories.each { |name, dir| parsed_directory.add_directory(name, dir) }
+
+ parsed_directory
end
- def self.basicFileParse(files_count, string_data, file_type)
+ private
+
+ def single_serialization(entity_name)
+ basic_serialization = self[entity_name].serialize
+ "#{entity_name}:#{basic_serialization.size}:#{basic_serialization}"
+ end
+
+ def self.basic_file_parse(files_count, string_data, file_type)
files = []
files_count.times do
name, data_length, string_data = string_data.split(':', 3)
data = string_data.slice!(0..data_length.to_i - 1)
files << [name, file_type.parse(data)]
end
[files, string_data]
- end
-
- def self.parse(string_data)
- parsed_directory = Directory.new
-
- files_count, string_data = string_data.split(':', 2)
- files, string_data = basicFileParse(files_count.to_i, string_data, File)
- files.each { |name, file| parsed_directory.add_file(name, file) }
-
- directories_count, string_data = string_data.split(':', 2)
- directories, _ = basicFileParse(directories_count.to_i, string_data, Directory)
- directories.each { |name, dir| parsed_directory.add_directory(name, dir) }
-
- parsed_directory
end
end
end

Здрасти и мерси за коментарите и идеите :)

Направих някои (hopefully) подобрения предвид малкото време. Бърз въпрос - какво правим с променливи като @serializer, които са единни за целия клас или по-точно казано "се конструират" по един и същ образец, но не могат да бъдат дефинирани като класови, понеже използват инстанционни методи?

Има ли нещо очевидно, за което не се сещам (по-вероятно) или ситуацията не е много присъща за езика и не е хубаво да се вкарваме в нея? :)

Поздрави!

Малко късно и след срока, но ... :)

Конкретно от тази променлива не виждам смисъл. Използваш я само на едно място, за да я подадеш на map. Защо просто не направиш serialize така?

[@files, @directories].map do |entity_store|
  serialized_entities = entity_store.map { |name, _| single_serialization(name) }

  "#{entity_store.size}:#{serialized_entities.join}"
end.join

Малко ме обърква начинът, по който сериализираш нещата. Защо изпускаш самия обект, а не викаш serialize директно върху него?

Вече може да разгледаш другите решения за идеи :)

Здрасти,

Защото стават две нива на влагане, а ако се избегне външния map, става повторение - затова потърсих нещо друго :) Другия коментар не съм сигурен дали го разбрах.

Както и да е, TBH точно това го претупах в последния момент, така че не е трудно да се намерят кусури. :) Следващия път по-отрано.