How to handle combination []+= for auto-vivifying hash in Ruby?
There is no way to define a []+=
method in ruby. What happens when you type
x[y] += z
is
x[y] = x[y] + z
so both the []
and []=
methods are called on x
(and +
is called on x[y]
, which in this case is an AutoHash
). I think that the best way to handle this problem would be to define a +
method on AutoHash
, which will just return it's argument. This will make AutoHash.new[:x] += y
work for just about any type of y
, because the "empty" version of y.class
(''
for strings, 0
for numbers, ...) plus y
will almost always equal y
.
class AutoHash
def +(x); x; end
end
Adding that method will make both of these work:
# Numbers:
a = AutoHash.new
5.times { a[:sum] += 10 }
a[:sum] #=> 50
# Strings:
a = AutoHash.new
5.times { a[:sum] += 'a string ' }
a[:sum] #=> "a string a string a string a string a string "
And by the way, here is a cleaner version of your code:
class AutoHash < Hash
def initialize(args={})
super
@update, @update_index = args[:update], args[:update_key]
end
def [](k)
if has_key? k
super(k)
else
AutoHash.new :update => self, :update_key => k
end
end
def []=(k, v)
@update[@update_index] = self if @update and @update_index
super
end
def +(x); x; end
def self.few(n)
Array.new(n) { AutoHash.new }
end
end
:)
Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP?
Try this:
def hash_with_default_hash
Hash.new { |hash, key| hash[key] = hash_with_default_hash }
end
a = hash_with_default_hash
If the key doesn't exist, then the result of the block will be used as the default value. In this case, the default value is also a hash which uses hashes as its default values.
Multiple initialization of auto-vivifying hashes using a new operator in Ruby
Where you ask for a := b := c := AutoHash.new.few 3
I think (not sure I understand your desire) that you really want a,b,c=Autohash.new.few 3
Why does few take variable args, when you only ever use the first?
I also find your creation of the return value to be confusing, maybe try
def few(n=0)
Array.new(n) { AutoHash.new }
end
Beyond that, it seems like few
should be a class method. a,b,c=AutoHash.few 3
which will work if you defined few on the class:
def AutoHash.few(n=0)
Array.new(n) { AutoHash.new }
end
If a,b,c=AutoHash.few 3
isn't what you're looking for, and you really want to implement your own operator, then check out Hacking parse.y, which was a talk given at RubyConf 2009. You can watch the presentation at http://rubyconf2009.confreaks.com/19-nov-2009-17-15-hacking-parsey-tatsuhiro-ujihisa.html and you can see the slides at http://www.slideshare.net/ujihisa/hacking-parsey-rubyconf-2009
How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?
Ruby 2.3.0 introduced a method called dig
on both Hash
and Array
.
name = params.dig(:company, :owner, :name)
It returns nil
if the key is missing at any level.
If you are using a version of Ruby older than 2.3, you can install a gem such as ruby_dig
or hash_dig_and_collect
, or implement the functionality yourself:
module RubyDig
def dig(key, *rest)
if value = (self[key] rescue nil)
if rest.empty?
value
elsif value.respond_to?(:dig)
value.dig(*rest)
end
end
end
end
if RUBY_VERSION < '2.3'
Array.send(:include, RubyDig)
Hash.send(:include, RubyDig)
end
Ruby Autovivification
I agree with Jonas that you (and Sam) are making this more complicated than it needs to be, but I think even his version is too complicated. I'd just do this:
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
Now you have a hash with total amounts for each unique combination of account, ticker, type and price.
One liner nested hash creation in Ruby? (I come from Perl)
Unfortunately, there is no simple, practical way. A Ruby equivalent would be an ugly, ugly beast like:
((((@date[month] ||= {})[day] ||= {})[hours] ||= {})[min] ||= {})[sec] = 1
There is a way to assign default values for missing keys in hashes, though:
@date = Hash.new { |hash, key| hash[key] = {} }
# @date[:month] is set to a new, empty hash because the key is missing.
@date[:month][:day] = 1
Unfortunately this does not work recursively.
...unless you create it yourself; hooray for Ruby!
class Hash
def self.recursive
new { |hash, key| hash[key] = recursive }
end
end
@date = Hash.recursive
@date[month][day][hours][min][sec] = 1
# @date now equals {month=>{day=>{hours=>{min=>{sec=>1}}}}}
Keep in mind, though, that all unset values are now {}
rather than nil
.
How do I summarize hashes within a hash?
First and foremost don't use upper-case letters for variables in Ruby as you did with SomeHash
and XPath
. When a variable name starts with an upper-case letter, it means it's a constant and you probably didn't want it to be a constant.
each
is not the best way to do this, you can do this much more simply with inject
as in:
countries = {
"Albania"=>"Europe",
"Andorra"=>"Europe",
"Austria"=>"Europe",
"Lebanon"=>"Asia",
"Macau"=>"Asia",
"Malaysia"=>"Asia",
"Papua New Guinea"=>"Asia",
"Jamaica"=>"North America",
"Martinique"=>"North America",
"Argentina"=>"South America",
"Chile"=>"South America",
"Sao Tome and Principe"=>"Africa",
"Senegal"=>"Africa",
"Somalia"=>"Africa"}
by_continents = countries.inject({}) do |memo, (k,v)|
memo[v] ||= []
memo[v] << k
memo
end
The output for this is:
{"Europe"=>["Albania", "Andorra", "Austria"], "Asia"=>["Lebanon", "Macau", "Malaysia", "Papua New Guinea"], "North America"=>["Jamaica", "Martinique"], "South America"=>["Argentina", "Chile"], "Africa"=>["Sao Tome and Principe", "Senegal", "Somalia"]}
You have all countries grouped by continent and you can pick any one of them.
In your code it should be placed like this:
def country
inflation_hash = {}
XPath.match( data, "//country").map do |element|
inflation_hash[element.attributes["name"]] = element.attributes["continent"]
end
by_continents = inflation_hash.inject({}) do |memo, (k,v)|
memo[v] ||= []
memo[v] << k
memo
end
puts by_continents.inspect
by_continents
end
Related Topics
How to Reset Boolean to "Default: False" at End of Day
Ruby ".Downcase! " and "Downcase" Confusion
Why Can't We Override '||' and '&&'
Where Does Rails Store Data Created by Saving Activerecord Objects During Tests
Using the Right Exception Subclass in Ruby
Gem::Ext::Builderror: Error: Failed to Build Gem Native Extension. on Cenos 6.5
Grouping an Array by Comparing 2 Adjacent Elements
Why Do I Get a Encoding::Compatibilityerror with #Inspect
How to Transform the Utf8 Chars to Iso8859-1
Rails Adding Multiple Objects to an Empty Array
Can Anyone Explain This Array Declaration to Me
Validating Phone Number in Ruby
Why Is the Timezone Off in Delayed_Job
Error: Null Value in Column "Id" Violates Not-Null Constraint