【问题标题】:Move data into 3 Separate Hashes inside loop in ruby将数据移动到 ruby​​ 循环内的 3 个单独的哈希中
【发布时间】:2025-11-28 23:10:02
【问题描述】:

这只是我的第二篇文章,我还在学习 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,轿车,手动,2010,18131A,FWD,二手,5.5L/100km,丰田,凯美瑞,SE,{AC, 加热座椅、加热后视镜、无钥匙进入}

coupe,1100km,auto,RWD, Mercedec,CLK,LX ,18FO724A,2017,{AC, Heated 座椅,加热后视镜,无钥匙进入,电动座椅},6L/100km,二手

AWD,SUV,0km,auto,new,Honda,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 }

我尝试将数据从一个散列移动到另一个散列,我什至尝试先将数据存储在一个数组中,然后将其移动到散列,但我仍然不知道如何在循环中创建单独的散列。
谢谢

【问题讨论】:

  • 您能添加一个预期或首选输出的示例吗?
  • 是的,当然!

标签: ruby-on-rails ruby hash


【解决方案1】:

我不完全理解这个问题,但我认为建议您如何处理一个更基本的问题很重要:以有效且类似于 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的key

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,我们可以改为使用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 中。然后我们可以将上面find 表达式中的a 替换为ExtractionMethods.instance_methods。 (见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会生成什么元素并传递给block。

另一种方法是使用方便的方法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 #=&gt; IO.

【讨论】:

  • 谢谢你这么详细的回答,很有帮助
【解决方案2】:

您可以利用您的文件实例是enumerable 的事实。这允许您利用 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

【讨论】:

  • 嘿,谢谢你的回答,这会创建单独的哈希吗?在我的情况下,我需要 3 个哈希,它们都具有相同的键但不同的值
  • @SaraMoufarrej 啊,所以你想要一个哈希数组,其中每个哈希的所有值都是在循环的每次迭代中构造的?那么,换句话说,每行一个哈希?
  • 是的,就是这样!
【解决方案3】:

希望我理解你的问题是正确的。 我会像下面那样做。现在,每次您调用此操作时,它都会返回一个包含每个列表的哈希。

    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 

【讨论】:

  • 嘿,我收到语法错误q3.rb:22: syntax error, unexpected ':', expecting '}' listings.push { kilometers: km, type: t, transmission: t...
  • 试试 :kilometers => km 等等!
  • 是的,我仍然遇到同样的错误q3.rb:22: syntax error, unexpected =&gt;, expecting &amp;. or :: or '[' or '.' ...h { :kilometers =&gt; km, :type =&gt; t, :transmission =&gt; trans, :... ...
  • 啊我的错这样做:listing = { km: km, transmission: tm ........} Listings.push Listing
  • 更新了我的答案,这不会对我产生错误,所以希望它对你有用!