[英]Move data into 3 Separate Hashes inside loop in ruby
这只是我的第二篇文章,我还在学习红宝石。 我试图根据我的Java知识来解决这个问题,但我似乎无法做到这一点。
我需要做的是:我有一个逐行读取文件的函数,并从每一行中提取不同的汽车特征,例如:
def convertListings2Catalogue (fileName)
f = File.open(fileName, "r")
f.each_line do |line|
km=line[/[0-9]+km/]
t = line[(Regexp.union(/sedan/i, /coupe/i, /hatchback/i, /station/i, /suv/i))]
trans = ....
end end
现在,对于每一行,我需要将提取的功能存储到我可以在程序中稍后访问的单独哈希中。
我面临的问题:1)我正在覆盖相同哈希中的功能2)无法访问我的函数外的哈希
那个在我的文件中:
65101km,Sedan,Manual,2010,18131A,FWD,二手,5.5L / 100km,Toyota,camry,SE,{AC,加热座椅,加热后视镜,无钥匙进入}
轿跑车,1100km,汽车,RWD,Mercedec,CLK,LX,18FO724A,2017,{AC,加热座椅,加热后视镜,无钥匙进入,电动座椅},6L / 100km,二手
AWD,SUV,0km,汽车,新款,本田,CRV,8L / 100km,{加热座椅,加热后视镜,无钥匙进入},19BF723A,2018,LE
现在我的函数提取每个汽车模型的功能,但我需要将这些功能存储在具有相同键但不同值的3个不同哈希中。
listing = Hash.new(0)
listing = { kilometers: km, type: t, transmission: trans, drivetrain: dt, status: status, car_maker: car_maker }
我尝试将数据从一个哈希移动到另一个哈希,我甚至尝试先将数据存储在数组中然后将其移动到哈希,但我仍然无法弄清楚如何在循环内创建单独的哈希。
谢谢
您可以利用文件实例是可枚举的事实。 这允许您利用inject
方法,并且可以使用空哈希对其进行种子处理。 在这种情况下, collector
是在迭代继续时传递的哈希。 一定要(隐含地,通过让collector
成为块的最后一行)返回收集器的值,因为inject
方法将使用它来提供给下一次迭代。 这是一些非常强大的东西!
我认为这大致是你想要的。 我使用model
作为哈希中的键,并使用set_of_features
作为数据。
def convertListings2Catalogue (fileName)
f = File.open(fileName, "r")
my_hash = f.inject({}) do |collector, line|
km=line[/[0-9]+km/]
t = line[(Regexp.union(/sedan/i, /coupe/i, /hatchback/i, /station/i, /suv/i))]
trans = line[(Regexp.union(/auto/i, /manual/i, /steptronic/i))]
dt = line[(Regexp.union(/fwd/i, /rwd/i, /awd/i))]
status = line[(Regexp.union(/used/i, /new/i))]
car_maker = line[(Regexp.union(/honda/i, /toyota/i, /mercedes/i, /bmw/i, /lexus/i))]
stock = line.scan(/(\d+[a-z0-9]+[a-z](?<!km\b))(?:,|$)/i).first
year = line.scan(/(\d{4}(?<!km\b))(?:,|$)/).first
trim = line.scan(/\b[a-zA-Z]{2}\b/).first
fuel = line.scan(/[\d.]+L\/\d*km/).first
set_of_features = line.scan(/\{(.*?)\}/).first
model = line[(Regexp.union(/camry/i, /clk/i, /crv/i))]
collector[model] = set_of_features
collector
end
end
希望我理解你的问题是正确的。 我会像下面这样做。 现在,每当您调用此操作时,它将返回包含每个列表的哈希值。
def convertListings2Catalogue (fileName)
listings = []
f = File.open(fileName, "r")
f.each_line do |line|
km=line[/[0-9]+km/]
t = line[(Regexp.union(/sedan/i, /coupe/i, /hatchback/i, /station/i, /suv/i))]
trans = line[(Regexp.union(/auto/i, /manual/i, /steptronic/i))]
dt = line[(Regexp.union(/fwd/i, /rwd/i, /awd/i))]
status = line[(Regexp.union(/used/i, /new/i))]
car_maker = line[(Regexp.union(/honda/i, /toyota/i, /mercedes/i, /bmw/i, /lexus/i))]
stock = line.scan(/(\d+[a-z0-9]+[a-z](?<!km\b))(?:,|$)/i).first
year = line.scan(/(\d{4}(?<!km\b))(?:,|$)/).first
trim = line.scan(/\b[a-zA-Z]{2}\b/).first
fuel = line.scan(/[\d.]+L\/\d*km/).first
set_of_features = line.scan(/\{(.*?)\}/).first
model = line[(Regexp.union(/camry/i, /clk/i, /crv/i))]
listing = { kilometers: km, type: t, transmission: trans, drivetrain: dt, status: status, car_maker: car_maker }
listings.push listing
return listings
end
end
然后,只要你使用它,你就可以做到。
listnings = convertListings2Catalogue("somefile.txt")
listnings.first #to get the first listing
我并不完全理解这个问题,但我认为建议如何处理一个更基本的问题很重要:以有效和类似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
并完成了这个词。 如果没有,我们执行模块ExtractionMethods
中的每个实例方法m
,直到找到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
,我们可以改用对象#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
。 然后我们可以用ExtractionMethods.instance_methods
替换上面的find
表达式中的a
。 (参见Module#instance_methods 。)
现在假设数据已更改,以便添加其他字段(例如,“颜色”或“价格”)。 然后,对所需代码的唯一修改是对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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.