Ruby - Access multidimensional hash and avoid access nil object [duplicate] - ruby

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Ruby: Nils in an IF statement
Is there a clean way to avoid calling a method on nil in a nested params hash?
Let's say I try to access a hash like this:
my_hash['key1']['key2']['key3']
This is nice if key1, key2 and key3 exist in the hash(es), but what if, for example key1 doesn't exist?
Then I would get NoMethodError: undefined method [] for nil:NilClass. And nobody likes that.
So far I deal with this doing a conditional like:
if my_hash['key1'] && my_hash['key1']['key2'] ...
Is this appropriate, is there any other Rubiest way of doing so?

There are many approaches to this.
If you use Ruby 2.3 or above, you can use dig
my_hash.dig('key1', 'key2', 'key3')
Plenty of folks stick to plain ruby and chain the && guard tests.
You could use stdlib Hash#fetch too:
my_hash.fetch('key1', {}).fetch('key2', {}).fetch('key3', nil)
Some like chaining ActiveSupport's #try method.
my_hash.try(:[], 'key1').try(:[], 'key2').try(:[], 'key3')
Others use andand
myhash['key1'].andand['key2'].andand['key3']
Some people think egocentric nils are a good idea (though someone might hunt you down and torture you if they found you do this).
class NilClass
def method_missing(*args); nil; end
end
my_hash['key1']['key2']['key3']
You could use Enumerable#reduce (or alias inject).
['key1','key2','key3'].reduce(my_hash) {|m,k| m && m[k] }
Or perhaps extend Hash or just your target hash object with a nested lookup method
module NestedHashLookup
def nest *keys
keys.reduce(self) {|m,k| m && m[k] }
end
end
my_hash.extend(NestedHashLookup)
my_hash.nest 'key1', 'key2', 'key3'
Oh, and how could we forget the maybe monad?
Maybe.new(my_hash)['key1']['key2']['key3']

You could also use Object#andand.
my_hash['key1'].andand['key2'].andand['key3']

Conditions my_hash['key1'] && my_hash['key1']['key2'] don't feel DRY.
Alternatives:
1) autovivification magic. From that post:
def autovivifying_hash
Hash.new {|ht,k| ht[k] = autovivifying_hash}
end
Then, with your example:
my_hash = autovivifying_hash
my_hash['key1']['key2']['key3']
It's similar to the Hash.fetch approach in that both operate with new hashes as default values, but this moves details to the creation time.
Admittedly, this is a bit of cheating: it will never return 'nil' just an empty hash, which is created on the fly. Depending on your use case, this could be wasteful.
2) Abstract away the data structure with its lookup mechanism, and handle the non-found case behind the scenes. A simplistic example:
def lookup(model, key, *rest)
v = model[key]
if rest.empty?
v
else
v && lookup(v, *rest)
end
end
#####
lookup(my_hash, 'key1', 'key2', 'key3')
=> nil or value
3) If you feel monadic you can take a look at this, Maybe

Related

Loop method until it returns falsey

I was trying to make my bubble sort shorter and I came up with this
class Array
def bubble_sort!(&block)
block = Proc.new { |a, b| a <=> b } unless block_given?
sorted = each_index.each_cons(2).none? do |i, next_i|
if block.call(self[i], self[next_i]) == 1
self[i], self[next_i] = self[next_i], self[i]
end
end until sorted
self
end
def bubble_sort(&prc)
self.dup.bubble_sort!(&prc)
end
end
I don't particularly like the thing with sorted = --sort code-- until sorted.
I just want to run the each_index.each_cons(s).none? code until it returns true. It's a weird situation that I use until, but the condition is a code I want to run. Any way, my try seems awkward, and ruby usually has a nice concise way of putting things. Is there a better way to do this?
This is just my opinion
have you ever read the ruby source code of each and map to understand what they do?
No, because they have a clear task expressed from the method name and if you test them, they will take an object, some parameters and then return a value to you.
For example if I want to test the String method split()
s = "a new string"
s.split("new")
=> ["a ", " string"]
Do you know if .split() takes a block?
It is one of the core ruby methods, but to call it I don't pass a block 90% of the times, I can understand what it does from the name .split() and from the return value
Focus on the objects you are using, the task the methods should accomplish and their return values.
I read your code and I can not refactor it, I hardly can understand what the code does.
I decided to write down some points, with possibility to follow up:
1) do not use the proc for now, first get the Object Oriented code clean.
2) split bubble_sort! into several methods, each one with a clear task
def ordered_inverted! (bubble_sort!), def invert_values, maybe perform a invert_values until sorted, check if existing methods already perform this sorting functionality
3) write specs for those methods, tdd will push you to keep methods simple and easy to test
4) If those methods do not belong to the Array class, include them in the appropriate class, sometimes overly complicated methods are just performing simple String operations.
5) Reading books about refactoring may actually help more then trying to force the usage of proc and functional programming when not necessary.
After looking into it further I'm fairly sure the best solution is
loop do
break if condition
end
Either that or the way I have it in the question, but I think the loop do version is clearer.
Edit:
Ha, a couple weeks later after I settled for the loop do solution, I stumbled into a better one. You can just use a while or until loop with an empty block like this:
while condition; end
until condition; end
So the bubble sort example in the question can be written like this
class Array
def bubble_sort!(&block)
block = Proc.new { |a, b| a <=> b } unless block_given?
until (each_index.each_cons(2).none? do |i, next_i|
if block.call(self[i], self[next_i]) == 1
self[i], self[next_i] = self[next_i], self[i]
end
end); end
self
end
def bubble_sort(&prc)
self.dup.bubble_sort!(&prc)
end
end

Is there a { |x| x } shorthand in ruby?

I often use .group_by{ |x| x } and .find{ |x| x }
The latter is to find the first item in an array which is true.
Currently I'm just using .compact.first but I feel like there must be an elegant way to use find here, like find(&:to_bool) or .find(true) that I'm missing.
Using .find(&:nil?) works but is the opposite of what I want, and I couldn't find a method that was the opposite of #find or #detect, or a method like #true?
So is there a more elegant way to write .find{ |x| x }? If not, I'll stick with .compact.first
(I know compact won't remove false but that's not a problem for me, also please avoid rails methods for this)
Edit: For my exact case it is used on arrays of only strings and nils e.g.
[nil, "x", nil, nil, nil, nil, "y", nil, nil, nil, nil] => "x"
If you do not care about what is returned you can sometimes use the hash method.
Thw feature you are asking for is not available in Ruby yet, however. it is present in the Ruby road-map:
https://bugs.ruby-lang.org/issues/6373
Expected to be implemented before 2035-12-25, can you wait?
That being said, how much typing is group_by{|x|x} ?
Edit:
As Stefan pointed out, my answer is now longer valid for Ruby 2.2 and above since the introduction of Object#itself.
There’s not.
If tap worked without a block you could do:
array.detect(&:tap)
But it doesn’t. Either way, I think what you have is extremely concise, idiomatic, and happens to be the same number of characters as the non-working above alternative, and thus you should stick with that:
array.compact.first
You could monkey-patch your way to getting a shorter version, but then it becomes unclear to anyone otherwise familiar with Ruby, which probably isn’t worth the minor “savings”.
As a curiosity, if you happened to want array.detect { |x| !x } (the opposite) you could do:
array.detect(&:!)
This works because !x is actually shorthand for x.!. Of course this would only ever give you nil or false, which is probably not very useful.
No, there is not. I personally have a utility library I include in all my projects which has something like
IDENTITIY = -> x { x }
Then you would have
.group_by(&IDENTITY)
There is also Object#itself that simply returns self:
.group_by(&:itself)
Although the tag is for ruby - with Rails (more specifically ActiveSupport) you are given a method presence which will work for anything that responds positively to present? (that would exclude blank strings, arrays, hashes, etc):
array.find(&:presence)
It's not quite equivalent to the preferred result, but it will work for most cases I've come across.
I frequently use group_by, map, select, sort_by, and other various hash methods. I discovered this useful little extension yesterday by fiddling around with another answer on a similar question:
class Hash
def method_missing(n)
if has_key? n
self[n]
else
raise NoMethodError
end
end
end
For any hash created by ruby, or any data that has been jsonified by as_json, this addition allows me to write code which is a little shorter. Example:
# make yellow cells
yellow = red = false
tube_steps_status.group_by(&:step_ordinal).each do |type|
group = type.last.select(&:completed).sort_by(&:completed)
red = true if group.last.step_status == 'red' if group.any?
yellow = true if group.map(&:step_status).include?('red')
end
tube_summary_status = 'yellow' if yellow unless red

Ruby method chaining - `tap` with replacement [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Is there a `pipe` equivalent in ruby?
I'm looking at the tap method in Ruby - but unfortunately the object returned from the passed block is not passed on. What method do I use to have the object passed on?
Here's what I'm trying to (unsuccessfully) do:
obj.tap{ |o| first_transform(o) }.tap{ |o| second_transform(o)}
This is, of course, equivalent to second_transform(first_transform(o)). I'm just asking how to do it the first way.
Doing this is trivial with lists:
list.map{ |item| first_transform(item) }.map{ |item| second_transform(item)}
Why isn't it as easy with objects?
class Object
def as
yield self
end
end
With this, you can do [1,2,3].as{|l| l << 4}.as{|l| l << 5}
You could also consider to make #first_transform and #second_transform instance methods of the item's class (and return the transformed item of course).
These methods definitions should look like this:
class ItemClass
# If you want your method to modify the object you should
# add a bang at the end of the method name: first_transform!
def first_transform
# ... Manipulate the item (self) then return it
transformed_item
end
end
This way you could simply chain the methods calls this way:
list.map {|item| item.first_transform.second_transform }
It even reads better in my humble opinion ;)
The simple answer is tap doesn't do what you think it does.
tap is called on an object and will always return that same object.
As a simple example of taps use
"foo".tap do |foo|
bar(foo)
end
This still returns "foo"
In your example you have an object, and you want to apply two functions to it in succession.
second_transform(first_transform(obj))
UPDATED:
So I guess I'd ask why you want to chain in this way.
obj.do{|o| first_transform(o)}.do{|o| second_transform(o)}
Is that really more clear than
second_transform(first_transform(obj))
Lets take an example I often use
markdown(truncate(#post.content))
or
truncated_post = truncate(#post.content)
markdown(truncated_post)
I guess it depends on the nature of your transform

What is an elegant way in Ruby to tell if a variable is a Hash or an Array?

To check what #some_var is, I am doing a
if #some_var.class.to_s == 'Hash'
I am sure that there is a more elegant way to check if #some_var is a Hash or an Array.
You can just do:
#some_var.class == Hash
or also something like:
#some_var.is_a?(Hash)
It's worth noting that the "is_a?" method is true if the class is anywhere in the objects ancestry tree. for instance:
#some_var.is_a?(Object) # => true
the above is true if #some_var is an instance of a hash or other class that stems from Object. So, if you want a strict match on the class type, using the == or instance_of? method is probably what you're looking for.
First of all, the best answer for the literal question is
Hash === #some_var
But the question really should have been answered by showing how to do duck-typing here.
That depends a bit on what kind of duck you need.
#some_var.respond_to?(:each_pair)
or
#some_var.respond_to?(:has_key?)
or even
#some_var.respond_to?(:to_hash)
may be right depending on the application.
Usually in ruby when you are looking for "type" you are actually wanting the "duck-type" or "does is quack like a duck?". You would see if it responds to a certain method:
#some_var.respond_to?(:each)
You can iterate over #some_var because it responds to :each
If you really want to know the type and if it is Hash or Array then you can do:
["Hash", "Array"].include?(#some_var.class) #=> check both through instance class
#some_var.kind_of?(Hash) #=> to check each at once
#some_var.is_a?(Array) #=> same as kind_of
Hash === #some_var #=> return Boolean
this can also be used with case statement
case #some_var
when Hash
...
when Array
...
end
You can use instance_of?
e.g
#some_var.instance_of?(Hash)
I use this:
#var.respond_to?(:keys)
It works for Hash and ActiveSupport::HashWithIndifferentAccess.
In practice, you will often want to act differently depending on whether a variable is an Array or a Hash, not just mere tell. In this situation, an elegant idiom is the following:
case item
when Array
#do something
when Hash
#do something else
end
Note that you don't call the .class method on item.
If you want to test if an object is strictly or extends a Hash, use:
value = {}
value.is_a?(Hash) || value.is_a?(Array) #=> true
But to make value of Ruby's duck typing, you could do something like:
value = {}
value.respond_to?(:[]) #=> true
It is useful when you only want to access some value using the value[:key] syntax.
Please note that Array.new["key"] will raise a TypeError.
irb(main):005:0> {}.class
=> Hash
irb(main):006:0> [].class
=> Array

Elegant way of duck-typing strings, symbols and arrays?

This is for an already existing public API that I cannot break, but I do wish to extend.
Currently the method takes a string or a symbol or anything else that makes sense when passed as the first parameter to send
I'd like to add the ability to send a list of strings, symbols, et cetera. I could just use is_a? Array, but there are other ways of sending lists, and that's not very ruby-ish.
I'll be calling map on the list, so the first inclination is to use respond_to? :map. But a string also responds to :map, so that won't work.
How about treating them all as Arrays? The behavior you want for Strings is the same as for an Array containing only that String:
def foo(obj, arg)
[*arg].each { |method| obj.send(method) }
end
The [*arg] trick works because the splat operator (*) turns a single element into itself or an Array into an inline list of its elements.
Later
This is basically just a syntactically sweetened version or Arnaud's answer, though there are subtle differences if you pass an Array containing other Arrays.
Later still
There's an additional difference having to do with foo's return value. If you call foo(bar, :baz), you might be surprised to get [baz] back. To solve this, you can add a Kestrel:
def foo(obj, arg)
returning(arg) do |args|
[*args].each { |method| obj.send(method) }
end
end
which will always return arg as passed. Or you could do returning(obj) so you could chain calls to foo. It's up to you what sort of return-value behavior you want.
A critical detail that was overlooked in all of the answers: strings do not respond to :map, so the simplest answer is in the original question: just use respond_to? :map.
Since Array and String are both Enumerables, there's not an elegant way to say "a thing that's an Enumberable, but not a String," at least not in the way being discussed.
What I would do is duck-type for Enumerable (responds_to? :[]) and then use a case statement, like so:
def foo(obj, arg)
if arg.respond_to?(:[])
case arg
when String then obj.send(arg)
else arg.each { |method_name| obj.send(method_name) }
end
end
end
or even cleaner:
def foo(obj, arg)
case arg
when String then obj.send(arg)
when Enumerable then arg.each { |method| obj.send(method) }
else nil
end
end
Perhaps the question wasn't clear enough, but a night's sleep showed me two clean ways to answer this question.
1: to_sym is available on String and Symbol and should be available on anything that quacks like a string.
if arg.respond_to? :to_sym
obj.send(arg, ...)
else
# do array stuff
end
2: send throws TypeError when passed an array.
begin
obj.send(arg, ...)
rescue TypeError
# do array stuff
end
I particularly like #2. I severely doubt any of the users of the old API are expecting TypeError to be raised by this method...
Let's say your function is named func
I would make an array from the parameters with
def func(param)
a = Array.new
a << param
a.flatten!
func_array(a)
end
You end up with implementing your function func_array for arrays only
with func("hello world") you'll get a.flatten! => [ "hello world" ]
with func(["hello", "world"] ) you'll get a.flatten! => [ "hello", "world" ]
Can you just switch behavior based on parameter.class.name? It's ugly, but if I understand correctly, you have a single method that you'll be passing multiple types to - you'll have to differentiate somehow.
Alternatively, just add a method that handles an array type parameter. It's slightly different behavior so an extra method might make sense.
Use Marshal to serialize your objects before sending these.
If you don't want to monkeypatch, just massage the list to an appropriate string before the send. If you don't mind monkeypatching or inheriting, but want to keep the same method signature:
class ToBePatched
alias_method :__old_takes_a_string, :takes_a_string
#since the old method wanted only a string, check for a string and call the old method
# otherwise do your business with the map on things that respond to a map.
def takes_a_string( string_or_mappable )
return __old_takes_a_string( string_or_mappable ) if String === string_or_mappable
raise ArgumentError unless string_or_mappable.responds_to?( :map )
# do whatever you wish to do
end
end
Between those 3 types I'd do this
is_array = var.respond_to?(:to_h)
is_string = var.respond_to?(:each_char)
is_symbol = var.respond_to?(:to_proc)
Should give a unique answer for [], :sym, 'str'

Resources