How to make this hash creation prettier - ruby

I was wondering if there is a more elegant way of writing the following lines:
section_event_hash = []
sections.each do |s|
section_event_hash << { s => s.find_all_events }
end
I want to create a hash whose keys are the elements of sections, and the values are arrays of elements returned by the find_all_events method.

If you want section_event_hash to really be a Hash rather than an Array, then you could use each_with_object:
section_event_hash = sections.each_with_object({}) { |s, h| h[s] = s.find_all_events }
You could use map to build an array of arrays and then feed that to Hash[]:
section_event_hash = Hash[sections.map { |s| [s, s.find_all_events] }]

The code you posted isn't quite doing what you said you want. Let's take a closer look at it by testing like so:
sections = ["ab", "12"]
section_event_hash = []
sections.each do |s|
section_event_hash << { s => s.split("") }
end
puts section_event_hash.inspect
Gives:
[{"ab"=>["a", "b"]}, {"12"=>["1", "2"]}]
So you've actually created an array of hashes, where each hash contains one key-value pair.
The following code produces one hash with multiple elements. Notice how an empty hash is created with {} instead of []. Curly braces are the symbol for a hash, while the square brackets refer to a particular key.
section_event_hash = {}
sections.each do |s|
section_event_hash[s] = s.split("")
end
puts section_event_hash.inspect
=> {"ab"=>["a", "b"], "12"=>["1", "2"]}
As for a "more elegant" way of doing it, well that depends on your definition. As the other answers here demonstrate, there is usually more than one way to do something in ruby. seph's produces the same data structure as your original code, while mu's produces the hash you describe. Personally, I'd just aim for code that is easy to read, understand, and maintain.

array_of_section_event_hashes = sections.map do |s|
{s => s.find_all_events}
end

Related

Print particular values from object based on comparison

I am working on developing a UI wherein I need to retrieve particular values from an object based on conditions. object looks like below.
Category = {["personal_care_appliances", "glwise_category_config", "{catWhitelist: []}"],
["wine","glwise_category_config","{}"],
["shoes","glwise_category_config","{catWhitelist: []}"],
["automotive","glwise_category_config",],
["watch","glwise_category_config","{catWhitelist: []}"]
]
I need to print first element such as personal_care_appliances,shoes,watch as they have a component catWhitelist: [].
I tried using map and array syntax but it did not work.
PS. I am quite new to Ruby and learning through online docs.
Assuming that category is an array of arrays (which is not quite clear from your question), try map first element of nested array if the nested array includes "{catWhitelist: []}":
category.map { |e| e[0] if e.include? "{catWhitelist: []}" }.compact
# => ["personal_care_appliances", "shoes", "watch"]
Or, better to select required subarrays before to map it:
category.select { |e| e.include? "{catWhitelist: []}" }.map{ |e| e[0] }
If we assume you have this array:
category = [
["personal_care_appliances", "glwise_category_config", "{catWhitelist: []}"],
["wine","glwise_category_config","{}"],
["shoes","glwise_category_config","{catWhitelist: []}"],
["automotive","glwise_category_config",],
["watch","glwise_category_config","{catWhitelist: []}"]
]
and you want to get only the items which have catWhitelist, then you can use a method like:
def with_cat_whitelist(category)
result = []
category.each do |item|
result << item.first if item.last == "{catWhitelist: []}"
end
result
end

How to compare ruby hash with same key?

I have two hashes like this:
hash1 = Hash.new
hash1["part1"] = "test1"
hash1["part2"] = "test2"
hash1["part3"] = "test3"
hash2 = Hash.new
hash2["part1"] = "test1"
hash2["part2"] = "test2"
hash2["part3"] = "test4"
Expected output: part3
Basically, I want to iterate both of the hashes and print out "part3" because the value for "part3" is different in the hash. I can guarantee that the keys for both hashes will be the same, the values might be different. I want to print out the keys when their values are different?
I have tried iterating both hashes at once and comparing values but does not seem to give the right solution.
The cool thing about Ruby is that it is so high level that it is often basically English:
Print keys from the first hash if the values in the two hashes are different:
hash1.keys.each { |key| puts key if hash1[key] != hash2[key] }
Select the first hash keys that have different values in the two hashes and print each of them:
hash1.keys.select { |key| hash1[key] != hash2[key] }.each { |key| puts key }
Edit: I'll leave this should it be of interest, but #ndn's solution is certainly better.
p hash1.merge(hash2) { |_,v1,v2| v1==v2 }.reject { |_,v| v }.keys
# ["part3"]
hash1["part1"] = "test99"
p hash1.merge(hash2) { |_,v1,v2| v1==v2 }.reject { |_,v| v }.keys
# ["part1", "part3"]
This uses the form of Hash#merge that employs a block (here { |_,v1,v2| v1==v2 }) to determine the values of keys that are present in both hashes being merged. See the doc for an explanation of the three block variables, _, v1 and v2. The first block variable equals the common key. I've used the local variable _ for that, as is customary when the variable is not used in the block calculation.
The steps (for the original hash1):
g = hash1.merge(hash2) { |_,v1,v2| v1==v2 }
#=> {"part1"=>true, "part2"=>true, "part3"=>false}
h = g.reject { |_,v| v }
#=> {"part3"=>false}
h.keys
#=> ["part3"]
The obvious way is that of ndn, here a solution without blocks by converting to arrays, joining them and subtracting the elements that are the same, followed by converting back to hash and asking for the keys.
Next time it would be better to include what you tried so far.
((hash1.to_a + hash2.to_a) - (hash1.to_a & hash2.to_a)).to_h.keys
# ["part3"]

Ruby array of hashes values to string

I have an array of hashes (#1) that looks like this:
data = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>0}]
that I am trying to compare with following array of hashes (#2):
test = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>"0"}]
where #1 I obtained from database by mysql2 (what actually is in the database)
and #2 from my cucumber scenario (what I minimally expect ot be there).
By definition #2 must be a subset of #1 so I follow with this code:
data = data.to_set
test = test.to_set
assert test.subset?(data)
The problem is in data array the value of active is NOT a string. In case of data it is Fixnum, and in case of test, it is String.
I need a solution that will work even for more than one hash in the array. (As the database can return more than one row of results) That is why I convert to sets and use subset?
From other questions I got:
data.each do |obj|
obj.map do |k, v|
{k => v.to_s}
end
end
However it does not work for me. Any ideas?
Assumptions you can make:
All the keys in data will always be Strings.
All the keys in test will always be Strings. And always be the identical to data.
All the values in test will always be Strings.
Here are a couple of approaches that should do it, assuming I understand the question correctly.
#1: convert the hash values to strings
def stringify_hash_values(h)
h.each_with_object({}) { |(k,v),h| h[k] = v.to_s }
end
def sorta_subset?(data,test)
(test.map { |h| stringify_hash_values(data) } -
data.map { |h| stringify_hash_values(data) }).empty?
end
data = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>0}]
test = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>"0"}]
sorta_subset?(data,test) #=> true
#2 see if keys are the same and values converted to strings are equal
require 'set'
def hashes_sorta_equal?(h,g)
hk = h.keys
(hk.to_set == g.keys.to_set) &&
(h.values_at(*hk).map(&:to_s) == g.values_at(*hk).map(&:to_s))
end
def sorta_subset?(data,test)
test.all? { |h| data.any? { |g| hashes_sorta_equal?(g,h) } }
end
sorta_subset?(data,test) #=> true
Don't ask me why it works, but I found A solution:
data.map! do |obj|
obj.each do |k, v|
obj[k] = "#{v}"
end
end
I think it has something to do with what functions on arrays and hashes change the object itself and not create a changed copy of the object.

Ruby: Pair individual values of two arrays into third array

I have two arrays: ['x','y','z'] and [1,2]. How would I go about creating pairs of values (as a string) in a third array?
So I would end up with this:
['x:1', 'x:2', 'y:1', 'y:2', 'z:1', 'z:2']
Thanks for any help!
You can use the product method to create the pairs and then join them:
a1 = ['x','y','z']
a2 = [1,2]
a1.product(a2).map {|p| p.join(':') }
Here's a way that's short and efficient, and reads well.
a1 = ['x','y','z']
a2 = [1,2]
a1.flat_map { |e| a2.map { |f| "#{e}:#{f}" } }
#=> ['x:1', 'x:2', 'y:1', 'y:2', 'z:1', 'z:2']
I originally had a2.map { |f| e.to_s+?:+f.to_s }. I replaced flat_map with map and e.to_s+?:+f.to_s with "#{e}:#{f}", as suggested by #PhilRoss and #Stefan, respectively. Thanks to both of you.
My suggestion would be to split the problem into two separate problems. In this case, the following:
iterate through each element on both arrays at once. pushing in the values as we go.
place the string into a branch new array.
While messy, I would perhaps do something along this path:
def foo
#declare our known values as arrays, and initialize the container of the final result.
combined_array = []
array_letters = ['x', 'y', 'z']
array_numbers = ['1', '2']
array_letters.each do |letter|
array_numbers.each do |number|
combined_array << "#{letter}:#{number}"
end
end
#return our new array
combined_array
end
Now mind you, I am sure there is a better way to do this. But in consideration, I am fairly certain this should work.

In Ruby, is there an Array method that combines 'select' and 'map'?

I have a Ruby array containing some string values. I need to:
Find all elements that match some predicate
Run the matching elements through a transformation
Return the results as an array
Right now my solution looks like this:
def example
matchingLines = #lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Is there an Array or Enumerable method that combines select and map into a single logical statement?
I usually use map and compact together along with my selection criteria as a postfix if. compact gets rid of the nils.
jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}
=> [3, 3, 3, nil, nil, nil]
jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}.compact
=> [3, 3, 3]
Ruby 2.7+
There is now!
Ruby 2.7 is introducing filter_map for this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.
For example:
numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
Here's a good read on the subject.
Hope that's useful to someone!
You can use reduce for this, which requires only one pass:
[1,1,1,2,3,4].reduce([]) { |a, n| a.push(n*3) if n==1; a }
=> [3, 3, 3]
In other words, initialize the state to be what you want (in our case, an empty list to fill: []), then always make sure to return this value with modifications for each element in the original list (in our case, the modified element pushed to the list).
This is the most efficient since it only loops over the list with one pass (map + select or compact requires two passes).
In your case:
def example
results = #lines.reduce([]) do |lines, line|
lines.push( ...(line) ) if ...
lines
end
return results.uniq.sort
end
Another different way of approaching this is using the new (relative to this question) Enumerator::Lazy:
def example
#lines.lazy
.select { |line| line.property == requirement }
.map { |line| transforming_method(line) }
.uniq
.sort
end
The .lazy method returns a lazy enumerator. Calling .select or .map on a lazy enumerator returns another lazy enumerator. Only once you call .uniq does it actually force the enumerator and return an array. So what effectively happens is your .select and .map calls are combined into one - you only iterate over #lines once to do both .select and .map.
My instinct is that Adam's reduce method will be a little faster, but I think this is far more readable.
The primary consequence of this is that no intermediate array objects are created for each subsequent method call. In a normal #lines.select.map situation, select returns an array which is then modified by map, again returning an array. By comparison, the lazy evaluation only creates an array once. This is useful when your initial collection object is large. It also empowers you to work with infinite enumerators - e.g. random_number_generator.lazy.select(&:odd?).take(10).
If you have a select that can use the case operator (===), grep is a good alternative:
p [1,2,'not_a_number',3].grep(Integer){|x| -x } #=> [-1, -2, -3]
p ['1','2','not_a_number','3'].grep(/\D/, &:upcase) #=> ["NOT_A_NUMBER"]
If we need more complex logic we can create lambdas:
my_favourite_numbers = [1,4,6]
is_a_favourite_number = -> x { my_favourite_numbers.include? x }
make_awesome = -> x { "***#{x}***" }
my_data = [1,2,3,4]
p my_data.grep(is_a_favourite_number, &make_awesome) #=> ["***1***", "***4***"]
I'm not sure there is one. The Enumerable module, which adds select and map, doesn't show one.
You'd be required to pass in two blocks to the select_and_transform method, which would be a bit unintuitive IMHO.
Obviously, you could just chain them together, which is more readable:
transformed_list = lines.select{|line| ...}.map{|line| ... }
Simple Answer:
If you have n records, and you want to select and map based on condition then
records.map { |record| record.attribute if condition }.compact
Here, attribute is whatever you want from the record and condition you can put any check.
compact is to flush the unnecessary nil's which came out of that if condition
No, but you can do it like this:
lines.map { |line| do_some_action if check_some_property }.reject(&:nil?)
Or even better:
lines.inject([]) { |all, line| all << line if check_some_property; all }
I think that this way is more readable, because splits the filter conditions and mapped value while remaining clear that the actions are connected:
results = #lines.select { |line|
line.should_include?
}.map do |line|
line.value_to_map
end
And, in your specific case, eliminate the result variable all together:
def example
#lines.select { |line|
line.should_include?
}.map { |line|
line.value_to_map
}.uniq.sort
end
def example
#lines.select {|line| ... }.map {|line| ... }.uniq.sort
end
In Ruby 1.9 and 1.8.7, you can also chain and wrap iterators by simply not passing a block to them:
enum.select.map {|bla| ... }
But it's not really possible in this case, since the types of the block return values of select and map don't match up. It makes more sense for something like this:
enum.inject.with_index {|(acc, el), idx| ... }
AFAICS, the best you can do is the first example.
Here's a small example:
%w[a b 1 2 c d].map.select {|e| if /[0-9]/ =~ e then false else e.upcase end }
# => ["a", "b", "c", "d"]
%w[a b 1 2 c d].select.map {|e| if /[0-9]/ =~ e then false else e.upcase end }
# => ["A", "B", false, false, "C", "D"]
But what you really want is ["A", "B", "C", "D"].
You should try using my library Rearmed Ruby in which I have added the method Enumerable#select_map. Heres an example:
items = [{version: "1.1"}, {version: nil}, {version: false}]
items.select_map{|x| x[:version]} #=> [{version: "1.1"}]
# or without enumerable monkey patch
Rearmed.select_map(items){|x| x[:version]}
If you want to not create two different arrays, you can use compact! but be careful about it.
array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}
new_array.compact!
Interestingly, compact! does an in place removal of nil. The return value of compact! is the same array if there were changes but nil if there were no nils.
array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}.tap { |array| array.compact! }
Would be a one liner.
Your version:
def example
matchingLines = #lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
My version:
def example
results = {}
#lines.each{ |line| results[line] = true if ... }
return results.keys.sort
end
This will do 1 iteration (except the sort), and has the added bonus of keeping uniqueness (if you don't care about uniq, then just make results an array and results.push(line) if ...
Here is a example. It is not the same as your problem, but may be what you want, or can give a clue to your solution:
def example
lines.each do |x|
new_value = do_transform(x)
if new_value == some_thing
return new_value # here jump out example method directly.
else
next # continue next iterate.
end
end
end

Resources