Calling flatten on a hash in ruby. Oddities - ruby

Say I have the following hash:
error_hash = {
:base => [
[0] [
[0] "Address is required to activate"
]
]
}
Are these results odd?
[18] pry(#<Api::UsersController>)> error_hash.flatten
[
[0] :base,
[1] [
[0] [
[0] "Address is required to activate"
]
]
]
[19] pry(#<Api::UsersController>)> error_hash.flatten(1)
[
[0] :base,
[1] [
[0] [
[0] "Address is required to activate"
]
]
]
[20] pry(#<Api::UsersController>)> error_hash.flatten(2)
[
[0] :base,
[1] [
[0] "Address is required to activate"
]
]
[21] pry(#<Api::UsersController>)> error_hash.flatten(3)
[
[0] :base,
[1] "Address is required to activate"
]
I would have expected .flatten to have been equal to flatten(3), or in otherwords, I would have expected .flatten to have flattened recursively until evereything was in a single array.

Why would you expect flatten to act recursively when the documentation does suggest otherwise?
You can extend the capability of hash using following:
class Hash
def flatten_deepest
self.each_with_object({}) do |(key, val), h|
if val.is_a? Hash
val.flatten_to_root.map do |hash_key, hash_val|
h["#{key}.#{hash_key}".to_sym] = hash_val
end
else
h[k] = val
end
end
end
end
and then do:
error_hash.flatten_deepest
I think you got the idea.

Related

ruby fill elements of an array into a set of nested arrays and remove duplicates

I have the arrays months and monthly_doc_count_for_topic.
months = ["2019-01-01", "2019-02-01", "2019-03-01", "2019-04-01"]
monthly_doc_count_for_topic = [
["foo","2019-02-01: 186904","2019-03-01: 196961"],
["bar","2019-01-01: 8876","2019-04-01: 8694"]
]
goal = [
["foo","2019-02-01: 186904","2019-03-01: 196961","2019-01-01","2019-02-01","2019-03-01","2019-04-01"],
["bar","2019-01-01: 8876","2019-04-01: 8694","2019-01-01","2019-02-01","2019-03-01","2019-04-01"]
]
I'd like to fill in element of the array months into arrays inside monthly_doc_count_for_topic so it looks like array goal.
My attempt:
monthly_doc_count_for_topic.map do |topic_set|
months.each { |month| topic_set << month }
end
But I'm getting:
=> [
[0] [
[0] "2019-01-01",
[1] "2019-02-01",
[2] "2019-03-01",
[3] "2019-04-01"
],
[1] [
[0] "2019-01-01",
[1] "2019-02-01",
[2] "2019-03-01",
[3] "2019-04-01"
]
]
it's not appending the values from monthly_doc_count_for_topic instead replacing it with elements from array months. How can I modify my code to achieve the output like array goal? Thank you very much!
In your attempt replace
monthly_doc_count_for_topic.map
with
monthly_doc_count_for_topic.each
and it works perfectly fine:
goal = monthly_doc_count_for_topic.each do |topic_set|
months.each { |month| topic_set << month }
end
But I'd prefer CarySwoveland's solution in the comment, it's less verbose:
monthly_doc_count_for_topic.map { |topic_set| topic_set + months }

Using .sort_by to sort a nested array

I have a nested array that I want to sort by a specific object, some advice would be very much appreciated.
In this example I'd like the output to return sorted by the dates that are nested.
arr = [
[
{
"log"=>[
[
"2016-09-03T00:00:00-03:00",
],
[
"2016-09-01T00:00:00-03:00",
],
[
"2016-09-02T00:00:00-03:00",
]
]
}
]
]
arr = [
[
{
"log"=>[
["2016-09-03T00:00:00-03:00"],
["2016-09-01T00:00:00-03:00"],
["2016-09-02T00:00:00-03:00"]
]
}
]
]
To return a sorted array and not mutate arr:
[[{ "log"=>arr[0][0]["log"].sort_by(&:first) }]]
#=> [[{"log"=>[
# ["2016-09-01T00:00:00-03:00"],
# ["2016-09-02T00:00:00-03:00"],
# ["2016-09-03T00:00:00-03:00"]
# ]}]]
To sort in place:
arr[0][0]["log"] = arr[0][0]["log"].sort_by(&:first)
#=> [["2016-09-01T00:00:00-03:00"],
# ["2016-09-02T00:00:00-03:00"],
# ["2016-09-03T00:00:00-03:00"]]
arr
#=> [[{"log"=>[
# ["2016-09-01T00:00:00-03:00"],
# ["2016-09-02T00:00:00-03:00"],
# ["2016-09-03T00:00:00-03:00"]
# ]}]]

How do I split the responses from an SNMP array?

I've got the following sample response from a system when walking the tree:
[name=1.3.6.1.4.1.15248.2.5.1.3.1.1.8650, value=8650 (INTEGER)]
[name=1.3.6.1.4.1.15248.2.5.1.3.1.1.8651, value=8651 (INTEGER)]
[name=1.3.6.1.4.1.15248.2.5.1.3.1.2.8650, value=QNewsAK (OCTET STRING)]
[name=1.3.6.1.4.1.15248.2.5.1.3.1.2.8651, value=QSuite4AK (OCTET STRING)]
[name=1.3.6.1.4.1.15248.2.5.1.3.1.3.8650, value=46835255 (INTEGER)]
[name=1.3.6.1.4.1.15248.2.5.1.3.1.3.8651, value=11041721 (INTEGER)]
[name=1.3.6.1.4.1.15248.2.5.1.3.1.4.8650, value=8442357 (INTEGER)]
[name=1.3.6.1.4.1.15248.2.5.1.3.1.4.8651, value=5717570 (INTEGER)]
[name=1.3.6.1.4.1.15248.2.5.1.3.1.5.8650, value=0 (INTEGER)]
[name=1.3.6.1.4.1.15248.2.5.1.3.1.5.8651, value=0 (INTEGER)]
I've got two distinct sets of data here. I don't know how many rows I will eventually get, and as you can also see, the first pair of values are also part of the OID.
Printing them nicely obviously tidies it up, but if I want to use them once on each line, what's the best way to split it?
I might get up to eight distinct sets of values that I'll have to work with, so each line would be for example:
8650, QNewsAK, 46835255, 8442357, 0
Which are the "ID", "Name", "Size", "Free", and "Status", where status is ordinarily non-zero.
Here's a starting point using group_by to do the heavy-lifting:
SNMP_RESPONSE = [
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.1.8650, value=8650 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.1.8651, value=8651 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.2.8650, value=QNewsAK (OCTET STRING)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.2.8651, value=QSuite4AK (OCTET STRING)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.3.8650, value=46835255 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.3.8651, value=11041721 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.4.8650, value=8442357 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.4.8651, value=5717570 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.5.8650, value=0 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.5.8651, value=0 (INTEGER)]',
]
SNMP_RESPONSE.group_by{ |s| s.split(',').first[/\d+$/] }
Which returns:
{
"8650" => [
[0] "[name=1.3.6.1.4.1.15248.2.5.1.3.1.1.8650, value=8650 (INTEGER)]",
[1] "[name=1.3.6.1.4.1.15248.2.5.1.3.1.2.8650, value=QNewsAK (OCTET STRING)]",
[2] "[name=1.3.6.1.4.1.15248.2.5.1.3.1.3.8650, value=46835255 (INTEGER)]",
[3] "[name=1.3.6.1.4.1.15248.2.5.1.3.1.4.8650, value=8442357 (INTEGER)]",
[4] "[name=1.3.6.1.4.1.15248.2.5.1.3.1.5.8650, value=0 (INTEGER)]"
],
"8651" => [
[0] "[name=1.3.6.1.4.1.15248.2.5.1.3.1.1.8651, value=8651 (INTEGER)]",
[1] "[name=1.3.6.1.4.1.15248.2.5.1.3.1.2.8651, value=QSuite4AK (OCTET STRING)]",
[2] "[name=1.3.6.1.4.1.15248.2.5.1.3.1.3.8651, value=11041721 (INTEGER)]",
[3] "[name=1.3.6.1.4.1.15248.2.5.1.3.1.4.8651, value=5717570 (INTEGER)]",
[4] "[name=1.3.6.1.4.1.15248.2.5.1.3.1.5.8651, value=0 (INTEGER)]"
]
}
The hash can be manipulated further:
groups = SNMP_RESPONSE.group_by{ |s| s.split(',').first[/\d+$/] }
values = groups.map{ |key, ary| ary.map{ |s| s[/value=(\S+)/, 1] } }
values looks like:
[
[0] [
[0] "8650",
[1] "QNewsAK",
[2] "46835255",
[3] "8442357",
[4] "0"
],
[1] [
[0] "8651",
[1] "QSuite4AK",
[2] "11041721",
[3] "5717570",
[4] "0"
]
]
A bit more massaging gives:
puts values.map{ |a| a.join(', ') }
Which outputs:
8650, QNewsAK, 46835255, 8442357, 0
8651, QSuite4AK, 11041721, 5717570, 0

Comparing Deep Nested Array of Array of Hashes in Ruby

I have an array of array of hashes which can be nested any level deep.
array = [
[ ['a','2'], ['b','5'] ],
[ ['c','4'], ['d','5'] ],
[ ['e','6'], [f,7] ],
...]
In the first stage I need to compare each consecutive hash - keep one of the elements and discarding the other.
In the second step the selected element of hash 1 have to be compared to selected element of the hash 2. This process has to continue till i am left with just one hashed element.
How do i do this i Ruby ?
thanks a lot for answering
You can do this with ==:
array1 = [
[ ['a','2'], ['b','5'] ],
[ ['c','4'], ['d','5'] ],
[ ['e','6'], ['f',7] ]
]
array2 = [
[ ['a','2'], ['b','5'] ],
[ ['c','4'], ['d','5'] ],
[ ['e','6'], ['f',7] ]
]
array3 = [
[ ['not','equal'] ]
]
array1 == array2
# => true
array2 == array3
# => false
See Array#== for specifics.

Most efficient way to extract arguments from String in Ruby

I would like to extract some information from a string in Ruby by only reading the String once (O(n) time complexity).
Here is an example:
The string looks like this: -location here -time 7:30pm -activity biking
I have a Ruby object I want to populate with this info. All the keywords are known, and they are all optional.
def ActivityInfo
_attr_reader_ :location, :time, :activity
def initialize(str)
#location, #time, #activity = DEFAULT_LOCATION, DEFAULT_TIME, DEFAULT_ACTIVITY
# Here is how I was planning on implementing this
current_string = ""
next_parameter = nil # A reference to keep track of which parameter the current string is refering to
words = str.split
while !str.empty?
word = str.shift
case word
when "-location"
if !next_parameter.nil?
next_parameter.parameter = current_string # Set the parameter value to the current_string
current_string = ""
else
next_parameter = #location
when "-time"
if !next_parameter.nil?
next_parameter.parameter = current_string
current_string = ""
else
next_parameter = #time
when "-activity"
if !next_parameter.nil?
next_parameter.parameter = current_string
current_string = ""
else
next_parameter = #time
else
if !current_string.empty?
current_string += " "
end
current_string += word
end
end
end
end
So basically I just don't know how to make a variable be the reference of another variable or method, so that I can then set it to a specific value. Or maybe there is just another more efficient way to achieve this?
Thanks!
The string looks suspiciously like a command-line, and there are some good Ruby modules to parse those, such as optparse.
Assuming it's not, here's a quick way to parse the commands in your sample into a hash:
cmd = '-location here -time 7:30pm -activity biking'
Hash[*cmd.scan(/-(\w+) (\S+)/).flatten]
Which results in:
{
"location" => "here",
"time" => "7:30pm",
"activity" => "biking"
}
Expanding it a bit farther:
class ActivityInfo
def initialize(h)
#location = h['location']
#time = h['time' ]
#activity = h['activity']
end
end
act = ActivityInfo.new(Hash[*cmd.scan(/-(\w+) (\S+)/).flatten])
Which sets act to an instance of ActivityInfo looking like:
#<ActivityInfo:0x101142df8
#activity = "biking",
#location = "here",
#time = "7:30pm"
>
--
The OP asked how to deal with situations where the commands are not flagged with - or are multiple words. These are equivalent, but I prefer the first stylistically:
irb(main):003:0> cmd.scan(/-((?:location|time|activity)) \s+ (\S+)/x)
[
[0] [
[0] "location",
[1] "here"
],
[1] [
[0] "time",
[1] "7:30pm"
],
[2] [
[0] "activity",
[1] "biking"
]
]
irb(main):004:0> cmd.scan(/-(location|time|activity) \s+ (\S+)/x)
[
[0] [
[0] "location",
[1] "here"
],
[1] [
[0] "time",
[1] "7:30pm"
],
[2] [
[0] "activity",
[1] "biking"
]
]
If the commands are multiple words, such as "at location":
irb(main):009:0> cmd = '-at location here -time 7:30pm -activity biking'
"-at location here -time 7:30pm -activity biking"
irb(main):010:0>
irb(main):011:0* cmd.scan(/-((?:at \s location|time|activity)) \s+ (\S+)/x)
[
[0] [
[0] "at location",
[1] "here"
],
[1] [
[0] "time",
[1] "7:30pm"
],
[2] [
[0] "activity",
[1] "biking"
]
]
If you need even more flexibility look at Ruby's strscan module. You can use that to tear apart a string and find the commands and their parameters.
Convert String to Options Hash
If you just want easy access to your flags and their values, you can split your string into a hash where each flag is a key. For example:
options = Hash[ str.scan /-(\w+)\s+(\S+)/ ]
=> {"location"=>"here", "time"=>"7:30pm", "activity"=>"biking"}
You can then reference values directly (e.g. options['location']) or iterate through your hash in key/value pairs. For example:
options.each_pair { |k, v| puts "%s %s" % [k, v] }
A Dash of Metaprogramming
Okay, this is serious over-engineering, but I spent a little extra time on this question because I found it interesting. I'm not claiming the following is useful; I'm just saying it was fun for me to do.
If you want to parse your option flags and and dynamically create a set of attribute readers and set some instance variables without having to define each flag or variable separately, you can do this with a dash of metaprogramming.
# Set attribute readers and instance variables dynamically
# using Kernel#instance_eval.
class ActivityInfo
def initialize(str)
options = Hash[ str.scan /-(\w+)\s+(\S+)/ ]
options.each_pair do |k, v|
self.class.instance_eval { attr_reader k.to_sym }
instance_variable_set("##{k}", v)
end
end
end
ActivityInfo.new '-location here -time 7:30pm -activity biking'
=> #<ActivityInfo:0x00000001b49398
#activity="biking",
#location="here",
#time="7:30pm">
Honestly, I think setting your variables explicitly from an options hash such as:
#activity = options['activity']`
will convey your intent more clearly (and be more readable), but it's always good to have alternatives. Your mileage may vary.
Why reinvent the wheel when Thor can do the heavy lifting for you?
class ActivityInfo < Thor
desc "record", "record details of your activity"
method_option :location, :type => :string, :aliases => "-l", :required => true
method_option :time, :type => :datetime, :aliases => "-t", :required => true
method_option :activity, :type => :string, :aliases => "-a", :required => true
def record
location = options[:location]
time = options[:time]
activity = options[:activity]
# record details of the activity
end
end
The options will be parse for you based on the datatype you specified. You can invoke it programmatically:
task = ActivityInfo.new([], {location: 'NYC', time: Time.now, activity: 'Chilling out'})
task.record
Or from command line: thor activity_info:record -l NYC -t "2012-06-23 02:30:00" -a "Chilling out"

Resources