How use dig method several for children object - ruby

I have an hash
{:result=>
{:"1"=>
[{:"1"=>1,
:"2"=>"4698192612070913717",
:"5"=>
{:"1"=>{:"1"=>"1.0.0"},
:"2"=>
{:"1"=>1,
:"2"=>"1525341956127",
:"3"=>1000000000,
:"4"=>0,
:"5"=>{:"1"=>1000000000}},
:"3"=>["17"],
:"6"=>"4704522736971289334",
:"8"=>["4618851880555471022"],
:"9"=>[1]},
:"6"=>{:"3"=>{:"2"=>false}, :"4"=>{:"1"=>false}},
:"7"=>{:"1"=>1},
:"8"=>"production"},
{:"1"=>4,
:"2"=>"4700283765268993541",
:"6"=>{:"2"=>{:"1"=>200}, :"3"=>{:"2"=>false}, :"4"=>{:"1"=>false}},
:"8"=>"beta"},
{:"1"=>5,
:"2"=>"4699074054925986704",
:"6"=>{:"2"=>{:"1"=>100}, :"3"=>{:"2"=>false}, :"4"=>{:"1"=>false}},
:"8"=>"alpha"},
{:"1"=>10,
:"2"=>"4697702456121346981",
:"6"=>{:"2"=>{:"1"=>50}, :"3"=>{:"2"=>false}, :"4"=>{:"1"=>false}},
:"8"=>"internal"}],
:"3"=>{:"1"=>true, :"2"=>{:"1"=>{:"1"=>false}, :"2"=>{:"1"=>false}}},
:"4"=>false},
:xsrf=>"AMtNNDFJl06mR54j2zxFjYIYfGQR22sUKA:1528830206790"}
I am looking for simple way to return a value or nil
I have tried this
result[:'result'][:'1'][1].dig(:'5').dig(:'1').dig(:'1')
but it's not working
What can I do to avoid this
if result[:'result'][:'1'][1].dig(:'5')
puts result[:'result'][:'1'][1][:'5'][:'1'][:'1']
end

The idea behind dig is that you can go several levels deep into a hash at the same time and return nil if the key doesn't exist at any level during the 'digging'. So result[:'result'][:'1'][1].dig(:'5', :'1', :'1') will do what you are looking for and clean up your code as well. In fact, you could make it a little safer if you wanted by doing result.dig(:result, :'1', 1, :'5', :'1', :'1')

dig is not a single method, but a family of four methods, all of which made their debut in Ruby v2.3: Array#dig, Hash#dig, Struct#dig and OpenStruct#dig.
For example,
h = { a: [1, { c: 2, d: 3 }], b: 2 }
h.dig(:a, 1, :d)
#=> 3
employs Hash#dig because dig's receiver is a hash. Moreover, one might expect that when, in an intermediate calculation, dig has unearthed [1, { c: 2, d: 3 }] it will pass the shovel to Array#dig for further excavation.
Suppose
h = { a: [1, 2] }
Then
h.dig(:a, 1) #=> 2
h.dig(:a).dig(1) #=> 2
Does that mean the two are equivalent? Try this:
h.dig('cat', 1) #=> nil
h.dig('cat').dig(1) #=> NoMethodError: undefined method `dig' for nil:NilClass
The exception is due to the fact that h.dig('cat') #=> nil and NilClass has no instance method dig, so nil.dig(1) raises the exception. No, the two expressions are not equivalent.
If the value of the variable result is the OPs hash, we have (as pointed out by #Isaiah) the following.
result.dig(:result, :'1', 0, :"5", :"1", :"1")
#=> "1.0.0"
result.dig(:result, :'1', 0, :cat, :"1", :"1")
#=> nil
Note that dig will still raise an exception if the wrong data type is used:
[1, 2].dig(:a)
#=> TypeError: no implicit conversion of Symbol into Integer
To support versions of Ruby prior to 2.3 (where dig is not available) we can write the following, using Enumerable#reduce (aka inject).
arr = [:result, :'1', 0, :"5", :"1", :"1"]
arr.reduce(result) { |memo, obj| memo && memo[obj] }
#=> "1.0.0"
arr = [:result, :'1', 0, :cat, :"1", :"1"]
arr.reduce(result) { |memo, obj| memo && memo[obj] }
#=> nil

Related

Is there a default block argument in Ruby?

I am just starting to do Groovy after mostly doing ruby.
It has a default 'block argument', it, as it were, not officially the terminology for Groovy, but I'm new to Groovy.
(1..10).each {println(it)}
What about Ruby? Is there a default I can use so I don't have to make |my_block_arg| every time?
Thanks!
No, you don't have a "default" in Ruby.
Though, you can do
(1..10).each(&method(:puts))
Like Andrey Deinekos answer explained there is no default. You can set the self context using BasicObject#instance_eval or BasicObject#instance_exec. I don't recommend doing this since it can sometimes result in some unexpected results. However if you know what you're doing the following is still an option:
class Enumerator
def with_ie(&block)
return to_enum(__method__) { each.size } unless block_given?
each { |e| e.instance_eval(&block) }
end
end
(1..10).each.with_ie { puts self }
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 10
#=> 1..10
(1..10).map.with_ie { self * self }
#=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
(-5..5).select.with_ie { positive? }
#=> [1, 2, 3, 4, 5]
If you want to call one method you might as well do (-5..5).select(&:positive?), but when the objects you're iterating over have actual attributes it might be worth the trouble. For example:
people.map.with_ie { "#{id}: #{first_name} - #{last_name}" }
Keep in mind that if you have an local variable id, first_name or last_name in scope those are used instead of the methods on the object. This also doesn't quite work for hashes or Enumerable methods that pass more than one block argument. In this case self is set to an array containing the arguments. For example:
{a: 1, b: 2}.map.with_ie { self }
#=> [[:a, 1], [:b, 2]]
{a: 1, b: 2}.map.with_ie { self[0] }
#=> [:a, :b]
From Ruby 2.7 onwards, you can use numbered block arguments:
(1..10).each { puts _1 }
Granted, this hasn't been very well documented; some references are still using #1, but the above is tested on the official 2.7 version.

How to use &proc argument inside method

Array#max_by returns only a single value, but I want to have all values that have the max value.
hashes = [{a: 1, b:2}, {a:2, b:3}, {a:1, b:3}]
max = hashes.map{|h| h[:b]}.max
hashes.select{|h| h[:b] == max}
# => [{a: 2, b: 3}, {a: 1, b: 3}]
This code works fine, and I want to add it to Array class.
class Array
def max_values_by(&proc)
max = map(&proc).max
# I don't know how to use `select` here.
end
end
How to access the value of the &proc argument?
Use the proc in the block passed to select by calling it with call:
class Array
def max_values_by(&proc)
max = map(&proc).max
select { |h| proc.call(h) == max }
end
end
hashes.max_values_by { |h| h[:b] }
=> [{a: 2, b: 3}, {a: 1, b: 3}]
or with yield, which gives identical results:
def max_values_by(&proc)
max = map(&proc).max
select { |h| yield(h) == max }
end
Although proc.call is a little longer than yield, I prefer it in this case because it makes it clearer that the same block is being used in two places in the method, and because it's weird to use both the implicit block passing of yield and the explicit passing of &proc in the same method.
#DaveSchweisguth suggests a great implementation using select, like you requested. Another way of achieving the same result is by using group_by, like this:
>> hashes.group_by{|h| h[:b]}.max.last
=> [{:a=>2, :b=>3}, {:a=>1, :b=>3}]
or monkey-patched into Array as:
class Array
def max_values_by(&proc)
group_by(&proc).max.last
end
end

In Ruby (1.9.3), why does nil respond to the comparison operator, `<=>`?

To me, a null type being compared to anything else (even another null type) is an undefined operation. Please correct me if I'm wrong there.
Under that assumption, the following makes sense to me:
nil.is_a? Comparable
=> false
nil.respond_to? :<=
=> false
nil.respond_to? :<
=> false
nil.respond_to? :>=
=> false
nil.respond_to? :>
=> false
However, nil does respond to the "spaceship" comparison operator:
nil.respond_to? :<=>
=> true
I cannot think of a situation where comparing nil is even meaningful, let alone practical. Why does nil have this behaviour?
nil in Ruby is a singleton instance of NilClass, which inherits from Object. Object implements <=>, which has its behavior defined as:
Returns 0 if obj and other are the same object or obj == other, otherwise nil. 0 means self is equal to other. 1 means self is bigger than other. Nil means the two values could not be compared.
(See the documentation)
Thus, nil <=> nil returns 0 (they are equivalent), but nil <=> anything_else returns nil, which means "could not be compared".
In Ruby, it is expected that all objects respond to <=> (including nil), but for objects for which it is a nonsensical or undefined operation, the return value is nil, which may then be handled as the calling code best sees fit. In the case of Enumerable's operations like #sort, it raises an exception:
[1, nil].sort
# => ArgumentError: comparison of NilClass with 1 failed
But it needn't necessarily; you could implement your own sort which just moves unsortable values to the beginning of the list:
[1, nil, 2, 3, nil].sort {|a, b| (a <=> b) || -1 }
# => [nil, nil, 1, 2, 3]
How useful is Object#<=> for nil? I think it's only limited by one's imagination.
Example #1
Here's a pedestrian example of how it can be useful. Suppose you wished to sort the array:
arr = [1,nil,3,nil,2]
with all the nils coming first, so it would return:
[nil, nil, 1, 2, 3]
As:
nil<=>nil #=> 0
and, for all non-nil objects a:
nil<=>x #=> nil
x<=>nil #=> nil
we can write:
arr.sort { |a,b| (a<=>b) ? a<=>b : a.nil? ? -1 : 1 }
#=> [nil, nil, 1, 2, 3]
Example #2
Now let's consider a second example that's much more interesting. Suppose we have oversold tickets to a theatre performance and must turn away some patrons, and give them refunds. The hash tickets shows what each person paid for their ticket:
ticket_holders = { 'bob'=>10, 'lucy'=>15, 'cher'=>5, 'arnold'=>12 }
We wish to minimize the refunds issued, but don't want negative publicity from turning away celebrities, given by the following:
celebrities = ['arnold', 'cher']
so we will give them the highest preference. We therefore wish to sort the ticket_holders by descending value, except we want key-value pairs whose key is in celebrities to come first. That is, we want the result to be:
['cher', 'arnold', 'lucy', 'bob']
or
['arnold', 'cher', 'lucy', 'bob']
Let's go for a general solution:
module Enumerable
def sort_by_nils_first
sort do |a,b|
av = yield(a)
bv = yield(b)
(av<=>bv) ? av<=>bv : av.nil? ? -1 : 1
end
end
end
which we apply thus:
ticket_holders.sort_by_nils_first { |name,price|
celebrities.include?(name) ? nil : -price }.map(&:first)
#=> ["arnold", "cher", "lucy", "bob"]
Considering alone the number of celebrities in the world, and how they are treated, I think this is a pretty useful method.
Applied to the earlier example, we obtain:
[1,nil,3,nil,2].sort_by_nils_first(&:itself)
#=> [nil, nil, 1, 2, 3]
where I've used Object#itself from v2.2.
sort_by_nils_first could of course be modified to return an Enumerator when no block is given, to make it comparable to Enumerable#sort_by.

Differences between [1,2,3].to_enum and [1,2,3].enum_for in Ruby

In Ruby I'm trying to understand between the to_enum and enum_for methods. Before I my question, I've provided some sample code and two examples to help w/ context.
Sample code:
# replicates group_by method on Array class
class Array
def group_by2(&input_block)
return self.enum_for(:group_by2) unless block_given?
hash = Hash.new {|h, k| h[k] = [] }
self.each { |e| hash[ input_block.call(e) ] << e }
hash
end
end
Example # 1:
irb (main)> puts [1,2,3].group_by2.inspect
=> #<Enumerator: [1, 2, 3]:group_by2>
In example #1: Calling group_by on the array [1,2,3], without passing in a block, returns an enumerator generated with the command self.enum_for(:group_by_2).
Example #2
irb (main)> puts [1,2,3].to_enum.inspect
=> #<Enumerator: [1, 2, 3]:each>
In example #2, the enumerator is generated by calling the to_enum method on the array [1,2,3]
Question:
Do the enumerators generates in examples 1 and 2, behave differently in any way? I can see from the inspected outputs that they show slightly different labels, but I can find any difference in the enumerators' behavior.
# Output for example #1
#<Enumerator: [1, 2, 3]:each> # label reads ":each"
# Output for example #2
#<Enumerator: [1, 2, 3]:group_by2> # label reads ":group_by2"
p [1, 2, 3].to_enum
p [1, 2, 3].enum_for
--output:--
#<Enumerator: [1, 2, 3]:each>
#<Enumerator: [1, 2, 3]:each>
From the docs:
to_enum
Creates a new Enumerator which will enumerate by calling method on
obj, passing args if any.
...
enum_for
Creates a new Enumerator which will enumerate by calling method on
obj, passing args if any.
ruby is a language that often has method names that are synonyms.
Followup question:
Does the symbol in the command [1,2,3].to_enum(:foo) serve a purpose,
other than replacing :each with :foo in the output?
Yes. By default, ruby hooks up the enumerator to the receiver's each() method. Some classes do not have an each() method, for instance String:
str = "hello\world"
e = str.to_enum
puts e.next
--output:--
1.rb:3:in `next': undefined method `each' for "helloworld":String (NoMethodError)
from 1.rb:3:in `<main>
to_enum() allows you to specify the method you would like the enumerator to use:
str = "hello\nworld"
e = str.to_enum(:each_line)
puts e.next
--output:--
hello
Now, suppose you have the array [1, 2, 3], and you want to to create an enumerator for your array. An array has an each() method, but instead of creating an enumerator with each(), which will return each of the elements in the array, then end; you want to create an enumerator that starts over from the beginning of the array once it reaches the end?
e = [1, 2, 3].to_enum(:cycle)
10.times do
puts e.next()
end
--output:--
1
2
3
1
2
3
1
2
3
1

Why does .map produce a row of nils when used to enumerate over hashes?

test =
{:content=>"type_name", :content_length=>9, :array_index=>0},
{:content=>"product_id", :content_length=>10, :array_index=>1},
{:content=>"First Item", :content_length=>10, :array_index=>0},
{:content=>"1111", :content_length=>4, :array_index=>1}
pp test.map {|x| puts x} #=>
{:content=>"type_name", :content_length=>9, :array_index=>0}
{:content=>"product_id", :content_length=>10, :array_index=>1}
{:content=>"First Item", :content_length=>10, :array_index=>0}
{:content=>"1111", :content_length=>4, :array_index=>1}
[nil, nil, nil, nil]
What is the cause of that array of nils? The map works perfectly, but then it causes these nils!
The trouble is that #map is designed to transform an array into a different array. Generally, the block of #map will not have side effects. Here's a use of #map to double all the numbers in an array:
[1, 2, 3].map { |n| n * 2} # => [2, 4, 6]
If the purpose of your loop is solely to have side effects (such as printing the elements), you want #each instead:
[1, 2, 3].each { |n| puts n }
# => 1
# => 2
# => 3
In this case, we don't care about the return value of #each. All we care about is that each number gets printed.
Argh what a stupid error!
This fixes it:
test.map {|x| puts x}
I was pretty printing the puts statement, and irb, trying to be helpful, returned nil four times!

Resources