How to extract value with static key (:value) in situation when we have object with one of optional nested objects?
message_obj = {
'id': 123456,
'message': {
'value': 'some value',
}
}
callback_obj = {
'id': 234567,
'callback': {
'value': 'some value',
}
}
In this situation, I using next instruction:
some_obj[:message] ? some_obj[:message][:value] : some_obj[:callback][:value]
How to extract value from nested object, then we know list of acceptable objects names (eg. [:message, :callback, :picture, ...]). In parent object exist only one nested object.
I would use Hash#values_at and then pick the value from the one hash that was returned:
message
.values_at(*[:message, :callback, :picture, ...])
.compact
.first[:value]
You could use dig
For example:
message_obj = {
'id': 123456,
'message': {
'value': 'some message value',
}
}
callback_obj = {
'id': 234567,
'callback': {
'value': 'some callback value',
}
}
objects = [message_obj, callback_obj]
objects.each do |obj|
message_value = obj.dig(:message, :value)
callback_value = obj.dig(:callback, :value)
puts "found a message value #{message_value}" if message_value
puts "found a callback value #{callback_value}" if callback_value
end
This would print:
found a message value some message value
found a callback value some callback value
The nice thing about dig is the paths can be any length, for example the following would also work.
objects = [message_obj, callback_obj]
paths = [
[:message, :value],
[:callback, :value],
[:foo, :bar],
[:some, :really, :long, :path, :to, :a, :value]
]
objects.each do |obj|
paths.each do |path|
value = obj.dig(*path)
puts value if value
end
end
Use Ruby's Pattern-Matching Feature with Hashes
This is a great opportunity to use the pattern matching features of Ruby 3. Some of these features were introduced as experimental and changed often in the Ruby 2.7 series, but most have now stabilized and are considered part of the core language, although I personally expect that they will continue to continue to grow and improve especially as they are more heavily adopted.
While still evolving, Ruby's pattern matching allows you to do things like:
objects = [message_obj, callback_obj, {}, nil]
objects.map do
case _1
in message: v
in callback: v
else v = nil
end
v.values.first if v
end.compact
#=> ["some message value", "some callback value"]
You simply define a case for each Hash key you want to match (very easy with top-level keys; a little harder for deeply-nested keys) and then bind them to a variable like v. You can then use any methods you like to operate on the bound variable, either inside or outside the pattern-matching case statement. In this case, since all patterns are bound to v, it makes more sense to invoke our methods on whatever instance of v was found In your example, each :value key has a single value, so we can just use #first or #pop on v.values to get the results we want.
I threw in an else clause to set v to avoid NoMatchingPatternError, and a nil guard in the event that v == nil, but this is otherwise very straightforward, compact, and extremely extensible. Since I expect pattern matching, especially for Hash-based patterns, to continue to evolve in the Ruby 3 series, this is a good way to both explore the feature and to take a fairly readable and extensible approach to what might otherwise require a lot more looping and validation, or the use of a third-party gem method like Hashie#deep_find. Your mileage may vary.
Caveats
As of Ruby 3.1.1, the ability to use the find pattern on deeply-nested keys is somewhat limited, and the use of variable binding when using the alternates syntax currently throws an exception. As this is a fairly new feature in core, keep an eye on the changelog for Ruby's master branch (and yes, future readers, the branch is still labeled "master" at the time of this writing) or on the release notes for the upcoming Ruby 3.2.0 preview and beyond.
For a caching layer, I need to create a unique sha for a hash. It should be unique for the content of that hash. Two hashes with the same config should have the same sha.
in_2014 = { scopes: [1, 2, 3], year: 2014 }
not_in_2104 = { scopes: [1, 2, 3], year: 2015 }
also_in_2014 = { year: 2014, scopes: [1, 2, 3] }
in_2014 == also_in_2014 #=> true
not_in_2104 == in_2014 #=> false
Now, in order to store it and quickly look this up, it need to be turned
into something of a shasum. Simply converting to string does not work,
so generating a hexdigest from it does not work either:
require 'digest'
in_2014.to_s == also_in_2014.to_s #=> false
Digest::SHA2.hexdigest(in_2014.to_s) == Digest::SHA2.hexdigest(also_in_2014.to_s) #=> false
What I want is a shasum or some other identifier that will allow me to
compare the hashes with one another. I want something like the last test that will return true if the contents of the hashes match.
I could sort the hashes before to_s, yet that seems cludgy to me. I
am, for one, afraid that I am overlooking something there (a sort returns an array, no longer a hash, for one). Is there
something simple that I am overlooking? Or is this not possible at all?
FWIW, we need this in a scenario like below:
Analysis.find_by_config({scopes: [1,2], year: 2014}).datasets
Analysis.find_by_config({account_id: 1337}).datasets
class Analysis < ActiveRecord::Base
def self.find_by_config(config)
self.find_by(config_digest: shasum_of(config))
end
def self.shasum_of(config)
#WAT?
end
def before_saving
self.config_digest = Analysis.shasum_of(config)
end
end
Note that here, Analysis does not have columns "scopes" or "year" or
"account_id". These are arbitrary configs, that we only need for looking
up the datasets.
I wouldn't recommend the hash method because it is unreliable. You can quickly confirm this by executing {one: 1}.hash in your IRB, the same command in your Rails console, and then in the IRB and/or Rails Console on another machine. The outputs will differ.
Sticking with Digest::SHA2.hexdigest(string) would be wiser.
You'll have to sort the hash and stringify it of course. This is what I would do:
hash.sort.to_s
If you don't want an array, for whatever reason, turn it back into a hash.
Hash[hash.sort].to_s #=> will return hash
And, for whatever reason, if you don't want to turn the hash into an array and then back into a hash, do the following for hash-to-sorted-hash:
def prepare_for_sum( hash )
hash.keys.sort.each_with_object({}) do |key, return_hash|
return_hash[key] = hash[key]
end.to_s
end
Using some modifications in the method above, you can sort the values too; it can be helpful in case of Array or Hash values.
Turns out, Ruby has a method for this exact case: Hash.hash.
in_2014.hash == also_in_2014.hash
This is kind of an odd question, but I don't .......
I overcomplicated the original question. If you want to see the original question, look through the previous edits. Here's my revised question by combining my two previous questions, as simple as I can explain it:
I have an object. The object can have (x) amount of fields and all of the fields and values can be different. Example:
#<User name="John", height=75> or #<Comment title="First!">
I'm using a gem/db requires a specific format for queries. This is the format that the query MUST have: CREATE (u:User{attribute: "Some att.", another_att: 32}) and so on and so forth. Notice the JavaScript-like hash it uses for its values.
I'm trying to interpolate an object's attributes into the query. But I don't know how to map the correct fields and values into the query. Here's an example of what I start with and what I want to end with:
object = #<User name="John", height=75> =>
"CREATE (u:User{name: "John", height: 75})"
What's the simplest way to achieve this? Thanks to everyone who's helped so far.
Note that it is possible to convert the attributes to a Ruby hash: {:name => "John", :height => 75}, (as explained in the original question) but it was too confusing to change that hash to the syntax in the query, so I've backtracked and simplified my question.
It is very unlikely that the literal is the problem. The JSON-style syntax
was introduced in Ruby 1.9 as a literal abbreviation for the longer
"hashrocket" style.
> {name: 'John'} == {:name => 'John'}
=> true
Figured it out by going through the Ruby docs. It was way simpler than it seemed. The .collect method (or map) can be used to map each value and field to an interpolated string that can be put into a query. Example:
hash = {first_name: 'John', last_name: 'Smith'}
hash.collect {|k, v| "#{k}: #{v}"}
=> ["first_name: John", "last_name: Smith"]
This doesn't make sense. There has to be something else going on here.
A hash is a hash is a hash, it doesn't matter what syntax you used to create it, it's still the exact same hash. Those four are 100% equivalent (and if they aren't, that's a bug in your Ruby implementation):
hash = { name: 'John' }
hash = { :name => 'John' }
hash = {}
hash[:name] = 'John'
hash = Hash.new
hash[%Q|name|.to_sym] = %w#John#.first
There is absolutely no difference between them. There is no such thing as a "Hashrocket Hash" or "JSON-style Hash". There is a difference between the literals used to construct those hashes, but that difference only exists at parse time. At runtime, they are just as identical as 16, 0x10 and 020 are.
So I semi-asked this in another thread about how to get .max and return a value to a screen. All where very good answers, I just didn't ask the entire question. I ended up going with:
hash_example = {777 =>["dog","brown",3], 123=>["cat","orange",2]} #hash example
h =hash_example.values.collect{|a|a[0]}.max #change .max value based on element
puts the a[1] element based on what is returned in h because of .max of a[0].max
The problem is now I want to take h (the .max value found) and based on finding that element return a different element from the same array in the next line of code. To further elaborate lets say the above code found dog as .max. How do I go about returning brown or 3 to the screen in the next line of code?
puts hash_example.some_method_here{block of useful code using the h value} ?
I'm probably looking into this the wrong way or is it just a simple puts statment ? I've tried some nesting in the block but I'm definetly not nesting it correctly. .inject and .map I think are the right direction but I'm not writing the block correctly.
You're probably best off sorting the hash values, and taking the last one (as the max value), then working from there.
>> h = {777 =>["dog","brown",3], 123=>["cat","orange",2]}
=> {777=>["dog", "brown", 3], 123=>["cat", "orange", 2]}
>> h.values.sort_by{|a|a[0]}.last[1]
=> "brown"
The sort_by method accepts a block that describes what you want to sort by, relative to a single element - in this case it's using the first array element.
Here is a way of finding the max that will also give you the other array elements...
e = {777=>["dog", "brown", 3], 123=>["cat", "orange", 2]}
>> e.values.transpose[0].max
=> "dog"
So we can rewrite the code from the top...
x = e.values
t = x.transpose[0]
x[t.index t.max]
Which returns ["dog", "brown", 3]
PHP, for all its warts, is pretty good on this count. There's no difference between an array and a hash (maybe I'm naive, but this seems obviously right to me), and to iterate through either you just do
foreach (array/hash as $key => $value)
In Ruby there are a bunch of ways to do this sort of thing:
array.length.times do |i|
end
array.each
array.each_index
for i in array
Hashes make more sense, since I just always use
hash.each do |key, value|
Why can't I do this for arrays? If I want to remember just one method, I guess I can use each_index (since it makes both the index and value available), but it's annoying to have to do array[index] instead of just value.
Oh right, I forgot about array.each_with_index. However, this one sucks because it goes |value, key| and hash.each goes |key, value|! Is this not insane?
This will iterate through all the elements:
array = [1, 2, 3, 4, 5, 6]
array.each { |x| puts x }
# Output:
1
2
3
4
5
6
This will iterate through all the elements giving you the value and the index:
array = ["A", "B", "C"]
array.each_with_index {|val, index| puts "#{val} => #{index}" }
# Output:
A => 0
B => 1
C => 2
I'm not quite sure from your question which one you are looking for.
I think there is no one right way. There are a lot of different ways to iterate, and each has its own niche.
each is sufficient for many usages, since I don't often care about the indexes.
each_ with _index acts like Hash#each - you get the value and the index.
each_index - just the indexes. I don't use this one often. Equivalent to "length.times".
map is another way to iterate, useful when you want to transform one array into another.
select is the iterator to use when you want to choose a subset.
inject is useful for generating sums or products, or collecting a single result.
It may seem like a lot to remember, but don't worry, you can get by without knowing all of them. But as you start to learn and use the different methods, your code will become cleaner and clearer, and you'll be on your way to Ruby mastery.
I'm not saying that Array -> |value,index| and Hash -> |key,value| is not insane (see Horace Loeb's comment), but I am saying that there is a sane way to expect this arrangement.
When I am dealing with arrays, I am focused on the elements in the array (not the index because the index is transitory). The method is each with index, i.e. each+index, or |each,index|, or |value,index|. This is also consistent with the index being viewed as an optional argument, e.g. |value| is equivalent to |value,index=nil| which is consistent with |value,index|.
When I am dealing with hashes, I am often more focused on the keys than the values, and I am usually dealing with keys and values in that order, either key => value or hash[key] = value.
If you want duck-typing, then either explicitly use a defined method as Brent Longborough showed, or an implicit method as maxhawkins showed.
Ruby is all about accommodating the language to suit the programmer, not about the programmer accommodating to suit the language. This is why there are so many ways. There are so many ways to think about something. In Ruby, you choose the closest and the rest of the code usually falls out extremely neatly and concisely.
As for the original question, "What is the “right” way to iterate through an array in Ruby?", well, I think the core way (i.e. without powerful syntactic sugar or object oriented power) is to do:
for index in 0 ... array.size
puts "array[#{index}] = #{array[index].inspect}"
end
But Ruby is all about powerful syntactic sugar and object oriented power, but anyway here is the equivalent for hashes, and the keys can be ordered or not:
for key in hash.keys.sort
puts "hash[#{key.inspect}] = #{hash[key].inspect}"
end
So, my answer is, "The “right” way to iterate through an array in Ruby depends on you (i.e. the programmer or the programming team) and the project.". The better Ruby programmer makes the better choice (of which syntactic power and/or which object oriented approach). The better Ruby programmer continues to look for more ways.
Now, I want to ask another question, "What is the “right” way to iterate through a Range in Ruby backwards?"! (This question is how I came to this page.)
It is nice to do (for the forwards):
(1..10).each{|i| puts "i=#{i}" }
but I don't like to do (for the backwards):
(1..10).to_a.reverse.each{|i| puts "i=#{i}" }
Well, I don't actually mind doing that too much, but when I am teaching going backwards, I want to show my students a nice symmetry (i.e. with minimal difference, e.g. only adding a reverse, or a step -1, but without modifying anything else).
You can do (for symmetry):
(a=*1..10).each{|i| puts "i=#{i}" }
and
(a=*1..10).reverse.each{|i| puts "i=#{i}" }
which I don't like much, but you can't do
(*1..10).each{|i| puts "i=#{i}" }
(*1..10).reverse.each{|i| puts "i=#{i}" }
#
(1..10).step(1){|i| puts "i=#{i}" }
(1..10).step(-1){|i| puts "i=#{i}" }
#
(1..10).each{|i| puts "i=#{i}" }
(10..1).each{|i| puts "i=#{i}" } # I don't want this though. It's dangerous
You could ultimately do
class Range
def each_reverse(&block)
self.to_a.reverse.each(&block)
end
end
but I want to teach pure Ruby rather than object oriented approaches (just yet). I would like to iterate backwards:
without creating an array (consider 0..1000000000)
working for any Range (e.g. Strings, not just Integers)
without using any extra object oriented power (i.e. no class modification)
I believe this is impossible without defining a pred method, which means modifying the Range class to use it. If you can do this please let me know, otherwise confirmation of impossibility would be appreciated though it would be disappointing. Perhaps Ruby 1.9 addresses this.
(Thanks for your time in reading this.)
Use each_with_index when you need both.
ary.each_with_index { |val, idx| # ...
The other answers are just fine, but I wanted to point out one other peripheral thing: Arrays are ordered, whereas Hashes are not in 1.8. (In Ruby 1.9, Hashes are ordered by insertion order of keys.) So it wouldn't make sense prior to 1.9 to iterate over a Hash in the same way/sequence as Arrays, which have always had a definite ordering. I don't know what the default order is for PHP associative arrays (apparently my google fu isn't strong enough to figure that out, either), but I don't know how you can consider regular PHP arrays and PHP associative arrays to be "the same" in this context, since the order for associative arrays seems undefined.
As such, the Ruby way seems more clear and intuitive to me. :)
Here are the four options listed in your question, arranged by freedom of control. You might want to use a different one depending on what you need.
Simply go through values:
array.each
Simply go through indices:
array.each_index
Go through indices + index variable:
for i in array
Control loop count + index variable:
array.length.times do | i |
Trying to do the same thing consistently with arrays and hashes might just be a code smell, but, at the risk of my being branded as a codorous half-monkey-patcher, if you're looking for consistent behaviour, would this do the trick?:
class Hash
def each_pairwise
self.each { | x, y |
yield [x, y]
}
end
end
class Array
def each_pairwise
self.each_with_index { | x, y |
yield [y, x]
}
end
end
["a","b","c"].each_pairwise { |x,y|
puts "#{x} => #{y}"
}
{"a" => "Aardvark","b" => "Bogle","c" => "Catastrophe"}.each_pairwise { |x,y|
puts "#{x} => #{y}"
}
I'd been trying to build a menu (in Camping and Markaby) using a hash.
Each item has 2 elements: a menu label and a URL, so a hash seemed right, but the '/' URL for 'Home' always appeared last (as you'd expect for a hash), so menu items appeared in the wrong order.
Using an array with each_slice does the job:
['Home', '/', 'Page two', 'two', 'Test', 'test'].each_slice(2) do|label,link|
li {a label, :href => link}
end
Adding extra values for each menu item (e.g. like a CSS ID name) just means increasing the slice value. So, like a hash but with groups consisting of any number of items. Perfect.
So this is just to say thanks for inadvertently hinting at a solution!
Obvious, but worth stating: I suggest checking if the length of the array is divisible by the slice value.
If you use the enumerable mixin (as Rails does) you can do something similar to the php snippet listed. Just use the each_slice method and flatten the hash.
require 'enumerator'
['a',1,'b',2].to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }
# is equivalent to...
{'a'=>1,'b'=>2}.to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }
Less monkey-patching required.
However, this does cause problems when you have a recursive array or a hash with array values. In ruby 1.9 this problem is solved with a parameter to the flatten method that specifies how deep to recurse.
# Ruby 1.8
[1,2,[1,2,3]].flatten
=> [1,2,1,2,3]
# Ruby 1.9
[1,2,[1,2,3]].flatten(0)
=> [1,2,[1,2,3]]
As for the question of whether this is a code smell, I'm not sure. Usually when I have to bend over backwards to iterate over something I step back and realize I'm attacking the problem wrong.
In Ruby 2.1, each_with_index method is removed.
Instead you can use each_index
Example:
a = [ "a", "b", "c" ]
a.each_index {|x| print x, " -- " }
produces:
0 -- 1 -- 2 --
The right way is the one you feel most comfortable with and which does what you want it to do. In programming there is rarely one 'correct' way to do things, more often there are multiple ways to choose.
If you are comfortable with certain way of doings things, do just it, unless it doesn't work - then it is time to find better way.
Using the same method for iterating through both arrays and hashes makes sense, for example to process nested hash-and-array structures often resulting from parsers, from reading JSON files etc..
One clever way that has not yet been mentioned is how it's done in the Ruby Facets library of standard library extensions. From here:
class Array
# Iterate over index and value. The intention of this
# method is to provide polymorphism with Hash.
#
def each_pair #:yield:
each_with_index {|e, i| yield(i,e) }
end
end
There is already Hash#each_pair, an alias of Hash#each. So after this patch, we also have Array#each_pair and can use it interchangeably to iterate through both Hashes and Arrays. This fixes the OP's observed insanity that Array#each_with_index has the block arguments reversed compared to Hash#each. Example usage:
my_array = ['Hello', 'World', '!']
my_array.each_pair { |key, value| pp "#{key}, #{value}" }
# result:
"0, Hello"
"1, World"
"2, !"
my_hash = { '0' => 'Hello', '1' => 'World', '2' => '!' }
my_hash.each_pair { |key, value| pp "#{key}, #{value}" }
# result:
"0, Hello"
"1, World"
"2, !"