簡體   English   中英

在ruby中將數據移動到循環內部的3個Separate Hashes中

[英]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

1通常在File上調用許多IO方法。 這是可以接受的,因為File.superclass #=> IO

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM