Рубиновая автовивификация
Я пытался использовать автовивификацию в ruby, чтобы выполнить простую консолидацию записей по этому вопросу:
2009-08-21|09:30:01|A1|EGLE|Eagle Bulk Shpg|BUY|6000|5.03
2009-08-21|09:30:35|A2|JOYG|Joy Global Inc|BUY|4000|39.76
2009-08-21|09:30:35|A2|LEAP|Leap Wireless|BUY|2100|16.36
2009-08-21|09:30:36|A1|AINV|Apollo Inv Cp|BUY|2300|9.15
2009-08-21|09:30:36|A1|CTAS|Cintas Corp|SELL|9800|27.83
2009-08-21|09:30:38|A1|KRE|SPDR KBW Regional Banking ETF|BUY|9200|21.70
2009-08-21|09:30:39|A1|APA|APACHE CORPORATION|BUY|5700|87.18
2009-08-21|09:30:40|A1|FITB|Fifth Third Bancorp|BUY|9900|10.86
2009-08-21|09:30:40|A1|ICO|INTERNATIONAL COAL GROUP, INC.|SELL|7100|3.45
2009-08-21|09:30:41|A1|NLY|ANNALY CAPITAL MANAGEMENT. INC.|BUY|3000|17.31
2009-08-21|09:30:42|A2|GAZ|iPath Dow Jones - AIG Natural Gas Total Return Sub-Index ETN|SELL|6600|14.09
2009-08-21|09:30:44|A2|CVBF|Cvb Finl|BUY|1100|7.64
2009-08-21|09:30:44|A2|JCP|PENNEY COMPANY, INC.|BUY|300|31.05
2009-08-21|09:30:36|A1|AINV|Apollo Inv Cp|BUY|4500|9.15
так, например, я хочу, чтобы запись для A1 AINV BUY 9.15 имела в общей сложности 6800. Это идеальная проблема для использования автовивификации. Вот мой код:
#!/usr/bin/ruby
require 'facets'
h = Hash.autonew
File.open('trades_long.dat','r').each do |line|
@date,@time,@account,@ticker,@desc,@type,amount,@price = line.chomp.split('|')
if @account != "account"
puts "#{amount}"
h[@account][@ticker][@type][@price] += amount
end
#puts sum.to_s
end
Проблема не в том, как я пытаюсь суммировать значение в h[@account][@ticker][@type][@price], это дает мне эту ошибку:
6000
/usr/local/lib/ruby/gems/1.9.1/gems/facets-2.7.0/lib/core/facets/hash/op_add.rb:8:in `merge': can't convert String into Hash (TypeError)
from /usr/local/lib/ruby/gems/1.9.1/gems/facets-2.7.0/lib/core/facets/hash/op_add.rb:8:in `+'
from ./trades_consolidaton.rb:13
from ./trades_consolidaton.rb:8:in `each'
from ./trades_consolidaton.rb:8
Я пытался использовать разные методы "автовивификации" безрезультатно. Это не произойдет в Perl! Аутовивификация будет знать, что вы пытаетесь сделать. Рубин, кажется, не имеет этой функции.
Так что мой вопрос на самом деле заключается в том, как мне выполнить простую "консолидацию" записей в ruby. В частности, как я могу получить сумму для чего-то вроде:
ч [@account] [@ тикер] [@ тип] [@ цена]
Большое спасибо за вашу помощь!!
Просто чтобы уточнить решение Гленна. Это было бы прекрасно, за исключением того, что дает (с некоторыми изменениями, чтобы использовать стандартную библиотеку CSV в ruby 1.9:
CSV.foreach("trades_long.dat", :col_sep => "|") do |row|
date,time,account,ticker,desc,type,amount,price = *row
records[[account,ticker,type,price]] += amount
end
выдает следующую ошибку:
TypeError: String can't be coerced into Fixnum
from (irb):64:in `+'
from (irb):64:in `block in irb_binding'
from /usr/local/lib/ruby/1.9.1/csv.rb:1761:in `each'
from /usr/local/lib/ruby/1.9.1/csv.rb:1197:in `block in foreach'
from /usr/local/lib/ruby/1.9.1/csv.rb:1335:in `open'
from /usr/local/lib/ruby/1.9.1/csv.rb:1196:in `foreach'
from (irb):62
from /usr/local/bin/irb:12:in `<main>'
4 ответа
Я согласен с Джонасом в том, что вы (и Сэм) усложняете это, чем нужно, но я думаю, что даже его версия слишком сложна. Я бы просто сделал это:
require 'fastercsv'
records = Hash.new(0)
FasterCSV.foreach("trades_long.dat", :col_sep => "|") do |row|
date,time,account,ticker,desc,type,amount,price = row.fields
records[[account,ticker,type,price]] += amount.to_f
end
Теперь у вас есть хэш с общими суммами для каждой уникальной комбинации аккаунта, тикера, типа и цены.
Если вы хотите, чтобы хеш-компоновщик работал таким образом, вам придется переопределить +
семантика.
Например, это прекрасно работает:
class HashBuilder
def initialize
@hash = {}
end
def []=(k,v)
@hash[k] = v
end
def [](k)
@hash[k] ||= HashBuilder.new
end
def +(val)
val
end
end
h = HashBuilder.new
h[1][2][3] += 1
h[1][2][3] += 3
p h[1][2][3]
# prints 4
По сути, вы пытаетесь применить +
оператор хэш.
>> {} + {}
NoMethodError: undefined method `+' for {}:Hash
from (irb):1
Однако в гранях {
>> require 'facets'
>> {1 => 10} + {2 => 20}
=> {1 => 10, 2 => 20}
>> {} + 100
TypeError: can't convert Fixnum into Hash
from /usr/lib/ruby/gems/1.8/gems/facets-2.7.0/lib/core/facets/hash/op_add.rb:8:in `merge'
from /usr/lib/ruby/gems/1.8/gems/facets-2.7.0/lib/core/facets/hash/op_add.rb:8:in `+'
from (irb):6
>> {} += {1 => 2}
=> {1=>2}
>>
Если вы хотите переопределить семантику + для своего хэша в этом случае, вы можете сделать:
class Hash; def +(v); v; end; end
Поместите этот фрагмент перед вашим исходным образцом, и все должно быть хорошо. Имейте в виду, что вы изменяете определенное поведение для + (примечание + не определено в Hash, его подтягивают с фасетами)
Похоже, вы делаете это сложнее, чем должно быть. Я хотел бы использовать гем FasterCSV и Enumerable# внедрить что-то вроде этого:
require 'fastercsv'
records=FasterCSV.read("trades_long.dat", :col_sep => "|")
records.sort_by {|r| r[3]}.inject(nil) {|before, curr|
if !before.nil? && curr[3]==before[3]
curr[6]=(curr[6].to_i+before[6].to_i).to_s
records.delete(before)
end
before=curr
}
Для других, которые находят свой путь здесь, теперь есть еще один вариант:
require 'xkeys' # on rubygems.org
h = {}.extend XKeys::Hash
...
# Start with 0.0 (instead of nil) and add the amount
h[@account, @ticker, @type, @price, :else => 0.0] += amount.to_f
Это создаст навигационную структуру. (Традиционный кеинг с массивами [@account, @ticker, @type, @price]
как предложено ранее, может быть лучше, это конкретное приложение). XKeys
автоматически оживляет при записи, а не при чтении, поэтому запрос структуры о несуществующих элементах не изменит структуру.