In a pure ruby script I have this:
result = JSON.parse result.body_str
count = result && result["ke1"] && result["ke1"]["key2"] && result["ke1"]["key2"]["key3"] && result["ke1"]["key2"]["key3"]["key4"] ?
result["key1"]["key2"]["key3"]["key4"].to_i :
123
Is there any way to simplify this?
count = result["key1"]["key2"]["key3"]["key4"].to_i rescue 123
if you want to make a private method for better readability, you could do
def count(result)
result["key1"]["key2"]["key3"]["key4"].to_i
rescue NoMethodError
123
end
I add the NoMethodError to limit the errors that the rescue can swallow. Despite arguments about
using exceptions for flow control, I prefer this for readability. In a small function or one liner, it technically doesn't even change the flow, as it all remains contained in one location.
If it is used inside a tight loop with millions of records, you may want to compare with other solutions using a profiler, but you have to make that call based on the actual usage. If this is used on a bit of code that may run 5 times a day, stick with what's easier to read and maintain.
I would write it like this, and put it in a module to be included as required.
Code
def value_at_deep_key(hash, path)
path.each_with_index.reduce(hash) do |current, (segment, i) |
case c = current[segment]
when Hash then c
else (i==path.size-1) ? (current.key?(segment) ? c : :NO_MATCH) : {}
end
end
end
Examples
value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b, :c]) #=> "cat"
value_at_deep_key({a: {b: {c: false}}}, [:a, :b, :c]) #=> false
value_at_deep_key({a: {b: {c: nil}}}, [:a, :b, :c]) #=> nil
value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b]) #=> {:c=>"cat"}
value_at_deep_key({a: {b: {c: "cat"}}}, [:a]) #=> {:b=>{:c=>"cat"}}
value_at_deep_key({z: {b: {c: "cat"}}}, []) #=> {:z=>{:b=>{:c=>"cat"}}}
value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c]) #=> :NO_MATCH
value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c, :d]) #=> :NO_MATCH
One could then write:
val = value_at_deep_key(hash, path)
(val = 123) if (val == :NO_MATCH)
If the value for the last key could not be nil,
else (i==path.size-1) ? (current.key?(segment) ? c : :NO_MATCH) : {}
could be replaced with:
else (i==path.size-1) ? c : {}
in which case nil would be returned when there is no match.
I occasionally define a method like this
def value_at_deep_key hash, path, default=nil
path.inject(hash) {|current,segment| current && current[segment]} || default
end
This uses inject to grab each level of the hash in turn.
You would use like this
value_at_deep_key(result, %w(key1 key2 key3 key4), 123)
Personally I don't like the use of rescue for this sort of thing - it can mask errors.
The accepted answer doesn't work very well:
Here is the code:
def value_at_deep_key(hash, path, default=nil)
path.inject(hash) {|current,segment| current && current[segment]} || default
end
Here are some results:
1)--------------------
h = {
'key1' => {'key2' => {'key3' => {'key4' => 3}}}
}
p value_at_deep_key(h, %w[key1 key2 key3 key4], 123)
--output:--
3
2)--------------------
h = {
'key1' => 1,
'key2' => 2,
'key3' => 3,
'key4' => 4,
}
p value_at_deep_key(h, %w[key1 key2 key3 key4], 123)
--output:--
1.rb:16:in `[]': no implicit conversion of String into Integer (TypeError)
from 1.rb:16:in `block in value_at_deep_key'
from 1.rb:16:in `each'
from 1.rb:16:in `inject'
from 1.rb:16:in `value_at_deep_key'
from 1.rb:19:in `<main>'
3)---------------------
h = {
'key1' => {'key2' => {'key3' => 4}}
}
p value_at_deep_key(h, %w[key1 key2 key3 key4], 123)
--output:--
1.rb:16:in `[]': no implicit conversion of String into Integer (TypeError)
The following answer seems to work better:
def value_at_deep_key(hash, key_sequence, default=nil)
return "No keys to lookup!" if key_sequence.empty?
value = hash
key_sequence.each do |key|
case value
when Hash
value = value[key]
else
value = nil
break
end
end
value.nil? ? default : Integer(value) #A found value of nil produces the default, which is
#also the case when one of the keys doesn't exist in the Hash.
#Because to_i() will silently convert a found string with no leading numbers to 0,
#use Integer() instead, which will throw a descriptive error when trying to convert any String(or Hash or Array) to an int.
end
--output:--
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> `Integer': invalid value for Integer(): "cat" (ArgumentError)
p value_at_deep_key({a: {b: {c: false}}}, [:a, :b, :c], 123) #=> `Integer': can't convert false into Integer (TypeError)
p value_at_deep_key({a: {b: {c: nil}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b], 123) #=> `Integer': can't convert Hash into Integer (TypeError
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a], 123) #=> `Integer': can't convert Hash into Integer (TypeError)
p value_at_deep_key({z: {b: {c: "cat"}}}, [], 123) #=> "No keys to lookup!"
p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c, :d], 123) #=> 123
p value_at_deep_key(
{'key1' => {'key2' => {'key3' => {'key4' => "4"}}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 4
p value_at_deep_key(
{ 'key1' => {'key2' => {'key3' => "4"}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{
'key1' => "1",
'key2' => "2",
'key3' => "3",
'key4' => "4",
},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{ 'key1' => {'key2' => {'key3' => {'key4' => nil}}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{'key1' => {'key2' => {'key3' => {'key4' => 'hello'}}}},
%w[key1 key2 key3 key4],
default=123,
) #=> `Integer': invalid value for Integer(): "hello" (ArgumentError)
But maybe the following answer will suit you better:
If you must have:
A found String that looks like a number--converted to an int, or
The default
...in other words no errors, you can do this:
def value_at_deep_key(hash, key_sequence, default=nil)
value = hash
key_sequence.each do |key|
case value
when Hash
value = value[key]
else
value = hash.object_id #Some unique value to signal that the Hash lookup failed.
break
end
end
begin
value == hash.object_id ? default : Integer(value)
rescue TypeError, ArgumentError #If the Hash lookup succeeded, but the value is: nil, true/false, a String that is not all numbers, Array, Hash, an object that neither responds to to_int() nor to_i()
default
end
end
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {c: false}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {c: nil}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a, :b], 123) #=> 123
p value_at_deep_key({a: {b: {c: "cat"}}}, [:a], 123) #=> 123
p value_at_deep_key({z: {b: {c: "cat"}}}, [], 123) #=> 123
p value_at_deep_key({z: {b: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {b: {z: "cat"}}}, [:a, :b, :c], 123) #=> 123
p value_at_deep_key({a: {z: {c: "cat"}}}, [:a, :b, :c, :d], 123) #=> 123
p value_at_deep_key(
{'key1' => {'key2' => {'key3' => {'key4' => "4"}}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 4
p value_at_deep_key(
{ 'key1' => {'key2' => {'key3' => "4"}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{
'key1' => "1",
'key2' => "2",
'key3' => "3",
'key4' => "4",
},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{ 'key1' => {'key2' => {'key3' => {'key4' => nil}}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
p value_at_deep_key(
{'key1' => {'key2' => {'key3' => {'key4' => [1, 2, 3] }}}},
%w[key1 key2 key3 key4],
default=123,
) #=> 123
Related
Given an array a, what is the best way to achieve its combinations up to the n-th? For example:
a = %i[a b c]
n = 2
# Expected => [[], [:a], [:b], [:c], [:a, b], [:b, :c], [:c, :a]]
Do as below :
a = %w[a b c]
n = 3
0.upto(n).flat_map { |i| a.combination(i).to_a }
# => [[], ["a"], ["b"], ["c"], ["a", "b"],
# ["a", "c"], ["b", "c"], ["a", "b", "c"]]
Another way:
def all_combis(a, n, b=[])
n.zero? ? b.unshift([]) : all_combis(a, n-1, b.unshift(*a.combination(n)))
end
all_combis(%i[a b c], 0)
#=> [[]]
all_combis(%i[a b c], 1)
#=> [[], [:a], [:b], [:c]]
all_combis(%i[a b c], 2)
#=> [[], [:a], [:b], [:c], [:a, :b], [:a, :c], [:b, :c]]
all_combis(%i[a b c], 3)
#=> [[], [:a], [:b], [:c], [:a, :b], [:a, :c], [:b, :c], [:a, :b, :c]]
If order and efficiency are unimportant, this also works:
a.repeated_combination(n).map(&:uniq) << []
%i[a b c].repeated_combination(2).map(&:uniq) << []
#=> [[:a], [:a, :b], [:a, :c], [:b], [:b, :c], [:c], []]
how can I buit an array using two arrays as follow:
name = [a, b, c]
how_many_of_each [3, 5, 2]
to get
my_array = [a, a, a, b, b, b, b, b, c, c]
Use zip, flat_map, and array multiplication:
irb(main):001:0> value = [:a, :b, :c]
=> [:a, :b, :c]
irb(main):002:0> times = [3, 5, 2]
=> [3, 5, 2]
irb(main):003:0> value.zip(times).flat_map { |v, t| [v] * t }
=> [:a, :a, :a, :b, :b, :b, :b, :b, :c, :c]
name.zip(how_many_of_each).inject([]) do |memo, (x, y)|
y.times { memo << x}
memo
end
=> [:a, :a, :a, :b, :b, :b, :b, :b, :c, :c]
EDIT: Oh well, there's better, see #David Grayson.
This will do it in an easy to understand way:
my_array = []
name.count.times do |i|
how_many_of_each[i].times { my_array << name[i] }
end
array = ["a", "b", "c"]
how_many = [2, 2, 2]
result = []
array.each_with_index do |item, index|
how_many[index].times { result << item }
end
print result # => ["a", "a", "b", "b", "c", "c"]
You can pick the one you want (just swap the comment #):
class Array
def multiply_times(how_many)
r = []
#how_many.length.times { |i| how_many[i].times { r << self[i] } }
self.each_with_index { |e, i| how_many[i].times { r << e } }
r
end
end
p ['a', 'b', 'c'].multiply_times([3, 5, 2])
#=> ["a", "a", "a", "b", "b", "b", "b", "b", "c", "c"]
What is the easiest way to rearrange the elements of an array by referencing their indice? Something like this:
[:a, :b, :c, :d].rearrange(3, 1, 0, 2) # => [:d, :b, :a, :c]
[:a, :b, :c, :d].rearrange!(3, 1, 0, 2) # => [:d, :b, :a, :c]
Well, Ruby already has this method, only that it's called values_at:
>> [:a, :b, :c, :d].values_at(3, 1, 0, 2)
=> [:d, :b, :a, :c]
I'd turn it around a bit. A single permutation is just a function which maps an Array to another Array so this seems natural:
a = [:a, :b, :c, :d]
permuted = [3, 1, 0, 2].map { |i| a[i] }
That's easy to monkey patch into Array if necessary:
class Array
def permute(*p)
p.map { |i| self[i] }
end
end
Error checking, permute!, and further niceties are left as an exercise.
I always see replace in the Array and Hash documentation and I always think that it's odd.
I'm sure I've done something like this many times:
a = [:a, :b, :c, :d]
...
if some_condition
a = [:e, :f]
end
But I never thought to use this instead:
a = [:a, :b, :c, :d]
...
if some_condition
a.replace [:e, :f]
end
Which I assume is the intended use. Does this really save memory, or have some other benefit, or is it just a style thing?
a = [:e, :f] and a.replace [:e, :f],
the two statements generated instructions as follows:
1.
a = [:a, :b, :c, :d]
a = [:e, :f]
instructions:
ruby --dump=insns test.rb
== disasm: <RubyVM::InstructionSequence:<main>#test.rb>=================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace 1 ( 1)
0002 duparray [:a, :b, :c, :d]
0004 setdynamic a, 0
0007 trace 1 ( 2)
0009 duparray [:e, :f]
0011 dup
0012 setdynamic a, 0
0015 leave
2.
a = [:a, :b, :c, :d]
a.replace([:e, :f])
instructions:
ruby --dump=insns test.rb
== disasm: <RubyVM::InstructionSequence:<main>#test.rb>=================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace 1 ( 1)
0002 duparray [:a, :b, :c, :d]
0004 setdynamic a, 0
0007 trace 1 ( 2)
0009 getdynamic a, 0
0012 duparray [:e, :f]
0014 send :replace, 1, nil, 0, <ic:0>
0020 leave
The replace method is not faster than assignment operator, but replace can modify receiver array in-place, and, replace method really save memory, this can be seen from rb_ary_replace's source.
VALUE
rb_ary_replace(VALUE copy, VALUE orig)
{
rb_ary_modify_check(copy);
orig = to_ary(orig);
if (copy == orig) return copy;
if (RARRAY_LEN(orig) <= RARRAY_EMBED_LEN_MAX)
{
VALUE *ptr;
VALUE shared = 0;
if (ARY_OWNS_HEAP_P(copy))
{
xfree(RARRAY_PTR(copy));
}
else if (ARY_SHARED_P(copy))
{
shared = ARY_SHARED(copy);
FL_UNSET_SHARED(copy);
}
FL_SET_EMBED(copy);
ptr = RARRAY_PTR(orig);
MEMCPY(RARRAY_PTR(copy), ptr, VALUE, RARRAY_LEN(orig));
if (shared)
{
rb_ary_decrement_share(shared);
}
ARY_SET_LEN(copy, RARRAY_LEN(orig));
}
else
{
VALUE shared = ary_make_shared(orig);
if (ARY_OWNS_HEAP_P(copy))
{
xfree(RARRAY_PTR(copy));
}
else
{
rb_ary_unshare_safe(copy);
}
FL_UNSET_EMBED(copy);
ARY_SET_PTR(copy, RARRAY_PTR(orig));
ARY_SET_LEN(copy, RARRAY_LEN(orig));
rb_ary_set_shared(copy, shared);
}
return copy; }
I think the intended use is to modify an array in-place that has been passed to a method. For example:
def m(a)
a.replace(%w[a b])
end
a = %w[x y z]
m(a)
# a is now ['a', 'b']
Without replace, you'd have to do something like this:
def m(a)
a.clear
a << 'a' # or use .push of course
a << 'b'
end
Using replace lets you do it all at once should bypass the auto-shrinking and auto-growing (which probably involves copying some memory) behavior that would be a side effect of replacing the array's content (not the array itself!) element by element. The performance benefit (if any) is probably just an extra, the primary intent is probably to get pointer-to-pointer behavior without having to introduce pointers or wrap the array in an extra object.
a = [:a, :b, :c, :d]
b = [:x, :y, :z]
a.replace(b)
a.object_id == b.object_id
=> false
a = [:a, :b, :c, :d]
b = [:x, :y, :z]
a = b
a.object_id == b.object_id
=> true
Also
a = [:a, :b, :c, :d]
c = a
b = [:x, :y, :z]
a.replace(b)
p c # => [:x, :y, :z]
vs
a = [:a, :b, :c, :d]
c = a
b = [:x, :y, :z]
a = b
p c # => [:a, :b, :c, :d]
This does not answer your question exactly.
I have a factorised array of n dimensions and I would like to develop it.
Here is an example:
develop([:a, :aa]) #=> [[:a, :aa]]
...which is the same as: [:a].product([:aa]).
Or, more complicated:
develop([:a, [:aa, :bb]]) #=> [[:a, :aa],
[:a, :bb]]
I'm working with Ruby 1.9. Thank you for any idea.
Edit:
Another example, with 3 levels of embedded arrays:
develop([:a, [[:b, [:ba, :bb]],
[:c, [:ca, :cb]],
[:d, [:da, :db]]]]) #=> [[:a, :b, :ba],
[:a, :b, :bb],
[:a, :c, :ca],
[:a, :c, :cb],
[:a, :d, :da],
[:a, :d, :db]]
I wonder if we could use Array's product method (http://ruby-doc.org/core-1.9.3/Array.html#method-i-product), even if we have some embedded arrays.
I'm not sure I fully understand what you are trying to do to these poor arrays, but I managed to make a function that gives the correct output for both the cases you specified. Here is the complete code:
def develop(x)
return x unless x.is_a? Array
y = []
x[1].each do |s|
d = develop(s)
d = [d] unless d.is_a? Array
d.each do |t|
t = [t] unless t.is_a? Array
y << [x.first] + t
end
end
return y
end
x = [:a,
[
[:b, [:ba, :bb]],
[:c, [:ca, :cb]],
[:d, [:da, :db]]
]
]
p develop(x)
p develop [:a, [:aa, :bb]]
The output is:
C:\Users\David\Documents\scraps\test_ruby>ruby test.rb
[[:a, :b, :ba], [:a, :b, :bb], [:a, :c, :ca], [:a, :c, :cb], [:a, :d, :da], [:a, :d, :db]]
[[:a, :aa], [:a, :bb]]
EDIT 1: Here's a shorter version that also gives the right output:
def develop(x)
return [x] unless x.is_a? Array
Array(x.last).collect do |s|
develop(s).collect do |t|
[x.first] + Array(t)
end
end.flatten 1
end