How do I split the responses from an SNMP array? - ruby

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

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 }

Merge duplicated values in array of hashes

I have an array of hashes like this
arr_of_hashes = [
{"started_at"=>"2018-07-11", "stopped_at"=>"2018-07-11"},
{"started_at"=>"2018-07-13", "stopped_at"=>"2018-07-13"},
{"started_at"=>"2018-07-13", "stopped_at"=>"2018-07-13"},
{"started_at"=>"2018-07-16", "stopped_at"=>"2018-07-16"},
{"started_at"=>"2018-07-16", "stopped_at"=>"2018-07-16"},
{"started_at"=>"2018-07-16", "stopped_at"=>"still active"}
]
I want to remove duplicates. Also, among:
{"started_at"=>"2018-07-16", "stopped_at"=>"2018-07-16"},
{"started_at"=>"2018-07-16", "stopped_at"=>"still active"}
I want to keep only the last line. How can I do that?
I tried to do:
sorted_arr = arr_of_hashes.uniq
arr_of_hashes.reverse.uniq { |hash| hash["started_at"] }.reverse
About pass block to uniq and about reverse.
#result
[
{"started_at"=>"2018-07-11", "stopped_at"=>"2018-07-11"},
{"started_at"=>"2018-07-13", "stopped_at"=>"2018-07-13"},
{"started_at"=>"2018-07-16", "stopped_at"=>"still active"}
]
Something like this?
[2] pry(main)> arr_of_hashes.reject { |h| h['started_at'] == h['stopped_at'] }
[
[0] {
"started_at" => "2018-07-16",
"stopped_at" => "still active"
}
]
Its not clear form your question what output you want to get
arr_of_hashes.each_with_object({}) { |g,h| h.update(g["started_at"]=>g) }.values
#=> [{"started_at"=>"2018-07-11", "stopped_at"=>"2018-07-11"},
# {"started_at"=>"2018-07-13", "stopped_at"=>"2018-07-13"},
# {"started_at"=>"2018-07-16", "stopped_at"=>"still active"}]
See Hash#update (a.k.a. merge!) and note that values's receiver is as follows.
arr_of_hashes.each_with_object({}) { |g,h| h.update(g["started_at"]=>g) }
#=> {"2018-07-11"=>{"started_at"=>"2018-07-11", "stopped_at"=>"2018-07-11"},
# "2018-07-13"=>{"started_at"=>"2018-07-13", "stopped_at"=>"2018-07-13"},
# "2018-07-16"=>{"started_at"=>"2018-07-16", "stopped_at"=>"still active"}}

Calling flatten on a hash in ruby. Oddities

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.

How to split string across new lines and keep blank lines?

Given the ruby code:
"aaaa\nbbbb\n\n".split(/\n/)
This outputs:
["aaaa", "bbbb"]
I would like the output to include the blank line indicated by \n\n -- I want the result to be:
["aaaa", "bbbb", ""]
What is the easiest/best way to get this exact result?
I'd recommend using lines instead of split for this task. lines will retain the trailing line-break, which allows you to see the desired empty-line. Use chomp to clean up:
"aaaa\nbbbb\n\n".lines.map(&:chomp)
[
[0] "aaaa",
[1] "bbbb",
[2] ""
]
Other, more convoluted, ways of getting there are:
"aaaa\nbbbb\n\n".split(/(\n)/).each_slice(2).map{ |ary| ary.join.chomp }
[
[0] "aaaa",
[1] "bbbb",
[2] ""
]
It's taking advantage of using a capture-group in split, which returns the split text with the intervening text being split upon. each_slice then groups the elements into two-element sub-arrays. map gets each two-element sub-array, does the join followed by the chomp.
Or:
"aaaa\nbbbb\n\n".split(/(\n)/).delete_if{ |e| e == "\n" }
[
[0] "aaaa",
[1] "bbbb",
[2] ""
]
Here's what split is returning:
"aaaa\nbbbb\n\n".split(/(\n)/)
[
[0] "aaaa",
[1] "\n",
[2] "bbbb",
[3] "\n",
[4] "",
[5] "\n"
]
We don't see that used very often, but it can be useful.
You can supply a negative argument for the second parameter of split to avoid stripping trailing empty strings;
"aaaa\nbbbb\n\n".split(/\n/, -1)
Note that this will give you one extra empty string compared to what you want.
You can use the numeric argument, but IMO it's a bit tricky since (IMO) it's not quite consistent with what I'd expect, and AFAICT you'd want to trim the last null field:
jruby-1.6.7 :020 > "aaaa\nbbbb\n\n".split(/\n/, -1)[0..-2]
=> ["aaaa", "bbbb", ""]

Ruby regex: scan all

I have a string:
TFS[MAD,GRO,BCN],ALC[GRO,PMI,ZAZ,MAD,BCN],BCN[ALC,...]...
I want to convert it into a list:
list = (
[0] => "TFS"
[0] => "MAD"
[1] => "GRO"
[2] => "BCN"
[1] => "ALC"
[0] => "GRO"
[1] => "PMI"
[2] => "ZAZ"
[3] => "MAD"
[4] => "BCN"
[2] => "BCN"
[1] => "ALC"
[2] => ...
[3] => ...
)
How do I do this in Ruby?
I tried:
(([A-Z]{3})\[([A-Z]{3},+))
But it returns only the first element in [] and doesn't make a comma optional (at the end of "]").
You need to tell the regex that the , is not required after each element, but instead in front of each argument except the first. This leads to the following regex:
str="TFS[MAD,GRO,BCN],ALC[GRO,PMI,ZAZ,MAD,BCN],BCN[ALC]"
str.scan(/[A-Z]{3}\[[A-Z]{3}(?:,[A-Z]{3})*\]/)
#=> ["TFS[MAD,GRO,BCN]", "ALC[GRO,PMI,ZAZ,MAD,BCN]", "BCN[ALC]"]
You can also use scan's behavior with capturing groups, to split each match into the part before the brackets and the part inside the brackets:
str.scan(/([A-Z]{3})\[([A-Z]{3}(?:,[A-Z]{3})*)\]/)
#=> [["TFS", "MAD,GRO,BCN"], ["ALC", "GRO,PMI,ZAZ,MAD,BCN"], ["BCN", "ALC"]]
You can then use map to split each part inside the brackets into multiple tokens:
str.scan(/([A-Z]{3})\[([A-Z]{3}(?:,[A-Z]{3})*)\]/).map do |x,y|
[x, y.split(",")]
end
#=> [["TFS", ["MAD", "GRO", "BCN"]],
# ["ALC", ["GRO", "PMI", "ZAZ", "MAD", "BCN"]],
# ["BCN", ["ALC"]]]
Here's another way using a hash to store your contents, and less regex.
string = "TFS[MAD,GRO,BCN],ALC[GRO,PMI,ZAZ,MAD,BCN],BCN[ALC]"
z=Hash.new([])
string.split(/][ \t]*,/).each do |x|
o,p=x.split("[")
z[o]=p.split(",")
end
z.each_pair{|x,y| print "#{x}:#{y}\n"}
output
$ ruby test.rb
TFS:["MAD", "GRO", "BCN"]
ALC:["GRO", "PMI", "ZAZ", "MAD", "BCN"]
BCN:["ALC]"]
first split the groups
groups = s.scan(/[^,][^\[]*\[[^\[]*\]/)
# => ["TFS[MAD,GRO,BCN]", "ALC[GRO,PMI,ZAZ,MAD,BCN]"]
Now you have the groups, the rest is pretty straightforward:
groups.map {|x| [x[0..2], x[4..-2].split(',')] }
# => [["TFS", ["MAD", "GRO", "BCN"]], ["ALC", ["GRO", "PMI", "ZAZ", "MAD", "BCN"]]]
If I understood correctly, you may want to get such array.
yourexamplestring.scan(/([A-Z]{3})\[([^\]]+)/).map{|a,b|[a,b.split(',')]}
[["TFS", ["MAD", "GRO", "BCN"]], ["ALC", ["GRO", "PMI", "ZAZ", "MAD", "BCN"]], ["BCN", ["ALC", "..."]]]

Resources