How to detect value in array of hashes - ruby

I have array of hashes:
#array = [{:id => "1", :status=>"R"},
{:id => "1", :status=>"R"},
{:id => "1", :status=>"B"},
{:id => "1", :status=>"R"}]
How to detect, does it contain in hashes with the value of status "B"? Like in simply array:
#array = ["R","R","B","R"]
puts "Contain B" if #array.include?("B")

Use any?:
#array.any? { |h| h[:status] == "B" }

Arrays (enumerables actually) have a detect method. It returns a nil if it doesn't detect anything, so you can use it like Andrew Marshall's any.
#array = [{:id => "1", :status=>"R"},
{:id => "1", :status=>"R"},
{:id => "1", :status=>"B"},
{:id => "1", :status=>"R"}]
puts "Has status B" if #array.detect{|h| h[:status] == 'B'}

Just to add to what steenslag said:
detect doesn't always return nil.
You can pass in a lambda to execute (call) if detect does not 'detect' (find) an item. In other words, you can tell detect what to do if it can't detect (find) something.
To add to your example:
not_found = lambda { "uh oh. couldn't detect anything!"}
# try to find something that isn't in the Enumerable object:
#array.detect(not_found) {|h| h[:status] == 'X'}
will return "uh oh. couldn't detect anything!"
This means that you don't have to write this kind of code:
if (result = #array.detect {|h| h[:status] == 'X'}).nil?
# show some error, do something here to handle it
# (this would be the behavior you'd put into your lambda)
else
# deal nicely with the result
end
That's one major difference between any? and detect -- you can't tell any? what to do if it doesn't find any items.
This is in the Enumerable class. ref: http://ruby-doc.org/core/classes/Enumerable.html#M003123

Related

Count the occurence of values in Ruby

I'm trying to count numbers of different values in mysql table columns. Range of possible values is integer and from 0-10. The following code is working, but I'm wondering if there is a more elegant way to do this?
# Example data from Mysql
result = [{ :column1 => "2", :column2 => "3", :column3 => "1"},{ :column1 => "2", :column2 => "3", :column3 => "1"},{ :column1 => "1", :column2 => "2", :column3 => "3"}]
# Init hash
final_result = Hash.new { |h, k| h[k] = { } }
# Loop result columns
result.each do |single_column|
# Loop single items inside columns
single_column.each do |single_result|
# Create column if does not exist
if final_result[single_result[0]][single_result[1]].nil? then
final_result[single_result[0]][single_result[1]] = 1
else
final_result[single_result[0]][single_result[1]] += 1
end
end
end
puts final_result
# => {:column1=>{"2"=>2, "1"=>1}, :column2=>{"3"=>2, "2"=>1}, :column3=>{"1"=>2, "3"=>1}}
There's some room for cleaning up here. The most obvious part is that long, clunky if statement. The test vs nil? is pointless, remember in Ruby the only things that are logically false are false and nil, so since false is never going to show up here, the test vs. nil specifically can be removed.
More than that, though, you're on the right track with the custom Hash.new call, but you don't go far enough. Why not initialize the second tier with zero?
That results in code that looks like:
result = [
{ :column1 => "2", :column2 => "3", :column3 => "1"},
{ :column1 => "2", :column2 => "3", :column3 => "1"},
{ :column1 => "1", :column2 => "2", :column3 => "3"}
]
# Init hash
final_result = Hash.new { |h, k| h[k] = Hash.new(0) }
# Loop result columns
result.each do |single_column|
single_column.each do |r|
final_result[r[0]][r[1]] += 1
end
end
puts final_result.inspect
Have a look at the active record count method (doc link). You can use that in combination with group to do what you are trying to achieve.
[:column1, :column2, :column3].inject({}) do |hash, column|
hash[column] = Model.group(column).count
hash
end

How do I convert a hash's values into lambdas recursively?

I have a Hash that looks like this:
{
:a => "700",
:b => "600",
:c => "500",
:d => "400",
:e => "300",
:f => {
:g => "200",
:h => [
"test"
]
}
}
my goal is to iterate over this hash and return a copy that have all the values wrapped in a lambda, similar to this: https://github.com/thoughtbot/paperclip/blob/dca87ec5d8038b2d436a75ad6119c8eb67b73e70/spec/paperclip/style_spec.rb#L44
I went with each_with_object({}) but best I can do is to wrap only the first level, so I tried to check when I meet another Hash in the cycle (:f in this case, only it's key's values should be a lambda unless they are a hash as well) and treat it, but it's becoming quite troublesome.
def hash_values_to_lambda(old_hash)
{}.tap do |new_hash|
old_hash.each do |key, value|
new_hash[key] =
if value.is_a?(Hash)
hash_values_to_lambda(value)
else
lambda { value } # or -> { value } with new syntax
end
end
end
end
If you want, you can go with each_with_object instead of tap:
old_hash.each_with_object({}) do |(key, value), new_hash|
# everything else remains the same
end

Incrementation in Ruby hashes

I'm trying to increment the key in a hash. For example. I'm trying to get this
{:b => "crayons", :c => "colors", :d => "apples"}
to turn into this
{:c => "crayons", :d => "colors", :e => "apples"}
I thought this code would do the trick but it doesn't. What do I need to change?
def hash(correct)
mapping = correct.each{|key, element| key.next}
Hash[correct.map {|key, element| [mapping[key], element]}]
end
Using Enumerable#each_with_object
def hash_correct(hsh)
hsh.each_with_object({}) { |(k,v), hsh| hsh[k.succ] = v }
end
hash_correct({:b => "crayons", :c => "colors", :d => "apples"})
# => {:c=>"crayons", :d=>"colors", :e=>"apples"}
def hash(correct)
Hash[correct.map{|key, element| [key.next, element]}]
end
h = {:b => "crayons", :c => "colors", :d => "apples"}
h.keys.map(&:succ).zip(h.values).to_h
#=> {:c=>"crayons", :d=>"colors", :e=>"apples"}
If the intent were to modify (not keep) the original hash, the update could be done in place:
keys = h.keys.reverse
keys.each { |k| h[k.succ] = h[k] }
h.delete(keys.last)
which could be inscrutablized to:
h.delete(h.keys.reverse.each { |k| h[k.succ] = h[k] }.last)
def hash(correct)
exp_hash = correct.map { | k, v| {k.next => v} }
Hash[*exp_hash.collect{|h| h.to_a}.flatten]
end
correct = {:b => "crayons", :c => "colors", :d => "apples"}
I thought this code would do the trick but it doesn't.
mapping = correct.each{|key, element| key.next}
If you go to the ruby Symbol docs and click on the link for next()...surprise there is no entry for next, but the description at the top of the window says:
succ
Same as sym.to_s.succ.intern.
From that you have to deduce that next() is a synonym for succ(). So Symbol#next/succ converts the symbol to a string by calling to_s(). Well, you know that you are going to get a String returned from to_s, and no matter what you do to that String, e.g. calling String#succ on it, it isn't going to effect some Symbol, e.g. your hash key. Furthermore, if you look at the docs for String#succ, it says
succ -> new_string
...so String#succ creates another String object and calling intern() on that String object, and by the way intern() is just a synonym for to_sym(), once again won't affect some Symbol...and it won't even affect the String object returned by to_s.
Finally, intern() doesn't change the second string object but instead returns a Symbol:
a String
V
key.next => key.to_s.succ.intern => Symbol
^
another String
...and because you didn't do anything with the Symbol returned by intern(), it is discarded.

Best way to find a hash that contains a key, in an array of hashes in ruby?

Say we have
[{:name=>"Joe", :age => 15},{:name=>"Josh", :age => 83},{:name=>"Jim", :age => 1203}]
What is the best way way to get the hash where the name is "Joe"? To return {:name=>"Joe", :age=>15}?
array.select will return an array of values (even if there is only one match):
my_array.select { |item| item[:name] == "Joe" }
# => [{:name => "Joe", :age => 15}]
You can use Enumerable's find method instead to return the first instance (as a hash):
my_array.find { |item| item[:name] == "Joe" }
# => {:name => "Joe", :age => 15}
array.select { |e| e[:name] == 'Joe' }.first
If this is an operation you need to perform frequently, then store the set of hashes as a hash of hashes, with the value for the name key as the key to each hash. That way you can look up your hashes in O(1) time.

Unique on an array of hashes based on value

I feel like this could be improved (a common feeling in ruby). I'm trying to uniq an array of hashes based on value. In this example, I want the colors of the elements. Moss and snow are impostors.
# remove unique array of hashes based on a hash value
a = [
{ :color => "blue", :name => "water" },
{ :color => "red", :name => "fire" },
{ :color => "white", :name => "wind" },
{ :color => "green", :name => "earth" },
{ :color => "green", :name => "moss" },
{ :color => "white", :name => "snow" }
]
# remove moss and snow
uniques = []
a.each_with_index do |r, i|
colors = uniques.collect {|e| e[:color]}
if !colors.include? r[:color]
uniques.push r
else
a[i] = nil
end
end
a.compact!
puts a
This will print
{:color=>"blue", :name=>"water"}
{:color=>"red", :name=>"fire"}
{:color=>"white", :name=>"wind"}
{:color=>"green", :name=>"earth"}
Which is "correct" however I feel like this is excessive. My experience with .map .inject is limited and those advanced techniques elude me. If someone could re-factor this, it might help me understand another terse technique.
In Ruby 1.9, try the following
a.uniq! {|e| e[:color] }
I'd go with Array's reject or select methods:
require 'pp'
a = [
{ :color => "blue", :name => "water" },
{ :color => "red", :name => "fire" },
{ :color => "white", :name => "wind" },
{ :color => "green", :name => "earth" },
{ :color => "green", :name => "moss" },
{ :color => "white", :name => "snow" }
]
pp a.reject{ |h| %w[moss snow].include?( h[:name]) }
# >> [{:color=>"blue", :name=>"water"},
# >> {:color=>"red", :name=>"fire"},
# >> {:color=>"white", :name=>"wind"},
# >> {:color=>"green", :name=>"earth"}]
Alternately you can be positive about it and select the ones you want to keep:
pp a.select{ |h| %w[water fire wind earth].include?( h[:name] ) }
# >> [{:color=>"blue", :name=>"water"},
# >> {:color=>"red", :name=>"fire"},
# >> {:color=>"white", :name=>"wind"},
# >> {:color=>"green", :name=>"earth"}]
You're not really dealing with hashes, it's an array that happens to contain hashes, so don't let them confuse you. Array methods like reject and select are core methods for filtering out unwanted, or keeping wanted, elements.
In your code sample, you're losing sight of what your objective is: You want the elements, rejecting "moss" and "snow", which are non-elements. Filter out the non-elements, and you're left with the correct/real elements in the hashes. From there you can extract the correct colors.
An additional problem to watch out for with using uniq, is it is positional, in other words, it looks for the first unique value and rejects subsequent ones. This wasn't apparent in your code because your array was consistently the same order as you tested. If you shuffled the order though...:
2.times do
pp a.shuffle.uniq{ |h| h[:color] }
end
Pass #1...
# [{:color=>"red", :name=>"fire"},
# {:color=>"white", :name=>"wind"},
# {:color=>"green", :name=>"moss"},
# {:color=>"blue", :name=>"water"}]
Pass #2...
# [{:color=>"green", :name=>"earth"},
# {:color=>"blue", :name=>"water"},
# {:color=>"red", :name=>"fire"},
# {:color=>"white", :name=>"snow"}]
Suddenly we see that both "moss" and "snow" are sneaking into the results even though the colors are unique. Those are subtle gotcha's that you have to watch out for.
For anyone who might want an even shorter variant of the correct answer by Steve Wilhelm ,
BEWARE:
a.uniq!(&:color)
WILL NOT WORK for an array of hashes, just like
a[1].color
wouldn't work either.
For more information on the & operator, read this link, or the comments on this question which in turn have plenty of links to resources.
On the other hand, you could get the Symbol#to_proc method working using lambdas, as is explained here, though it could be just complicating things, and certainly would not be a shorter version of the correct answer. However, it is very interesting knowledge.
Thanks mukesh-kumar-gupta for the heads-up

Resources