How to split a hash in two hashes based on a condition? - ruby

I have a hash:
input = {"a"=>"440", "b"=>"-195", "c"=>"-163", "d"=>"100"}
From it I want to get two hashes, one containing the pairs whose value (as integer) is positive, the other containing negative values, for example:
positive = {"a"=>"440", "d"=>"100" }
negative = {"b"=>"-195", "c"=>"-163" }
How can I achieve this using the minimum amount of code?

You can use the Enumerable#partition method to split an enumerable object (like a hash) based on a condition. For example, to separate positive/negative values:
input.partition { |_, v| v.to_i < 0 }
# => [[["b", "-195"], ["c", "-163"]],
# [["a", "440"], ["d", "100"]]]
Then, to get the desired result, you can use map and to_h to convert the key/value arrays to hashes:
negative, positive = input.partition { |_, v| v.to_i < 0 }.map(&:to_h)
positive
# => {"a"=>"440", "d"=>"100"}
negative
# => {"b"=>"-195", "c"=>"-163"}
If you use a version of Ruby prior 2.1 you can replace the Array#to_h method (that was introduced in Ruby 2.1) like this:
evens, odds = input.partition { |_, v| v.to_i.even? }
.map { |alist| Hash[alist] }

This implementation uses Enumerable#group_by:
input = {"a"=>"440", "b"=>"-195", "c"=>"-163", "d"=>"100"}
grouped = input.group_by { |_, v| v.to_i >= 0 }.map { |k, v| [k, v.to_h] }.to_h
positives, negatives = grouped.values
positives #=> {"a"=>"440", "d"=>"100"}
negatives #=> {"b"=>"-195", "c"=>"-163"}
I must say that Enumerable#partition is more appropriate, as #toro2k answered.

something like this then?
positive = Hash.new
negative = Hash.new
input.each_pair { |var,val|
if val.to_i > 0
positive[var] = val
else
negative[var] = val
end
}

Related

Counting number of string occurrences in a string/array

I'm expecting to return all words with the max occurrences in a given string. The following code is expected to do so:
t1 = "This is a really really really cool experiment cool really "
frequency = Hash.new(0)
words = t1.split
words.each { |word| frequency[word.downcase] += 1 }
frequency = frequency.map.max_by { |k, v| v }
puts "The words with the most frequencies is '#{frequency[0]}' with
a frequency of #{frequency[1]}."
The output is:
The words with the most frequencies is 'really' with
a frequency of 4.
However, it does not work if there are, for example two strings that equal to the max. For example, if I add three cools to the text, it would still return the same output even though the count of cool is also equal to four.
It would be nice if you could tell me if those method would work on an array too instead of a string.
Try this.
t1 = "This is a really really really cool cool cool"
Step 1: Break your string into an array of words
words = t1.split
#=> ["This", "is", "a", "really", "really", "really", "cool", "cool", "cool"]
Step 2: Compute your frequency hash
frequency = Hash.new(0)
words.each { |word| frequency[word.downcase] += 1 }
frequency
##=> {"this"=>1, "is"=>1, "a"=>1, "really"=>3, "cool"=>3}
Step 3: Determine the maximum frequency
arr = frequency.max_by { |k, v| v }
#=> ["really", 3]
max_frequency = arr.last
#=> 3
Step 4: Create an array containing words having a frequency of max_frequency
arr = frequency.select { |k, v| v == max_frequency }
#=> {"really"=>3, "cool"=>3}
arr.map { |k, v| k }
#=> ["really", "cool"]
Conventional way of writing this in Ruby
words = t1.split
#=> ["This", "is", "a", "really", "really", "really", "cool", "cool", "cool"]
frequency = words.each_with_object(Hash.new(0)) do |word, f|
f[word.downcase] += 1
end
#=> {"this"=>1, "is"=>1, "a"=>1, "really"=>3, "cool"=>3}
max_frequency = frequency.max_by(&:last).last
#=> 3
frequency.select { |k, v| v == max_frequency }.map(&:first)
#=> ["really", "cool"]
Notes
e = [1,2,3].map #=> #<Enumerator: [1, 2, 3]:map>. This tells us that frequency.map.max_by { |k,v| v } is the same as frequency.max_by { |k,v| v }.
In frequency = frequency.map.max_by {|k, v| v }, frequency on the right is a hash; frequency on the left is an array. It's generally consider bad practice to reuse variables in that way.
Often frequency.max_by { |k,v| v } is written frequency.max_by { |_,v| v } or frequency.max_by { |_k,v| v }, mainly to signal to the reader that the first block variable is not used in the block calculation. (As I indicated above, this statement would generally be written frequency.max_by(&:last).) Note _ is a valid local variable.
frequency.max_by { |k, v| v }.last could instead be written frequency.map { |k, v| v }.max but that has the disadvantage that map produces an intermediate array of frequence.size elements, whereas the former produces an intermediate array of two elements.
You've already found the most frequent
greatest_frequency = frequency.max_by {|_, v| v }
Let's use it to found all the words which have this frequency
most_frequent_words = frequency.select { |_, v| v == greatest_frequency }.keys
puts "The words with the most frequencies are #{most_frequent_words.join(', ')} with a frequency of #{greatest_frequency}."
string = 'This is is a really a really a really cool cool experiment a cool cool really'
1). Separate string into array of words
words = string.split.map(&:downcase)
2). Calculate maximum frequency based on unique words
max_frequency = words.uniq.map { |i| words.count(i) }.max
3). Find combinations of word and frequency
combos = words.group_by { |e| e }.map { |k, v| [k, v.size] }.to_h
4). Select most frequent words
most_frequent_words = combos.select { |_, v| v == max_frequency }.keys
Result
puts "The words with the most frequencies are '#{most_frequent_words.join(', ')}' with a frequency of #{max_frequency}."
#=> The words with the most frequencies are 'a, really, cool' with a frequency of 4.

When is a block or object that is passed to Hash.new created or run?

I'm going through ruby koans and I am having a little trouble understanding when this code will be run:
hash = Hash.new {|hash, key| hash[key] = [] }
If there are no values in the hash, when does the new array get assigned to a given key in the Hash? Does it happen the first time a hash value is accessed without first assigning it? Please help me understand when exactly default values are created for any given hash key.
For the benefit of those new to Ruby, I have discussed alternative approaches to the problem, including the one that is the substance of this question.
The task
Suppose you are given an array
arr = [[:dog, "fido"], [:car, "audi"], [:cat, "lucy"], [:dog, "diva"], [:cat, "bo"]]
and wish to to create the hash
{ :dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"] }
First solution
h = {}
arr.each do |k,v|
h[k] = [] unless h.key?(k)
h[k] << v
end
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
This is quite straightforward.
Second solution
More Ruby-like is to write:
h = {}
arr.each { |k,v| (h[k] ||= []) << v }
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
When Ruby sees (h[k] ||= []) << v the first thing she does is expand it to
(h[k] = h[k] || []) << v
If h does not have a key k, h[k] #=> nil, so the expression becomes
(h[k] = nil || []) << v
which becomes
(h[k] = []) << v
so
h[k] #=> [v]
Note that h[k] on the left of equality uses the method Hash#[]=, whereas h[k] on the right employs Hash#[].
This solution requires that none of the hash values equal nil.
Third solution
A third approach is to give the hash a default value. If a hash h does not have a key k, h[k] returns the default value. There are two types of default values.
Passing the default value as an argument to Hash::new
If an empty array is passed as an argument to Hash::new, that value becomes the default value:
a = []
a.object_id
#=> 70339916855860
g = Hash.new(a)
#=> {}
g[k] returns [] when h does not have a key k. (The hash is not altered, however.) This construct has important uses, but it is inappropriate here. To see why, suppose we write
x = g[:cat] << "bo"
#=> ["bo"]
y = g[:dog] << "diva"
#=> ["bo", "diva"]
x #=> ["bo", "diva"]
This is because the values of :cat and :dog are both set equal to the same object, an empty array. We can see this by examining object_ids:
x.object_id
#=> 70339916855860
y.object_id
#=> 70339916855860
Giving Hash::new a block which returns the default value
The second form of default value is to perform a block calculation. If we define the hash with a block:
h = Hash.new { |h,k| h[key] = [] }
then if h does not have a key k, h[k] will be set equal to the value returned by the block, in this case an empty array. Note that the block variable h is the newly-created empty hash. This allows us to write
h = Hash.new { |h,k| h[k] = [] }
arr.each { |k,v| h[k] << v }
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
As the first element passed to the block is arr.first, the block variables are assigned values by evaluating
k, v = arr.first
#=> [:dog, "fido"]
k #=> :dog
v #=> "fido"
The block calculation is therefore
h[k] << v
#=> h[:dog] << "fido"
but since h does not (yet) have a key :dog, the block is triggered, setting h[k] equal to [] and then that empty array is appended with "fido", so that
h #=> { :dog=>["fido"] }
Similarly, after the next two elements of arr are passed to the block we have
h #=> { :dog=>["fido"], :car=>["audi"], :cat=>["lucy"] }
When the next (fourth) element of arr is passed to the block, we evaluate
h[:dog] << "diva"
but now h does have a key, so the default does not apply and we end up with
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy"]}
The last element of arr is processed similarly.
Note that, when using Hash::new with a block, we could write something like this:
h = Hash.new { launch_missiles("any time now") }
in which case h[k] would be set equal to the return value of launch_missiles. In other words, anything can be done in the block.
Even more Ruby-like
Lastly, the more Ruby-like way of writing
h = Hash.new { |h,k| h[k] = [] }
arr.each { |k,v| h[k] << v }
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
is to use Enumerable#each_with_object:
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |k,v| h[k] << v }
#=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
which eliminates two lines of code.
Which is best?
Personally, I am indifferent to the second and third solutions. Both are used in practice.
The block is called when you add a new key to the hash. In that specific case:
hash["d"] #calls the block and store [] as a value of "d" key
hash["d"] #should print []
For more information, visit: https://docs.ruby-lang.org/en/2.0.0/Hash.html
If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block's responsibility to store the value in the hash if required.
Makes life easier
This is syntactic sugar for those times that you have a hash whose values are all arrays and you don't want to check each time to see if the hash key is already there and the empty array is already initialized before adding new elements. It allows this:
hash[:new_key] << new_element
instead of this:
hash[:new_key] = [] unless hash[:new_key]
hash[:new_key] << new_element
Solves an older problem
It's also an alternative to the simpler way of specifying a default value for hashes, which looks like this:
hash = Hash.new([])
The problem with this approach is that the same array object is used as the default for all keys. So
hash = Hash.new([])
hash[:a] << 1
hash[:b] << 2
will return [1, 2] for either hash[:a] or hash[:b], or even hash[:foo] for that matter. Which is not usually the desired/expected behavior.

How do I create a hash where the keys are values from an array Ruby

I have an array:
arr = [a, ab, abc]
I want to make a hash, using the values of the array as the keys:
newhash = [a[1], ab[1], abc[1]]
I have tried:
arr.each do |r|
newhash[r] == 1
end
to no avail.
How would I about accomplishing this in ruby?
If you are feeling like a one-liner, this will work as well
h = Hash[arr.collect { |v| [v, 1] } ]
collect is invoked once per element in the array, so it returns an array of 2-element arrays of key-value pairs.
Then this is fed to the hash constructor, which turns the array of pairs into a hash
You could also use the #reduce method from Enumerable (which is included into the Array class).
new_hash = arr.reduce({}) { |hsh, elem| hsh[elem] = 1; hsh }
And your new_hash looks like this in Ruby:
{"a": 1, "ab": 1, "abc": 1}
== is comparison. = is assigning. So just modify == into =. It works.
newhash = {}
arr.each do |r|
newhash[r] = 1
end
(I believe a, ab, abc are strings)
To learn more, this might help you. Array to Hash Ruby
You can do it like this:
ary = [[:foo, 1], [:bar, 2]]
Hash[ary] # => {:foo=>1, :bar=>2}
If you want to do it like you tried earlier, you want to initialize hash correctly:
ary = [:foo, :bar]
hash = {}
ary.each do |key|
hash[key] = 1
end # => {:foo=>1, :bar=>2}

Create array of individual hashes from one hash

Given a hash of key/value pairs, how can I turn that into an array of individual hashes for each key/value pair.
So for example, starting with:
{"hello"=>"bonjour", "goodbye"=>"au revoir"}
And turning that into:
[ {"hello" => "bonjour"}, {"goodbye" => "au revoir"} ]
I got that with the following but am wondering if there's an easier approach:
array = []
hash.each do |k,v|
h = Hash.new
h[k] = v
array << h
end
Do as below using Enumerable#map:
h = {"hello"=>"bonjour", "goodbye"=>"au revoir"}
h.map { |k,v| { k => v } }
# => [{"hello"=>"bonjour"}, {"goodbye"=>"au revoir"}]

Ruby hash keys to array conditional on hash value

I would like to extract hash key values to an array when a condition is met. For example, with hash h I want to extract the keys where the values are "true":
h = { :a => true, :b => false, :c =>true }
I've come up with this:
h.map {|k,v| k if v==true} - [nil]
Any alternatives?
h.select { |_, v| v }.keys
Will do the same, but in more readable way.
You can also do
s = {}
h.each do |k,v|
s[k] = v if v==true
end

Resources