Using OptionParser for string argument input and hash assignment. What is the best way to read-in multiple variables for a single argument? How do I then assign those to a hash to reference? Here is what I have so far:
large_skus = Hash.new
small_skus = Hash.new
OptionParser.new do |opts|
opts.on("-b", "--brands bName1,bName2,bNameN", String, "Check specific brands by name") do |b|
options[:brands] = b.split(",")
end
opts.on("-l", "--large lSku1,lSku2,lSkuN", String, "Large SKUs - List CSVs") do |l|
options[:large_skus] = l.split(",")
##For each sku given
brandName = options[:brands]
large_skus[brandName] = l[$sku].to_i
##
end
opts.on("-s", "--small sSku1,sSku2,sSkuN", String, "Small SKUs - List CSVs") do |s|
options[:small_skus] = s.split(",")
##For each sku given
brandName = options[:brands]
small_skus[brandName] = s[$sku].to_i
##
end
end.parse!(ARGV)
Given an input of:
ruby test.rb --brands bName1,bName2,bNameN --large lSku1,lSku2,lSkuN --small wQueue1,wQueue2,wQueueN
This code
#!/usr/bin/env ruby
require 'ap'
require 'optparse'
options = {}
OptionParser.new do |opts|
opts.on("-b", "--brands bName1,bName2,bNameN", Array, "Check specific brands by name") do |b|
options[:brands] = b
end
opts.on("-l", "--large lSku1,lSku2,lSkuN", Array, "Large SKUs - List CSVs") do |l|
options[:large_skus] = l
end
opts.on("-s", "--small wQueue1,wQueue2,wQueueN", Array, "Small SKUs - List CSVs") do |s|
options[:small_skus] = s
end
end.parse!(ARGV)
ap options
Produces this output:
{
:brands => [
[0] "bName1",
[1] "bName2",
[2] "bNameN"
],
:large_skus => [
[0] "lSku1",
[1] "lSku2",
[2] "lSkuN"
],
:small_skus => [
[0] "wQueue1",
[1] "wQueue2",
[2] "wQueueN"
]
}
Notice that instead of using types of String for each option, I'm using Array. That lets OptionParser do the heavy lifting of parsing the elements into an array. From that point it's up to you what you do with the array elements.
I think you are approaching this the wrong way. You want your users to have to keep track of the order of the parameters they input but you don't want to do it yourself in the code!
How about you don't ask anybody to keep track of what goes with what and make it explicit:
ruby test.rb --input bName1,lSku1,wQueue1 --input bName2,lSku2,wQueue2 --input bNameN,lSkuN,wQueueN
Code:
opts.on("--input <brand,Large_Skus,Small_Skus>", "input description",
"NOTE: Can be used more than once.") do |opt|
list = opt.split(',')
unless list.lenght == 3
raise "some error because you didn't place all arguments"
end
options[:input].push list
end
result:
[ [ 'bName1', 'lSku1', 'wQueue1' ],
[ 'bName2', 'lSku2', 'wQueue2' ],
[ 'bNameN', 'lSkuN', 'wQueueN' ] ]
Related
When someone types "!disconnect", I want the bot to disconnect, using the response "PART ##{CHANNEL}".
The snippet below is not the full code, but there are end statements and everything.
CHANNEL = "SomeChannelHere"
prefix = "!"
message = "!disconnect"
commands = [
"disconnect" => "PART ##{CHANNEL}"]
commands.each do |command|
if message.include?(prefix + command)
response = commands[command]
How do I get the response?
Your commands is currently an array of hashes:
commands = ["disconnect" => "PART ##{CHANNEL}"]
#=> [{"disconnect"=>"PART #SomeChannelHere"}]
You have to use { ... } instead of [ ... ]:
commands = {"disconnect" => "PART ##{CHANNEL}"}
#=> {"disconnect"=>"PART #SomeChannelHere"}
Furthermore, the each block expects two arguments (key and value):
commands.each do |command, response|
# ...
end
You are missing the key of the hash:
commands.each do |command|
p command["disconnect"]
p command.has_key?("disconnect")
end
I am trying to iterate through an array in Ruby. I using eachmethod but getting the following error message: NoMethodError: undefined method ``each' for "1, 2":String.
I am using jruby-1.6.7. Not sure if that is the problem.
Here is the code:
#!/usr/bin/ruby
puts "Select one of these.\n1. ABC\n2. DEF\n3. GHI\n"
schemas = gets.chomp
len = schemas.size
puts schemas.split(",")
puts schemas[0..len]
schemas.each {|x| puts x}
I need some guidance with iterating through a simple array in Ruby?
Thanks.
You were on the right track:
schemas = gets.chomp
# => "1, 2"
# save the result of split into an array
arr = schemas.split(",")
# => [1, 2]
# loop the array
arr.each { |schema| puts schema }
# => 1
# => 2
You can also do this in one line, though the array won't get saved anywhere.
schemas = gets.chomp
schemas.split(",").each { |schema| puts schema }
# => 1
# => 2
You have the right idea, however you are calling the Array#each method on a String.
schemas = gets.chomp
puts schemas.split(",")
It's true that the String#split method converts a string to an array, however you never actually converted the data type. schemas is still recognized as a string.
What you could do is
schemas = schemas.split(",")
Then
schemas.each{|x| puts x}
Lets say we have the following array of strings (this array is a lot bigger):
[
'http://www.example.com?id=123456',
'http://www.example.com?id=234567'
]
As you can see, everything up to the first digit is the same in both strings. Is there a way to easily find what both strings have in common and what is different? So that I get a string like 'http://www.example.com?id=' and and array like ['123456', '234567'].
Here's a method to find the longest common prefix in an array.
def _lcp(str1, str2)
end_index = [str1.length, str2.length].min - 1
end_index.downto(0) do |i|
return str1[0..i] if str1[0..i] == str2[0..i]
end
''
end
def lcp(strings)
strings.inject do |acc, str|
_lcp(acc, str)
end
end
lcp [
'http://www.example.com?id=123456',
'http://www.example.com?id=234567',
'http://www.example.com?id=987654'
]
#=> "http://www.example.com?id="
lcp [
'http://www.example.com?id=123456',
'http://www.example.com?id=123457'
]
#=> "http://www.example.com?id=12345"
# This is an approach using higher level ruby std-lib components instead of a regex.
# Why re-invent the wheel?
module UriHelper
require 'uri'
require 'cgi'
# Take an array of urls and extract the id parameter.
# #param urls {Array} an array of urls to parse
# #returns {Array}
def UriHelper.get_id_params( urls )
urls.map do |u|
puts u
uri = URI(u)
params = CGI::parse(uri.query)
params["id"].first # returned
end
end
end
require "test/unit"
# This is unit test proving our helper works as intended
class TestUriHelper < Test::Unit::TestCase
def test_get_id_params
urls = [
'http://www.example.com?id=123456',
'http://www.example.com?id=234567'
]
assert_equal("123456", UriHelper.get_id_params(urls).first )
assert_equal("234567", UriHelper.get_id_params(urls).last )
end
end
I have a rather simple regexp, but I wanted to use named regular expressions to make it cleaner and then iterate over results.
Testing string:
testing_string = "111x222b333"
My regexp:
regexp = %r{
(?<width> [0-9]{3} ) {0}
(?<height> [0-9]{3} ) {0}
(?<depth> [0-9]+ ) {0}
\g<width>x\g<height>b\g<depth>
}x
dimensions = regexp.match(testing_string)
This work like a charm, but heres where the problem comes:
dimensions.each { |k, v| dimensions[k] = my_operation(v) }
# ERROR !
undefined method `each' for #<MatchData "111x222b333" width:"111" height:"222" depth:"333">.
There is no .each method in MatchData object, and I really don't want to monkey patch it.
How can I fix this problem ?
I wasn't as clear as I thought: the point is to keep names and hash-like structure.
If you need a full Hash:
captures = Hash[ dimensions.names.zip( dimensions.captures ) ]
p captures
#=> {"width"=>"111", "height"=>"222", "depth"=>"333"}
If you just want to iterate over the name/value pairs:
dimensions.names.each do |name|
value = dimensions[name]
puts "%6s -> %s" % [ name, value ]
end
#=> width -> 111
#=> height -> 222
#=> depth -> 333
Alternatives:
dimensions.names.zip( dimensions.captures ).each do |name,value|
# ...
end
[ dimensions.names, dimensions.captures ].transpose.each do |name,value|
# ...
end
dimensions.names.each.with_index do |name,i|
value = dimensions.captures[i]
# ...
end
So today a new Ruby version (2.4.0) was released which includes many new features, amongst them feature #11999, aka MatchData#named_captures. This means you can now do this:
h = '12'.match(/(?<a>.)(?<b>.)(?<c>.)?/).named_captures
#=> {"a"=>"1", "b"=>"2", "c"=>nil}
h.class
#=> Hash
So in your code change
dimensions = regexp.match(testing_string)
to
dimensions = regexp.match(testing_string).named_captures
And you can use the each method on your regex match result just like on any other Hash, too.
I'd attack the whole problem of creating the hash a bit differently:
irb(main):052:0> testing_string = "111x222b333"
"111x222b333"
irb(main):053:0> hash = Hash[%w[width height depth].zip(testing_string.scan(/\d+/))]
{
"width" => "111",
"height" => "222",
"depth" => "333"
}
While regex are powerful, their siren-call can be too alluring, and we get sucked into trying to use them when there are more simple, or straightforward, ways of accomplishing something. It's just something to think about.
To keep track of the number of elements scanned, per the OPs comment:
hash = Hash[%w[width height depth].zip(scan_result = testing_string.scan(/\d+/))]
=> {"width"=>"111", "height"=>"222", "depth"=>"333"}
scan_result.size
=> 3
Also hash.size will return that, as would the size of the array containing the keys, etc.
#Phrogz's answer is correct if all of your captures have unique names, but you're allowed to give multiple captures the same name. Here's an example from the Regexp documentation.
This code supports captures with duplicate names:
captures = Hash[
dimensions.regexp.named_captures.map do |name, indexes|
[
name,
indexes.map { |i| dimensions.captures[i - 1] }
]
end
]
# Iterate over the captures
captures.each do |name, values|
# name is a String
# values is an Array of Strings
end
If you want to keep the names, you can do
new_dimensions = {}
dimensions.names.each { |k| new_dimensions[k] = my_operation(dimensions[k]) }
Input
cycle = 4
order = []
order[0] = [
/foobar/, /vim/
]
order[1] = [ /simple/,/word/, /.*/ ]
record = [ 'vim', 'foobar', 'foo', 'word', 'bar', 'something', 'something1', 'something2', 'something3', 'something4']
Requirement
I want to make a list named report. Original source is record which is an one-dimension array. All elements of record will be split into different group and sorted. The group and order is defined in order.
This is pseudo code:
order.each do |group|
group.each do |pattern|
record.each do |r|
if r =~ pattern
#report[# of group][# of row][ # of element (max is 4th)] = r
end
end
end
end
Please note:
the element number in a [row] is 4 which is defined in cycle.
[# of row] : If # of element > 4 , # of row will + 1
Every element(string) in report is unique.
Expected output:
require 'ap'
ap report
[
[0] [
[0] [
[0] "foobar",
[1] "vim"
]
],
[1] [
[0] [
[0] "word",
[1] "foo",
[2] "bar",
[3] "something"
],
[1] [
[0] "something1",
[1] "something2"
[2] "something3"
[3] "something4"
]
]
]
This should do it (though it's not very pretty):
report = []
record.uniq!
order.each_with_index do |group, gi|
group.each do |pattern|
record.select { |r| r =~ pattern }.each do |match|
report[gi] ||= [[]]
report[gi] << [] if report[gi].last.length == cycle
report[gi].last << match
end
record.delete_if { |r| r =~ pattern }
end
end
puts report.inspect
#=> [[["foobar", "vim"]], [["word", "foo", "bar", "something"], ["something1", "something2", "something3", "something4"]]]
Note that record is mutated, so if you need it to remain the same you should dup it.
Here's another approach. I'm still not entirely happy with this -- couldn't figure out how to boil down the last two steps into one. Also it ended up having more lines than Andrew Marshall's answer. Boo.
Spec attached.
require 'spec_helper'
def report(cycle, order, record)
record.uniq!
order.each_with_index.map do |pattern_list, index|
pattern_list.map do |pattern|
record.each_with_index.inject([]) do |memo, (item, item_index)|
memo.tap do
if pattern =~ item
memo << item
record[item_index] = nil
end
end
end
end.flatten
end.map do |items|
items.each_with_index.group_by do |item, index|
index.div(cycle)
end.map do |ordering, item_with_index|
item_with_index.map(&:first)
end
end
end
describe 'report' do
let(:cycle) { 4 }
let(:order) { [
[/foobar/, /vim/],
[/simple/,/word/, /.*/]
] }
let(:record) {
[ 'vim', 'foobar', 'foo', 'word', 'bar', 'something', 'something1', 'something2', 'something3', 'something4']
}
it "just works" do
report(cycle, order, record.dup).should == [
[["foobar","vim"]],
[["word","foo","bar","something"],["something1","something2","something3","something4"]]
]
end
end
Simple answer, you can use each_with_index which works similar to each but gives you the index if the loop as the second parameter.
I can't give you a full example sadly, as I didn't fully understand your use case. However with the documentation you should be able to proceed.