Переместить данные в 3 отдельных хэша внутри цикла в ruby - PullRequest
0 голосов
/ 20 ноября 2018

Это только мой второй пост, и я все еще изучаю рубин.Я пытаюсь понять это, основываясь на моих знаниях 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) Можно 'Доступ к хэшу вне моей функции

Вот что у меня в файле:

65101км, Седан, Руководство, 2010,18131A, БУДУЩИЙ, Использовано, 5,5 л / 100км,Toyota, camry, SE, {AC, подогрев сидений, подогрев зеркал, вход без ключа}

купе, 1100 км, авто, заднеприводный, Mercedec, CLK, LX, 18FO724A, 2017, {AC, подогрев сидений, подогрев зеркал, Безключевой доступ, Электропривод сидений}, 6L / 100км, Б / у

AWD, Внедорожник, 0км, авто, новый, Honda, CRV, 8L / 100км, {Подогрев сидений, Подогрев зеркал, Безключевой доступ}, 19BF723A,2018, LE

Теперь моя функция извлекает функции каждой модели автомобиля, но мне нужно хранить эти функции в 3 различных хэшах с тУ него одинаковые ключи, но разные значения.

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

Я пытался переместить данные из одного хэша в другой, я даже пытался сначала сохранить данные в массиве, а затем переместить их в хэш, но я все еще не могу понять, как создавать отдельные хэшивнутри петли.
Спасибо

Ответы [ 3 ]

0 голосов
/ 20 ноября 2018

Надеюсь, я понял, что вы правильно задали вопрос.Я бы сделал это, как показано ниже.Теперь каждый раз, когда вы вызываете это действие, он возвращает хеш с каждым списком в нем.

    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 
0 голосов
/ 21 ноября 2018

Я не до конца понимаю вопрос, но я подумал, что важно предложить, как вы можете решить более фундаментальную проблему: извлечение нужной информации из каждой строки файла эффективным и подобным 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.

0 голосов
/ 20 ноября 2018

Вы можете использовать тот факт, что ваш экземпляр файла является перечисляемым .Это позволяет вам использовать метод 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...