How ||= and unless include? differe? - ruby

What are the differences between the 2 following line ?
# settings and globals are Hash
#settings[:xvfb] = globals[:xvfb] unless settings.include?(:xvfb)
#settings[:xvfb] ||= globals[:xvfb]
Are they equivalent ?

Nope, they are not equivalent. They differ in handling falsey values.
globals = { foo: 'bar' }
h = { foo: nil }
h.include?(:foo) # => true
h[:foo] = globals[:foo] unless h.include?(:foo)
h # => {:foo=>nil}
h = { foo: nil }
h[:foo] ||= globals[:foo]
h # => {:foo=>"bar"}

Related

(Not #dig!) How to determine a key *exists* in a deeply nested Ruby Hash?

Is there an "easy" way, short of hand-writing the kind of nested Hash/Array traversal performed by Hash#dig, that I can determine if a key is present in a deeply nested Hash? Another way to ask this is to say "determine if any value is assigned".
There is a difference between a Hash having nothing assigned, or it having an explicit nil assigned - especially if the Hash were constructed with a different missing key default value than nil!
h = { :one => { :two => nil }}
h.dig(:one, :two).nil? # => true; but :two *is* present; it is assigned "nil".
h[:one].key?(:two) # => true, because the key exists
h = { :one => {}}
h.dig(:one, :two).nil? # => true; :two *is not* present; no value is assigned.
h[:one].key?(:two) # => FALSE, because the key does not exist
If you are purely checking the existence of a key, you can combine dig and key?. Use key? on the final or last key in your series of keys.
input_hash = {
hello: {
world: {
existing: nil,
}
}
}
# Used !! to make the result boolean
!!input_hash.dig(:hello, :world)&.key?(:existing) # => true
!!input_hash.dig(:hello, :world)&.key?(:not_existing) # => false
!!input_hash.dig(:hello, :universe)&.has_key?(:not_existing) # => false
Inspired by your core extension suggestion I updated the implementation a bit to better mimic that of #dig
requires 1+ arguments
raises TypeError if the dig does not return nil, the resulting object does not respond to dig? and there are additional arguments to be "dug"
module Diggable
def dig?(arg,*args)
return self.member?(arg) if args.empty?
if val = self[arg] and val.respond_to?(:dig?)
val.dig?(*args)
else
val.nil? ? false : raise(TypeError, "#{val.class} does not have a #dig? method")
end
end
end
[Hash,Struct,Array].each { |klass| klass.send(:include,Diggable) }
class Array
def dig?(arg,*args)
return arg.abs < self.size if args.empty?
super
end
end
if defined?(OpenStruct)
class OpenStruct
def dig?(arg,*args)
self.to_h.dig?(arg,*args)
end
end
end
Usage
Foo = Struct.new(:a)
hash = {:one=>1, :two=>[1, 2, 3], :three=>[{:one=>1, :two=>2}, "hello", Foo.new([1,2,3]), {:one=>{:two=>{:three=>3}}}]}
hash.dig? #=> ArgumentError
hash.dig?(:one) #=> true
hash.dig?(:two, 0) #=> true
hash.dig?(:none) #=> false
hash.dig?(:none, 0) #=> false
hash.dig?(:two, -1) #=> true
hash.dig?(:two, 10) #=> false
hash.dig?(:three, 0, :two) #=> true
hash.dig?(:three, 0, :none) #=> false
hash.dig?(:three, 2, :a) #=> true
hash.dig?(:three, 3, :one, :two, :three, :f) #=> TypeError
Example
For reference - taking the unusual step of answering my own question ;-) - here's one of several ways I could solve this if I just wanted to write lots of Ruby.
def dig?(obj, *args)
arg = args.shift()
return case obj
when Array
if args.empty?
arg >= 0 && arg <= obj.size
else
dig?(obj[arg], *args)
end
when Hash
if args.empty?
obj.key?(arg)
else
dig?(obj[arg], *args)
end
when nil
false
else
raise ArgumentError
end
end
Of course, one could also have opened up classes like Array and Hash and added #dig? to those, if you prefer core extensions over explicit methods:
class Hash
def dig?(*args)
arg = args.shift()
if args.empty?
self.key?(arg)
else
self[arg]&.dig?(*args) || false
end
end
end
class Array
def dig?(*args)
arg = args.shift()
if args.empty?
arg >= 0 && arg <= self.size
else
self[arg]&.dig?(*args) || false
end
end
end
...which would raise NoMethodError rather than ArgumentError if the #dig? arguments led to a non-Hash/Array node.
Obviously it would be possible to compress those down into more cunning / elegant solutions that use fewer lines, but the above has the benefit of IMHO being pretty easy to read.
In the scope of the original question, though, the hope was to lean more on anything Ruby has out-of-the-box. We've collectively acknowledged early-on that there is no single-method solution, but the answer from #AmazingRein gets close by reusing #dig to avoid recursion. We might adapt that as follows:
def dig?(obj, *args)
last_arg = args.pop()
obj = obj.dig(*args) unless args.empty?
return case obj
when Array
last_arg >= 0 && last_arg <= obj.size
when Hash
obj.key?(last_arg)
when nil
false
else
raise ArgumentError
end
end
...which isn't too bad, all things considered.
# Example test...
hash = {:one=>1, :two=>[1, 2, 3], :three=>[{:one=>1, :two=>2}, "hello", {:one=>{:two=>{:three=>3}}}]}
puts dig?(hash, :one)
puts dig?(hash, :two, 0)
puts dig?(hash, :none)
puts dig?(hash, :none, 0)
puts dig?(hash, :two, -1)
puts dig?(hash, :two, 10)
puts dig?(hash, :three, 0, :two)
puts dig?(hash, :three, 0, :none)
puts dig?(hash, :three, 2, :one, :two, :three)
puts dig?(hash, :three, 2, :one, :two, :none)
Here is a concise way of doing it which works with nested Array and Hash (and any other object that responds to fetch).
def deep_fetch? obj, *argv
argv.each do |arg|
return false unless obj.respond_to? :fetch
obj = obj.fetch(arg) { return false }
end
true
end
obj = { hello: [ nil, { world: nil } ] }
deep_fetch? obj, :hell # => false
deep_fetch? obj, :hello, 0 # => true
deep_fetch? obj, :hello, 2 # => false
deep_fetch? obj, :hello, 0, :world # => false
deep_fetch? obj, :hello, 1, :world # => true
deep_fetch? obj, :hello, :world
TypeError (no implicit conversion of Symbol into Integer)
The previous code raises an error when accessing an Array element with a non-Integer index (just like Array#dig), which sometimes is not the behavior one is looking for. The following code works well in all cases, but the rescue is not a good practice:
def deep_fetch? obj, *argv
argv.each { |arg| obj = obj.fetch(arg) } and true rescue false
end
obj = { hello: [ nil, { world: nil } ] }
deep_fetch? obj, :hell # => false
deep_fetch? obj, :hello, 0 # => true
deep_fetch? obj, :hello, 2 # => false
deep_fetch? obj, :hello, 0, :world # => false
deep_fetch? obj, :hello, 1, :world # => true
deep_fetch? obj, :hello, :world # => false

Safely assign value to nested hash using Hash#dig or Lonely operator(&.)

h = {
data: {
user: {
value: "John Doe"
}
}
}
To assign value to the nested hash, we can use
h[:data][:user][:value] = "Bob"
However if any part in the middle is missing, it will cause error.
Something like
h.dig(:data, :user, :value) = "Bob"
won't work, since there's no Hash#dig= available yet.
To safely assign value, we can do
h.dig(:data, :user)&.[]=(:value, "Bob") # or equivalently
h.dig(:data, :user)&.store(:value, "Bob")
But is there better way to do that?
It's not without its caveats (and doesn't work if you're receiving the hash from elsewhere), but a common solution is this:
hash = Hash.new {|h,k| h[k] = h.class.new(&h.default_proc) }
hash[:data][:user][:value] = "Bob"
p hash
# => { :data => { :user => { :value => "Bob" } } }
And building on #rellampec's answer, ones that does not throw errors:
def dig_set(obj, keys, value)
key = keys.first
if keys.length == 1
obj[key] = value
else
obj[key] = {} unless obj[key]
dig_set(obj[key], keys.slice(1..-1), value)
end
end
obj = {d: 'hey'}
dig_set(obj, [:a, :b, :c], 'val')
obj #=> {d: 'hey', a: {b: {c: 'val'}}}
interesting one:
def dig_set(obj, keys, value)
if keys.length == 1
obj[keys.first] = value
else
dig_set(obj[keys.first], keys.slice(1..-1), value)
end
end
will raise an exception anyways if there's no [] or []= methods.
I found a simple solution to set the value of a nested hash, even if a parent key is missing, even if the hash already exists. Given:
x = { gojira: { guitar: { joe: 'charvel' } } }
Suppose you wanted to include mario's drum to result in:
x = { gojira: { guitar: { joe: 'charvel' }, drum: { mario: 'tama' } } }
I ended up monkey-patching Hash:
class Hash
# ensures nested hash from keys, and sets final key to value
# keys: Array of Symbol|String
# value: any
def nested_set(keys, value)
raise "DEBUG: nested_set keys must be an Array" unless keys.is_a?(Array)
final_key = keys.pop
return unless valid_key?(final_key)
position = self
for key in keys
return unless valid_key?(key)
position[key] = {} unless position[key].is_a?(Hash)
position = position[key]
end
position[final_key] = value
end
private
# returns true if key is valid
def valid_key?(key)
return true if key.is_a?(Symbol) || key.is_a?(String)
raise "DEBUG: nested_set invalid key: #{key} (#{key.class})"
end
end
usage:
x.nested_set([:instrument, :drum, :mario], 'tama')
usage for your example:
h.nested_set([:data, :user, :value], 'Bob')
any caveats i missed? any better way to write the code without sacrificing readability?
Searching for an answer to a similar question I developmentally stumbled upon an interface similar to #niels-kristian's answer, but wanted to also support a namespace definition parameter, like an xpath.
def deep_merge(memo, source)
# From: http://www.ruby-forum.com/topic/142809
# Author: Stefan Rusterholz
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
memo.merge!(source, &merger)
end
# Like Hash#dig, but for setting a value at an xpath
def bury(memo, xpath, value, delimiter=%r{\.})
xpath = xpath.split(delimiter) if xpath.respond_to?(:split)
xpath.map!{|x|x.to_s.to_sym}.push(value)
deep_merge(memo, xpath.reverse.inject { |memo, field| {field.to_sym => memo} })
end
Nested hashes are sort of like xpaths, and the opposite of dig is bury.
irb(main):014:0> memo = {:test=>"value"}
=> {:test=>"value"}
irb(main):015:0> bury(memo, 'test.this.long.path', 'value')
=> {:test=>{:this=>{:long=>{:path=>"value"}}}}
irb(main):016:0> bury(memo, [:test, 'this', 2, 4.0], 'value')
=> {:test=>{:this=>{:long=>{:path=>"value"}, :"2"=>{:"4.0"=>"value"}}}}
irb(main):017:0> bury(memo, 'test.this.long.path.even.longer', 'value')
=> {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}}, :"2"=>{:"4.0"=>"value"}}}}
irb(main):018:0> bury(memo, 'test.this.long.other.even.longer', 'other')
=> {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}, :other=>{:even=>{:longer=>"other"}}}, :"2"=>{:"4.0"=>"value"}}}}
A more ruby-helper-like version of #niels-kristian answer
You can use it like:
a = {}
a.bury!([:a, :b], "foo")
a # => {:a => { :b => "foo" }}
class Hash
def bury!(keys, value)
key = keys.first
if keys.length == 1
self[key] = value
else
self[key] = {} unless self[key]
self[key].bury!(keys.slice(1..-1), value)
end
self
end
end

Why this (dead simple) ruby regex behaves like this?

Why "whatever".gsub(/.*/, "bien") outputs "bienbien" instead of just "bien"?
I'm completely lost here :S Anyone could point me in the right direction?
You can see what's happening using a block:
>> 'foo'.sub(/.*/) { |m| p m; 'bar' }
"foo"
=> "bar"
>> 'foo'.gsub(/.*/) { |m| p m; 'bar' }
"foo"
""
=> "barbar"
>> 'foo'.gsub(/^.*/) { |m| p m; 'bar' }
"foo"
=> "bar"
>> 'foo'.gsub(/^.*$/) { |m| p m; 'bar' }
"foo"
=> "bar"
>> 'foo'.gsub(/.*$/) { |m| p m; 'bar' }
"foo"
""
=> "barbar"
>> 'foo'.gsub(/.+/) { |m| p m; 'bar' }
"foo"
=> "bar"
Put another way, gsub will continue matching, and matches an empty string at the very end a line. (And that is arguably a bug.)

How to replace all nil value with "" in a ruby hash recursively?

str = "<a><b><c></c></b></a>"
hash = Hash.from_xml(str)
# => {"a"=>{"b"=>{"c"=>nil}}}
How can I replace all nils in a Hash to "" so that the hash becomes:
{"a"=>{"b"=>{"c"=>""}}}
Here is a recursive method that does not change the original hash.
Code
def denilize(h)
h.each_with_object({}) { |(k,v),g|
g[k] = (Hash === v) ? denilize(v) : v.nil? ? '' : v }
end
Examples
h = { "a"=>{ "b"=>{ "c"=>nil } } }
denilize(h) #=> { "a"=>{ "b"=>{ "c"=>"" } } }
h = { "a"=>{ "b"=>{ "c"=>nil , "d"=>3, "e"=>nil}, "f"=>nil } }
denilize(h) #=> { "a"=>{ "b"=>{ "c"=>"" , "d"=>3, "e"=>""}, "f"=>"" } }
this will destroy the original hash and will not work with hashes with infinite recursion.
def nil2empty(hash)
hash.keys.each do |key|
if hash[key].kind_of? Hash
nil2empty(hash[key])
else
hash[key] = '' if hash[key].nil?
end
end
true # of course, what else? :P
end
example of usage:
hash
=> {"a"=>{"b"=>{"c"=>nil}}}
nil2empty(hash)
=> true
hash
=> {"a"=>{"b"=>{"c"=>""}}}
I know this is not the answer you are expecting, but if you could handle a value instead of "" , this code works
eval({"a"=>{"b"=>{"c"=>nil}}}.to_s.gsub("nil", "1")) #=> returns a hash #{"a"=>{"b"=>{"c"=>1}}}

How to handle combination []+= for auto-vivifying hash in Ruby?

In order to implement auto-vivification of Ruby hash, one can employ the following class
class AutoHash < Hash
def initialize(*args)
super()
#update, #update_index = args[0][:update], args[0][:update_key] unless
args.empty?
end
def [](k)
if self.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 few(n=0)
Array.new(n) { AutoHash.new }
end
end
This class allows to do the following things
a = AutoHash.new
a[:a][:b] = 1
p a[:c] # => {} # key :c has not been created
p a # => {:a=>{:b=>1}} # note, that it does not have key :c
a,b,c = AutoHash.new.few 3
b[:d] = 1
p [a,b,c] # => [{}, {:d=>1}, {}] # hashes are independent
There is a bit more advanced definition of this class proposed by Joshua, which is a bit hard for me to understand.
Problem
There is one situation, where I think the new class can be improved. The following code fails with the error message NoMethodError: undefined method '+' for {}:AutoHash
a = AutoHash.new
5.times { a[:sum] += 10 }
What would you do to handle it? Can one define []+= operator?
Related questions
Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP?
Multiple initialization of auto-vivifying hashes using a new operator in Ruby
ruby hash initialization r
still open: How to create an operator for deep copy/cloning of objects 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
:)
What I think you want is this:
hash = Hash.new { |h, k| h[k] = 0 }
hash['foo'] += 3
# => 3
That will return 3, then 6, etc. without an error, because the the new value is default assigned 0.
require 'xkeys' # on rubygems.org
a = {}.extend XKeys::Hash
a[:a, :b] = 1
p a[:c] # => nil (key :c has not been created)
p a # => { :a => { :b => 1 } }
a.clear
5.times { a[:sum, :else => 0] += 10 }
p a # => { :sum => 50 }

Resources