Я не до конца понимаю вопрос, но я подумал, что важно предложить, как вы можете решить более фундаментальную проблему: извлечение нужной информации из каждой строки файла эффективным и подобным Ruby способом.Если у вас есть эта информация в виде массива хешей, по одному хешу на строку, вы можете делать с ней все, что захотите.В качестве альтернативы, вы можете перебирать строки в файле, создавая хеш для каждой строки и выполняя любые необходимые операции, прежде чем переходить к следующей строке.
Будучи новичком в Ruby, вы, несомненно, найдете часть кода нижетрудно понять.Однако, если вы настойчивы, я думаю, вы сможете понять все это, и в процессе узнайте много нового о Ruby.В последнем разделе моего ответа я сделал несколько предложений, которые помогут вам расшифровать код.
Код
words_by_key = {
type: %w| sedan coupe hatchback station suv |,
transmission: %w| auto manual steptronic |,
drivetrain: %w| fwd rwd awd |,
status: %w| used new |,
car_maker: %w| honda toyota mercedes bmw lexus |,
model: %w| camry clk crv |
}
#=> {:type=>["sedan", "coupe", "hatchback", "station", "suv"],
# :transmission=>["auto", "manual", "steptronic"],
# :drivetrain=>["fwd", "rwd", "awd"],
# :status=>["used", "new"],
# :car_maker=>["honda", "toyota", "mercedes", "bmw", "lexus"],
# :model=>["camry", "clk", "crv"]}
WORDS_TO_KEYS = words_by_key.each_with_object({}) { |(k,v),h| v.each { |s| h[s] = k } }
#=> {"sedan"=>:type, "coupe"=>:type, "hatchback"=>:type, "station"=>:type, "suv"=>:type,
# "auto"=>:transmission, "manual"=>:transmission, "steptronic"=>:transmission,
# "fwd"=>:drivetrain, "rwd"=>:drivetrain, "awd"=>:drivetrain,
# "used"=>:status, "new"=>:status,
# "honda"=>:car_maker, "toyota"=>:car_maker, "mercedes"=>:car_maker,
# "bmw"=>:car_maker, "lexus"=>:car_maker,
# "camry"=>:model, "clk"=>:model, "crv"=>:model}
module ExtractionMethods
def km(str)
str[/\A\d+(?=km\z)/]
end
def year(str)
str[/\A\d+{4}\z/]
end
def stock(str)
return nil if str.end_with?('km')
str[/\A\d+\p{Alpha}\p{Alnum}*\z/]
end
def trim(str)
str[/\A\p{Alpha}{2}\z/]
end
def fuel_consumption(str)
str.to_f if str[/\A\d+(?:\.\d+)?(?=l\/100km\z)/]
end
end
class K
include ExtractionMethods
def extract_hashes(fname)
File.foreach(fname).with_object([]) do |line, arr|
line = line.downcase
idx_left = line.index('{')
idx_right = line.index('}')
if idx_left && idx_right
g = { set_of_features: line[idx_left..idx_right] }
line[idx_left..idx_right] = ''
line.squeeze!(',')
else
g = {}
end
arr << line.split(',').each_with_object(g) do |word, h|
word.strip!
if WORDS_TO_KEYS.key?(word)
h[WORDS_TO_KEYS[word]] = word
else
ExtractionMethods.instance_methods.find do |m|
v = public_send(m, word)
(h[m] = v) unless v.nil?
v
end
end
end
end
end
end
Пример
data =<<BITTER_END
65101km,Sedan,Manual,2010,18131A,FWD,Used,5.5L/100km,Toyota,camry,SE,{AC, Heated Seats, Heated Mirrors, Keyless Entry}
coupe,1100km,auto,RWD, Mercedec,CLK,LX ,18FO724A,2017,{AC, Heated Seats, Heated Mirrors, Keyless Entry, Power seats},6L/100km,Used
AWD,SUV,0km,auto,new,Honda,CRV,8L/100km,{Heated Seats, Heated Mirrors, Keyless Entry},19BF723A,2018,LE
BITTER_END
FILE_NAME = 'temp'
File.write(FILE_NAME, data)
#=> 353 (characters written to file)
k = K.new
#=> #<K:0x00000001c257d348>
k.extract_hashes(FILE_NAME)
#=> [{:set_of_features=>"{ac, heated seats, heated mirrors, keyless entry}",
# :km=>"65101", :type=>"sedan", :transmission=>"manual", :year=>"2010",
# :stock=>"18131a", :drivetrain=>"fwd", :status=>"used", :fuel_consumption=>5.5,
# :car_maker=>"toyota", :model=>"camry", :trim=>"se"},
# {:set_of_features=>"{ac, heated seats, heated mirrors, keyless entry, power seats}",
# :type=>"coupe", :km=>"1100", :transmission=>"auto", :drivetrain=>"rwd",
# :model=>"clk", :trim=>"lx", :stock=>"18fo724a", :year=>"2017",
# :fuel_consumption=>6.0, :status=>"used"},
# {:set_of_features=>"{heated seats, heated mirrors, keyless entry}",
# :drivetrain=>"awd", :type=>"suv", :km=>"0", :transmission=>"auto",
# :status=>"new", :car_maker=>"honda", :model=>"crv", :fuel_consumption=>8.0,
# :stock=>"19bf723a", :year=>"2018", :trim=>"le"}]
Объяснение
Во-первых, обратите внимание, что перед выполнением HEREDOC необходимо ввести отступ.
Вы увидите, что метод экземпляра K#extract_hashes
использует IO # foreach для построчного чтения файла. 1
Первым шагом при обработке каждой строки файла является его сокращение.Затем вы захотите разбить строку на запятые, чтобы сформировать массив слов.Однако существует проблема в том, что вы не хотите разбивать запятые между левой и правой скобками ({
и }
), что соответствует клавише :set_of_features
.Я решил разобраться с этим, определив индексы двух фигурных скобок, создав хеш с одним ключом :set_of_features
, удалив эту подстроку из строки и наконец заменив полученную пару смежных запятых на одну запятую:
idx_left = line.index('{')
idx_right = line.index('}')
if idx_left && idx_right
g = { set_of_features: line[idx_left..idx_right] }
line[idx_left..idx_right] = ''
line.squeeze!(',')
else
g = {}
end
См. String для документации методов String
, используемых здесь и в других местах.
Теперь мы можем преобразовать полученный line
в массив слов, разбив назапятые.Если в выводе требуется заглавная буква, это следует сделать после построения хэшей.
Мы будем опираться на только что созданный хеш { set_of_features: line[idx_left..idx_right] }
.После завершения он будет добавлен к возвращаемому массиву.
Каждый элемент (word
) в массиве затем обрабатывается.Если это ключ хеша WORDS_TO_KEYS
, мы устанавливаем
h[WORDS_TO_KEYS[word]] = word
и заканчиваем этим словом.Если нет, мы выполняем каждый из методов экземпляра m
в модуле ExtractionMethods
, пока не будет найден один, для которого m[word]
не равен nil
.Когда это найдено, другая пара ключ-значение добавляется в хеш h
:
h[m] = word
Обратите внимание, что имя каждого метода экземпляра в ExtractionMethods
, который является символом (например, :km
), является ключом в хеше h
.Наличие отдельных методов облегчает отладку и тестирование.
Я мог бы написать:
if (s = km(word))
s
elsif (s = year(word))
s
elsif (s = stock(str))
s
elsif (s = trim(str))
s
elsif (s = fuel_consumption(str))
s
end
, но так как все эти методы принимают один и тот же аргумент, word
, мы можем вместо этого использовать Object# public_send :
a = [:km, :year, :stock, :trim, :fuel_consumption]
a.find do |m|
v = public_send(m, word)
(h[m] = v) unless v.nil?
v
end
Последний трюк - поместить все методы в массиве a
в модуль ExtractionMethods
и включить этот модуль в класс K
.Затем мы можем заменить a
в выражении find
выше на ExtractionMethods.instance_methods
.(См. Module # instance_methods .)
Предположим теперь, что данные изменены, так что добавляются дополнительные поля (например, для "color" или "price").Тогда только необходимые модификации кода - это изменения words_by_key
и / или добавление методов к ExtractionMethods
.
Понимание кода
Может быть полезно запустить код со вставленными операторами puts
.Например,
idx_left = line.index('{')
idx_right = line.index('}')
puts "idx_left=#{idx_left}, idx_left=#{idx_left}"
Там, где код связан , может быть полезно разбить его на временные переменные и вставить операторы puts
.Например, измените
arr << line.split(',').each_with_object(g) do |word, h|
...
на
a = line.split(',')
puts "line.split(',')=#{a}"
enum = a.each_with_object(g)
puts "enum.to_a=#{enum.to_a}"
arr << enum do |word, h|
...
Второй puts
здесь просто для того, чтобы увидеть, какие элементы сгенерирует перечислитель enum
и передаст в блок.
Другой способ сделать это - использовать удобный метод Object # tap , который вставляется между двумя методами:
arr << line.split(',').tap { |a| puts "line.split(',')=#{a}"}.
each_with_object(g) do |word, h|
...
tap
(отличное имя, а?), как здесь используется, просто возвращает свой получатель после отображения его значения.
Наконец, я использовал метод Enumerable # each_with_object в нескольких местах.Это может показаться сложным, но на самом деле это довольно просто.Например,
arr << line.split(',').each_with_object(g) do |word, h|
...
end
фактически эквивалентен:
h = g
arr << line.split(',').each do |word|
...
end
h
1 Многие IO методы обычно вызываются в File ,Это приемлемо, потому что File.superclass #=> IO
.