Ce n'est que mon deuxième article et j'apprends toujours le rubis. J'essaie de comprendre cela sur la base de mes connaissances Java, mais je n'arrive pas à faire les choses correctement.

Ce que je dois faire, c'est: j'ai une fonction qui lit un fichier ligne par ligne et extrait différentes caractéristiques de la voiture de chaque ligne, par exemple:

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

Maintenant, pour chaque ligne, je dois stocker les fonctionnalités extraites dans des hachages séparés auxquels je pourrai accéder plus tard dans mon programme.

Les problèmes auxquels je suis confronté: 1) J'écrase les fonctionnalités dans le même hachage 2) Impossible d'accéder au hachage en dehors de ma fonction

C'est ce qu'il y a dans mon dossier:

65101km, Berline, Manuelle, 2010, 18131A, FWD, Occasion, 5.5L / 100km, Toyota, camry, SE, {AC, Sièges chauffants, Miroirs chauffants, Entrée sans clé}

coupé, 1100km, auto, RWD, Mercedec, CLK, LX, 18FO724A, 2017, {AC, Sièges chauffants, Miroirs chauffants, Entrée sans clé, Sièges électriques}, 6L / 100km, Occasion

TI, SUV, 0 km, auto, neuf, Honda, CRV, 8 L / 100 km, {Sièges chauffants, Rétroviseurs chauffants, Entrée sans clé}, 19BF723A, 2018, LE

Maintenant, ma fonction extrait les caractéristiques de chaque modèle de voiture, mais je dois stocker ces caractéristiques dans 3 hachages différents avec les mêmes clés mais des valeurs différentes.

listing = Hash.new(0)
  listing = { kilometers: km, type: t, transmission: trans, drivetrain: dt, status: status, car_maker: car_maker }

J'ai essayé de déplacer les données d'un hachage à un autre, j'ai même essayé de stocker les données dans un tableau d'abord, puis de les déplacer vers le hachage, mais je ne comprends toujours pas comment créer des hachages séparés dans une boucle.
Merci

0
tester_s25 20 nov. 2018 à 18:56

3 réponses

Meilleure réponse

Je ne comprends pas entièrement la question mais j'ai pensé qu'il était important de suggérer comment vous pourriez traiter un problème plus fondamental: extraire les informations souhaitées de chaque ligne du fichier d'une manière efficace et semblable à Ruby. Une fois que vous avez ces informations, sous la forme d'un tableau de hachages, un hachage par ligne, vous pouvez en faire ce que vous voulez. Vous pouvez également parcourir les lignes du fichier en boucle, créer un hachage pour chaque ligne et effectuer les opérations souhaitées avant de passer à la ligne suivante.

Étant nouveau sur Ruby, vous trouverez sans aucun doute une partie du code ci-dessous difficile à comprendre. Si vous persévérez, cependant, je pense que vous serez en mesure de tout comprendre et, ce faisant, en apprendrez beaucoup sur Ruby. J'ai fait quelques suggestions dans la dernière section de ma réponse pour vous aider à déchiffrer le code.

Code

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

Exemple

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"}]

Explication

Tout d'abord, notez que le HEREDOC doit être sans retrait avant d'être exécuté.

Vous verrez que la méthode d'instance K#extract_hashes utilise IO # foreach pour lire le fichier ligne par ligne. 1

La première étape du traitement de chaque ligne du fichier consiste à le réduire. Vous voudrez ensuite diviser la chaîne par des virgules pour former un tableau de mots. Il y a un problème, cependant, en ce que vous ne voulez pas diviser par des virgules qui sont entre une accolade gauche et droite ({ et }), ce qui correspond à la clé :set_of_features . J'ai décidé de gérer cela en déterminant les indices des deux accolades, en créant un hachage avec la clé unique :set_of_features, en supprimant cette sous-chaîne de la ligne et en remplaçant enfin une paire résultante de virgules adjacentes par une seule virgule:

  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

Voir String pour la documentation des String méthodes utilisées ici et ailleurs.

Nous pouvons maintenant convertir le line résultant en un tableau de mots en le divisant par des virgules. Si une majuscule est souhaitée dans la sortie, cela doit être fait après la construction des hachages.

Nous allons construire sur le hachage { set_of_features: line[idx_left..idx_right] } qui vient d'être créé. Une fois terminé, il sera ajouté au tableau renvoyé.

Chaque élément (word) du tableau est ensuite traité. Si c'est une clé du hachage WORDS_TO_KEYS nous définissons

h[WORDS_TO_KEYS[word]] = word

Et sont finis avec ce mot. Sinon, nous exécutons chacune des méthodes d'instance m dans le module ExtractionMethods jusqu'à ce qu'il en trouve une pour laquelle m[word] n'est pas nil. Lorsque cela est trouvé, une autre paire clé-valeur est ajoutée au hachage h:

h[m] = word

Notez que le nom de chaque méthode d'instance dans ExtractionMethods, qui est un symbole (par exemple, :km), est une clé dans le hachage h. Le fait d'avoir des méthodes séparées facilite le débogage et les tests.

J'aurais pu écrire:

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

Mais comme toutes ces méthodes prennent le même argument, word, nous pouvons à la place utiliser Objet # 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

Un dernier ajustement est de mettre toutes les méthodes du tableau a dans un module ExtractionMethods et d'inclure ce module dans la classe K. Nous pouvons alors remplacer a dans l'expression find ci-dessus par ExtractionMethods.instance_methods. (Voir Module # instance_methods.)

Supposons maintenant que les données soient modifiées afin que des champs supplémentaires soient ajoutés (par exemple, pour "couleur" ou "prix"). Ensuite, les seules modifications du code requises sont des modifications de words_by_key et / ou l'ajout de méthodes à ExtractionMethods.

Comprendre le code

Il peut être utile d'exécuter le code avec les instructions puts insérées. Par exemple,

idx_left = line.index('{')
idx_right = line.index('}')
puts "idx_left=#{idx_left}, idx_left=#{idx_left}"

Lorsque le code est chaîné , il peut être utile de le séparer avec des variables temporaires et d'insérer des instructions puts. Par exemple, changez

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|
  ...

Le deuxième puts ici est simplement pour voir quels éléments l'énumérateur enum va générer et passer au bloc.

Une autre façon de faire est d'utiliser la méthode pratique Object #tap, qui est inséré entre deux méthodes:

arr << line.split(',').tap { |a| puts "line.split(',')=#{a}"}.
            each_with_object(g) do |word, h|
              ...

tap (grand nom, hein?), Tel qu'utilisé ici, renvoie simplement son récepteur après avoir affiché sa valeur.

Enfin, j'ai utilisé la méthode Enumerable # each_with_object à quelques endroits. Cela peut sembler complexe mais c'est en fait assez simple. Par exemple,

arr << line.split(',').each_with_object(g) do |word, h|
  ...
end

Équivaut effectivement à:

h = g
arr << line.split(',').each do |word|
  ...
end
h

1 De nombreuses méthodes IO sont généralement appelées sur Fichier. Ceci est acceptable car File.superclass #=> IO.

0
Cary Swoveland 16 juin 2019 à 16:28

Vous pouvez tirer parti du fait que votre instance de fichier est un énumérable. Cela vous permet d'exploiter le inject, et vous pouvez semer cela avec un hachage vide. collector dans ce cas est le hachage qui est transmis au fur et à mesure que l'itération se poursuit. Assurez-vous (implicitement, en ayant collector d'être la dernière ligne du bloc) de retourner la valeur de collector car la méthode inject l'utilisera pour alimenter l'itération suivante. C'est un truc assez puissant!

Je pense que c'est à peu près ce que vous recherchez. J'ai utilisé model comme clé dans le hachage et set_of_features comme données.

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
0
Volte 20 nov. 2018 à 16:18

J'espère que j'ai bien compris votre question. Je ferais ceci comme ci-dessous. Désormais, chaque fois que vous appellerez cette action, elle renverra un hachage avec chaque liste.

    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

Ensuite, partout où vous l'utilisez, vous pouvez le faire.

listnings = convertListings2Catalogue("somefile.txt")
listnings.first #to get the first listing 
0
kallelundgren93 20 nov. 2018 à 16:40