Нужна помощь с использованием Enumerable - PullRequest
0 голосов
/ 23 апреля 2019

Попытка прочитать файл, содержащий список информации, это файл .dtf.Информация в 1 абзаце за пункт.Пример:

ID : 001
category : 2
length : 18.33

ID : 002
category : 1
length : 19.75

ID : 003
category : 1
length : 18.8

ID : 004
category : 3
length : 17.9

ID : 005
category : 3
length : 16.9

ID : 006
category : 2
length : 17.9

ID : 007
category : 3
length : 21.5

ID : 008
category : 1
length : 20.7

ID : 009
category : 1
length : 16.5

ID : 010
category : 1
length : 23

ID : 011
category : 2
length : 18.73

ID : 012
category : 3
length : 17.9

ID : 013
category : 3
length : 23.4

ID : 014
category : 3
length : 17.9

ID : 015
category : 3
length : 20.93

и т. Д.

Необходимо сгруппировать категорию и указать общую длину для каждой группы.Кто-нибудь может помочь?

Успешно сгруппировать категорию, но не удалось получить общую длину.

a = IO.readlines("point.txt")
b = Hash.new(0)
a.each do |v|
  b[v] +=1
end
b.each do |k, v|
  puts "#{k} occurs #{v}"
end
b = Hash.new(0)

Ожидаемый результат:

Category 1 : 5 points
Total length : 98.75

Category 2 : 3 points
Total length : 54.96

Category 3 : 7 points
Total length : 136.43

Ответы [ 4 ]

2 голосов
/ 23 апреля 2019

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

category = nil
h = IO.foreach('temp').
       each_with_object(Hash.new {|h,k| h[k]={points: 0, length: 0}}) do |line,h|
         case line[/\p{L}+/]
         when 'category'
           category = line[/\d+/]
           h[category][:points] += 1
         when 'length'
           h[category][:length] += line[/[\d.]+/].to_f
         end
       end
  #=> {"2"=>{:points=>3, :length=>54.959999999999994},
  #    "1"=>{:points=>5, :length=>98.75},
  #    "3"=>{:points=>7, :length=>136.43}} 

Затем мы можем использовать этот хеш для отображения желаемых результатов.

h.sort_by(&:first).each do |k,v|
  puts "Category #{k} : #{v[:points]} points"      
  puts "Total length : #{v[:length].round(2)}"
  puts      
end

отображает:

Category 1 : 5 points
Total length : 98.75

Category 2 : 3 points
Total length : 54.96

Category 3 : 7 points
Total length : 136.43

IO :: foreach - очень полезный метод.Он не только читает файлы построчно (что может быть важно для больших файлов) и закрывает файл по окончании, но и возвращает перечислитель, когда блок не указан, что позволяет связать его с другими методами. 1 Здесь я приковал его к Enumerable # each_with_object со связанным объектом:

Hash.new { |h,k| h[k] = { points: 0, length: 0 } }

Документ Hash :: new объясняет, что этосоздает пустой хеш с прикрепленным процессором по умолчанию;то есть, это то же самое, что:

h = {}
pr = proc { |h,k| h[k] = { points: 0, length: 0 } }
  #=> #<Proc:0x000059d3963150b0@(irb):84> 
h.default_proc = pr
  #=> #<Proc:0x000059d3963150b0@(irb):84> 

См. Хэш # default_proc = .

Это просто означает, что если h[k] выполняется, когда hне имеет ключа k, h[k] устанавливается равным значению proc, когда он вызывается с аргументами h и k.Например (поскольку h пусто и поэтому не имеет ключей),

h['cat']
  #=> {:points=>0, :length=>0} 
h #=> {"cat"=>{:points=>0, :length=>0}}

Теперь давайте попробуем:

h['dog'][:points] += 1
  #=> 1 
h #=> {"cat"=>{:points=>0, :length=>0}, "dog"=>{:points=>1, :length=>0}} 

Ruby выполняет первое из этих выражений в два шага:

g = h['dog']
  #=> {:points=>0, :length=>0}
g[:points] += 1

При следующем выполнении h['dog'][:points] += 1 процедура по умолчанию не вызывается, поскольку h теперь имеет ключ 'dog'.

Наконец, category необходимо инициализировать (дляЛюбой объект) вне цикла, он приказывает, чтобы его значение сохранилось от одной строки к следующей. 2

1.foreach часто выполняется на File, а не IO.Это допустимо, потому что File является подклассом IO.

2.Если это не сделано, Ruby сначала установит значение переменной category в первой строке файла.После вычисления блока он выходит из области видимости, но в этот момент Ruby не «отменяет определение» переменной;вместо этого по соображениям производительности он устанавливает nil.Поэтому он будет равен nil при чтении второй строки файла и т. Д.

1 голос
/ 23 апреля 2019

Если каждая «точка входа» начинается с ID, вы можете использовать slice_before, чтобы соответствующим образом разделить данные, например ::

IO.foreach('point.txt').slice_before(/^ID/).each do |lines|
   # ...
end

Затем результат может быть сопоставлен с более управляемым объектом, например, с хешем:

points = IO.foreach('point.txt').slice_before(/^ID/).map do |lines|
  lines.each_with_object({}) do |line, h|
    case line
    when /^ID : (.*)/
      h[:id] = $1
    when /^category : (.*)/
      h[:category] = $1.to_i
    when /^length : (.*)/
      h[:length] = $1.to_f
    end
  end
end
#=> [
#     {:id=>"001", :category=>2, :length=>18.33},
#     {:id=>"002", :category=>1, :length=>19.75},
#     # ...
#   ]

Теперь мы можем сгруппировать точки по категориям:

grouped_points = points.group_by { |h| h[:category] }

и распечатайте результаты:

grouped_points.each do |category, points|
  puts "Category #{category} : #{points.length} points"
  puts "Total length : #{ points.sum { |p| p[:length] }.round(2) }"
  puts
end

Выход:

Category 2 : 3 points
Total length : 54.96

Category 1 : 5 points
Total length : 98.75

Category 3 : 7 points
Total length : 136.43

Возможно, вы захотите отсортировать grouped_points.

1 голос
/ 23 апреля 2019

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

В простейшем случае, если структура данных достаточно надежна и длина всегда следует за категорией, анализ может быть таким же тривиальным, как и

text = StringIO.new(<<~DATA)
  ID : 001
  category : 2
  length : 6.30

  ID : 002
  category : 1
  length : 17.9

  ID : 003
  category : 2
  length : 3.70

DATA

categories = Hash.new { |h,k| h[k] = {count: 0, length: 0} }
current_cat = nil

text.each_line do |line|
  next if line.strip.empty?
  key, value = line.split(":").map(&:strip)

  case key
  when "category"
    current_cat = value
    categories[current_cat][:count] += 1
  when "length"
    categories[current_cat][:length] += Float(value)
  end
end

puts categories.inspect # => {"2"=>{:count=>2, :length=>10.0}, "1"=>{:count=>1, :length=>17.9}}

(просто замените stringio на чтение из файла, чтобы сопоставить его с вашим вариантом использования)

0 голосов
/ 23 апреля 2019

Более или менее тот же суп, что и в других ответах.

После прочтения файла a содержит:

#=> ["ID : 001\n", "category : 2\n", "length : 18.33\n", "\n", "ID : 002\n", "category : 1\n", "length : 19.75\n", "\n", "ID : 003\n", "category : 1\n", "length : 18.8\n", "\n", "ID : 004\n", "category : 3\n", "length : 17.9\n", "\n", "ID : 005\n", "category : 3\n", "length : 16.9\n", "\n", "ID : 006\n", "category : 2\n", "length : 17.9\n", "\n", "ID : 007\n", "category : 3\n", "length : 21.5\n", "\n", "ID : 008\n", "category : 1\n", "length : 20.7\n", "\n", "ID : 009\n", "category : 1\n", "length : 16.5\n", "\n", "ID : 010\n", "category : 1\n", "length : 23\n", "\n", "ID : 011\n", "category : 2\n", "length : 18.73\n", "\n", "ID : 012\n", "category : 3\n", "length : 17.9\n", "\n", "ID : 013\n", "category : 3\n", "length : 23.4\n", "\n", "ID : 014\n", "category : 3\n", "length : 17.9\n", "\n", "ID : 015\n", "category : 3\n", "length : 20.93"]

Тогда вам нужно превратить этот беспорядок в более удобный объект, лучше всего использовать Array of Hashes, поэтому:

res = a.map{ |e| e.chomp.gsub(/\s+/, "").split(':') }.reject(&:empty?).each_slice(3).map(&:to_h)
#=> [{"ID"=>"001", "category"=>"2", "length"=>"18.33"}, {"ID"=>"002", "category"=>"1", "length"=>"19.75"}, {"ID"=>"003", "category"=>"1", "length"=>"18.8"}, ...

Может быть, лучше иметь значение length в виде числа с плавающей запятой:

res.map { |h| h['length'] = h['length'].to_f }

Наконец, группировка по "category" и преобразование значений результирующего хеша:

res.group_by { |h| h['category']}.transform_values { |v| [v.size, v.sum { |h| h['length'] }] }
#=> {"2"=>[3, 54.959999999999994], "1"=>[5, 98.75], "3"=>[7, 136.43]}


Один лайнер, просто для удовольствия:
a.map{ |e| e.chomp.gsub(/\s+/, "").split(':') }.reject(&:empty?).each_slice(3).map(&:to_h).tap { |res| res.map { |h| h['length'] = h['length'].to_f } }.group_by { |h| h['category']}.transform_values { |v| [v.size, v.sum { |h| h['length'] }] }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...