Print particular values from object based on comparison - ruby

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

Related

takes in a dictionary key and return an array filled with the appropriate values of the keys

Given the hash
person = {
"cats"=> 2,
"dogs"=> 1
}
I wish to construct the array
["cats", "cats", "dogs"]
"cats" appears twice because person["cats"] #=> 2. For the same reason "dogs" appears once. If the hash had a third key-value pair "pigs"=>3, I would want to return the array
["cats", "cats", "dogs", "pigs", "pigs", "pigs"]
I tried the following code.
arr = person.to_a
i = 0
new_arr = []
while i < arr.length
el = arr[i][0]
final = [new_arr << el]
print final.flatten
i += 1
end
This displays
["cats"]["cats", "dogs"] => nil
but does not seem to return a value.
new_arr
#=> ["cats", "dogs"]
As you see, I am not getting the answer I wanted and do not understand why print displays what I show above.
I would like to know what is wrong with my code and what would be a better way of doing this.
flat_map method will flatten multiple arrays into one
Array operator * creates array with multiple values
result = person.flat_map {|key, value| [key] * value}
# => ["cats", "cats", "dogs"]
Ruby has a lot of nice methods to work with collections. I believe it is better to use them instead of while loop.
You can iterate through the hash using inject
method. The first parameter in the block is the resulting array, that accumulates the result of each iteration, the second is a key/value pair.
person.inject([]) do |array, (key, value)|
array + Array.new(value, key)
end
Or it can be rewritten as a one line.
person.inject([]) { |array, (key, value)| array + Array.new(value, key) }

Select named array from a hash of array of arrays

I have a hash of an array of arrays. The array is indexed by the hash (that is the way I am reading this):
[
{"name":"G18","data": [["X301",141],["x7901",57],["x2100",142],["x90",58]]},
{"name":"G19","data": [["M16",141],["M203",57],["M29S",142]]},
{"name":"G20","data": [["X301",141],["x7901",57],["x2100",142],["x90",58]]}
]
I want to select the hashes that contain the array G18, and return only the data.
I tried searching for answer, but I haven't found anything like this yet.
This will work for you if you have only one item with name "G18":
a.find {|e| e[:name] == "G18" }[:data]
See: Enumerable#find in the official docs.
Given:
ary = [
{"name":"G18","data": [["X301",141],["x7901",57],["x2100",142],["x90",58]]},
{"name":"G19","data": [["M16",141],["M203",57],["M29S",142]]},
{"name":"G20","data": [["X301",141],["x7901",57],["x2100",142],["x90",58]]}
]
Try:
ary.select{|hsh| hsh[:name] == 'G18'}.first[:data]
=> [["X301", 141], ["x7901", 57], ["x2100", 142], ["x90", 58]]
In fact, marmeladze's answer is the correct one:
ary.find{|hsh| hsh[:name] == 'G18'}[:data]
Using select was a misfire.
collection = [
{"name":"G18","data": [["X301",141],["x7901",57],["x2100",142],["x90",58]]},
{"name":"G19","data": [["M16",141],["M203",57],["M29S",142]]},
{"name":"G20","data": [["X301",141],["x7901",57],["x2100",142],["x90",58]]}
]
def data_for_name(name, collection)
collection.find { |item| item[:name] == name }[:data]
end
p data_for_name("G18", collection)

Return array of hashes based on hash element match

I am wondering how one would search through an array of hashes and return a value based on a search string. For example, #contacts contains the hash elements: :full_name, :city, and :email. The variable #contacts (I guess it would be an array) contains three entries (perhaps rows). Below is the code I have so far to conduct a search based on :city value. However it's not working. Can anyone give me an idea what's going on?
def search string
#contacts.map {|hash| hash[:city] == string}
end
You should use select instead of map:
def search string
#contacts.select { |hash| hash[:city] == string }
end
In your code you tried to map (or transform) your array using a block, which yields boolean values. map takes a block and invokes the block for each element of self, constructing a new array containing elements returned by the block. As the result, you got an array of booleans.
select works similar. It takes a block and iterates over the array as well, but instead of transforming the source array it returns an array containing elements for which the block returns true. So it's a selection (or filtering) method.
In order to understand the difference between these two methods it's useful to see their example definitions:
class Array
def my_map
[].tap do |result|
self.each do |item|
result << (yield item)
end
end
end
def my_select
[].tap do |result|
self.each do |item|
result << item if yield item
end
end
end
end
Example usage:
irb(main):007:0> [1,2,3].my_map { |x| x + 1 }
[2, 3, 4]
irb(main):008:0> [1,2,3].my_select { |x| x % 2 == 1 }
[1, 3]
irb(main):009:0>
You can try this:
def search string
#contacts.select{|hash| h[:city].eql?(string) }
end
This will return an array of hashes which matches string.

How to make this hash creation prettier

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

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