Processing nested arrays - ruby

Given the following code (where I display some statistics about a number of bookings):
statistics = [["Germany", "EUR"], 23], [["Germany", "USD"], 42], [["Spain", "EUR"], 17]
statistics.each do |country_and_currency, number_of_bookings|
country, currency = country_and_currency # <-- Ugly.
puts "There are #{number_of_bookings} in #{currency} in #{country}"
end
The country_and_currency part is quite ugly. I tried ... do |*(country, currency), number_of_bookings|, but this did not work.
Is there an elegant way to process this nested array without using the country_and_currency variable?

Yes, it's possible:
statistics = [
[["Germany", "EUR"], 23],
[["Germany", "USD"], 42],
[["Spain", "EUR"], 17]
]
statistics.each do |(country, currency), number_of_bookings|
puts "There are #{number_of_bookings} in #{currency} in #{country}"
end
Output
There are 23 in EUR in Germany
There are 42 in USD in Germany
There are 17 in EUR in Spain

statistics.each do |(country, currency), number_of_bookings|
puts "There are #{number_of_bookings} in #{currency} in #{country}"
end

Related

Reading string by string in a line - Ruby

I have an input text file "input.txt" that looks like this:
Country Code ID QTY
FR B000X2D 75 130
FR B000X2E 75 150
How do I extract the first, second and the third string from each line?
This code maps a whole line into one field of array:
f = File.open("input.txt", "r")
line_array = []
f.each_line { |line| line_array << line }
f.close
puts line_array[1]
Which outputs:
FR B000X2D 75 130
Furthermore, how can I split one line into more lines based on a quantity number,
max(quantity) = 50 per line
so that the output is:
FR B000X2D 75 50
FR B000X2D 75 50
FR B000X2D 75 30
If this is space delimited, should be pretty easy to split things up:
File.readlines('input.txt').map do |line|
country, code, id, qty = line.chomp.split(/\s+/)
[ country, code, id.to_i, qty.to_i ]
end
You can also easily reject any rows you don't want, or select those you do, plus this helps with stripping off headers:
File.readlines('input.txt').reject do |line|
line.match(/\ACountry/i)
end.map do |line|
country, code, id, qty = line.chomp.split(/\s+/)
[ country, code, id.to_i, qty.to_i ]
end.select do |country, code, id, qty|
qty <= 50
end
Use the CSV class if these are tab separated entries. CSV stands for "comma separated values" but the you can provide your own separator
require 'csv'
CSV.foreach("fname", :row_sep => "\t") do |row|
# use row here...
end
See https://ruby-doc.org/stdlib-2.0.0/libdoc/csv/rdoc/CSV.html

What happen if we do operations on mocked objects? Rspec

Firsty I would say that I'm learning Ruby and TDD program, so please be forgiving for me. Acctualy I have two questions releted with each other but firsty please take a look on this code. Its part of my unit test for class Order:
context 'with products' do
let(:result) { instance_double('Money', value: 20, currency: 'EUR') }
let(:xxx) { instance_double('Money', value: 10, currency: 'EUR') }
let(:money2) { instance_double('Money', value: 10, currency: 'EUR',:+ => xxx , :to_s => '10.00 EUR' ) }
let(:money) { instance_double('Money', value: 10, currency: 'EUR', :+ => money2, :to_s => '10.00 EUR') }
let(:product1) { instance_double('Product', price: money2) }
let(:product2) { instance_double('Product', price: money) }
let(:products) { [product1, product2] }
it 'returns sum of product prices' do
#Real objects
product1 = Product.new
product1.price = Money.new('1.23', 'EUR')
product1.name = product1
product2 = Product.new
product2.price = Money.new('1.23', 'EUR')
product2.name = product2
products1 = [product1,product2]
puts products1.map!(&:price)
#Fake
puts "fakeproducts map"
fakeproducts = products.map!(&:price)
puts fakeproducts
puts "Sum of fakeproducts"
puts Money.sum(fakeproducts)
puts Money.sum(fakeproducts).to_s
puts "methods of sum fakeproducts"
puts Money.sum(fakeproducts).methods
expect(Order.new(full_name, date, products).total_amount).to eql result
end
end
Problem is that my real objects works,but mocked objects doens't . Error which I got:
Failure/Error: expect(Order.new(full_name, date, products).total_amount).to eql result
Double "Money (instance)" received unexpected message :price with (no args)
Total_amount function:
def total_amount
return 0 if products.empty?
asd = products.map!(&:price)
Money.sum(asd)
end
and Money.sum looks like this:
def self.sum(moneys)
moneys.group_by(&:currency).values.map(&:sum)
end
I suspect that, when I do some operations on my mocked object it lose its properties.
The questions are:
Is it normal that this things happen?
What is Solutions for this problem? Should I mock result of my function?
Your use of map! in total_amount is clobbering the products associated with the order, replacing them with their price, so that the next time you call price on the order's products (e.g. as in a subsequent call to total_amount), you are sending price to one of your money doubles.
You can avoid this particular symptom by using map instead of map! inside of total_amount.
As a related side, it's much easier to provide help with these kinds of questions if you provide the stack trace with your error and identify the corresponding source lines in your code.

I have an issue with require 'yaml' ,anyone can shed any light?

This is my code:
require 'yaml'
class Person
attr_accessor :name, :age
end
yaml_string = <<END_OF_DATA
---
-!ruby/object:Person
age: 45
name: Jimmy
- !ruby/object:Person
age:23
name: Laura Smith
END_OF_DATA
test_data = YAML::load(yaml_string)
puts test_data[0].name
puts test_data[1].name
This is the result I get:
ruby yaml1.rb
C:/Ruby200/lib/ruby/2.0.0/psych.rb:205:in parse': (<unknown>): mapping values are not allowed in this context at line 3 column 4 (Psych::SyntaxError)
from C:/Ruby200/lib/ruby/2.0.0/psych.rb:205:inparse_stream'
from C:/Ruby200/lib/ruby/2.0.0/psych.rb:153:in parse'
from C:/Ruby200/lib/ruby/2.0.0/psych.rb:129:inload'
from yaml1.rb:17:in `'
Exit code: 1
According to the book i'm reading (Beggining Ruby by Peter Cooper). My result should be like the one below:
Jimmy
Laura Smith
Anyone know why this is happening ? What am I doing wrong ?
Your YAML is not formatted properly, I guess wrote it by hand. Here's a correct version
---
- !ruby/object:Person
age: 45
name: Jimmy
- !ruby/object:Person
age: 23
name: Laura Smith
In case you didn't spot the differences, here they are
The entries age: ... and name: ... need to be indented
A space was missing in the second line (-!ruby/object:Person) between the dash (-) and the bang (!)
A space is needed between the number 23 and the colon in the line age:23

Ruby > Psych -> How to parse yaml docs in to ruby objects

I'm trying to do the following:
open and read a file with multiple yaml docs
parse yaml docs into ruby objects
print content of each ruby object
and the code:
yml_string = Psych.dump(File.read(infile))
Psych.load_stream(yml_string) .each do |mobj|
puts "mobj:\n #{mobj}"
end
The puts prints the contents of the yml_string (multiple yaml docs) but it is one long string. How does one go about parsing each yaml doc from the yml_string and store them in to ruby objects?
The contents of infile (based on OP's comment):
---
member:
country: AF
phone1: 60 223-4564
phone2: +93 799 123-456
---
member:
country: BR
phone1: +55 55 2000 3456
phone2: 55 9000 1234
---
member:
country: CA
phone1: 604 423-4567
phone2: +1 604 423-4567
This is what I ended up with
yaml_hash = Psych.load_stream(File.read(infile))
yaml_hash.each do |member|
mem = Hash[member['member']]
end
Thank you for all your help.
require 'yaml'
require 'pp'
infile = "test.yml"
pp YAML.load_stream(File.read(infile))
# [{"member"=>
# {"country"=>"AF", "phone1"=>"60 223-4564", "phone2"=>"+93 799 123-456"}},
# {"member"=>
# {"country"=>"BR", "phone1"=>"+55 55 2000 3456", "phone2"=>"55 9000 1234"}},
# {"member"=>
# {"country"=>"CA", "phone1"=>"604 423-4567", "phone2"=>"+1 604 423-4567"}}]
On recent MRI psych is the same as yaml lib
p [RUBY_VERSION, YAML == Psych]
["2.0.0", true]
p [RUBY_VERSION, YAML == Psych]
["1.9.3", true]

Split a complex file into a hash

I am running a command line program, called Primer 3. It takes an input file and returns data to standard output. I am trying to write a Ruby script which will accept that input, and put the entries into a hash.
The results returned are below. I would like to split the data on the '=' sign, so that the has would something like this:
{:SEQUENCE_ID => "example", :SEQUENCE_TEMPLATE => "GTAGTCAGTAGACNAT..etc", :SEQUENCE_TARGET => "37,21" etc }
I would also like to lower case the keys, ie:
{:sequence_id => "example", :sequence_template => "GTAGTCAGTAGACNAT..etc", :sequence_target => "37,21" etc }
This is my current script:
#!/usr/bin/ruby
puts 'Primer 3 hash'
primer3 = {}
while line = gets do
name, height = line.split(/\=/)
primer3[name] = height.to_i
end
puts primer3
It is returning this:
Primer 3 hash
{"SEQUENCE_ID"=>0, "SEQUENCE_TEMPLATE"=>0, "SEQUENCE_TARGET"=>37, "PRIMER_TASK"=>0, "PRIMER_PICK_LEFT_PRIMER"=>1, "PRIMER_PICK_INTERNAL_OLIGO"=>1, "PRIMER_PICK_RIGHT_PRIMER"=>1, "PRIMER_OPT_SIZE"=>18, "PRIMER_MIN_SIZE"=>15, "PRIMER_MAX_SIZE"=>21, "PRIMER_MAX_NS_ACCEPTED"=>1, "PRIMER_PRODUCT_SIZE_RANGE"=>75, "P3_FILE_FLAG"=>1, "SEQUENCE_INTERNAL_EXCLUDED_REGION"=>37, "PRIMER_EXPLAIN_FLAG"=>1, "PRIMER_THERMODYNAMIC_PARAMETERS_PATH"=>0, "PRIMER_LEFT_EXPLAIN"=>0, "PRIMER_RIGHT_EXPLAIN"=>0, "PRIMER_INTERNAL_EXPLAIN"=>0, "PRIMER_PAIR_EXPLAIN"=>0, "PRIMER_LEFT_NUM_RETURNED"=>0, "PRIMER_RIGHT_NUM_RETURNED"=>0, "PRIMER_INTERNAL_NUM_RETURNED"=>0, "PRIMER_PAIR_NUM_RETURNED"=>0, ""=>0}
Data source
SEQUENCE_ID=example
SEQUENCE_TEMPLATE=GTAGTCAGTAGACNATGACNACTGACGATGCAGACNACACACACACACACAGCACACAGGTATTAGTGGGCCATTCGATCCCGACCCAAATCGATAGCTACGATGACG
SEQUENCE_TARGET=37,21
PRIMER_TASK=pick_detection_primers
PRIMER_PICK_LEFT_PRIMER=1
PRIMER_PICK_INTERNAL_OLIGO=1
PRIMER_PICK_RIGHT_PRIMER=1
PRIMER_OPT_SIZE=18
PRIMER_MIN_SIZE=15
PRIMER_MAX_SIZE=21
PRIMER_MAX_NS_ACCEPTED=1
PRIMER_PRODUCT_SIZE_RANGE=75-100
P3_FILE_FLAG=1
SEQUENCE_INTERNAL_EXCLUDED_REGION=37,21
PRIMER_EXPLAIN_FLAG=1
PRIMER_THERMODYNAMIC_PARAMETERS_PATH=/usr/local/Cellar/primer3/2.3.4/bin/primer3_config/
PRIMER_LEFT_EXPLAIN=considered 65, too many Ns 17, low tm 48, ok 0
PRIMER_RIGHT_EXPLAIN=considered 228, low tm 159, high tm 12, high hairpin stability 22, ok 35
PRIMER_INTERNAL_EXPLAIN=considered 0, ok 0
PRIMER_PAIR_EXPLAIN=considered 0, ok 0
PRIMER_LEFT_NUM_RETURNED=0
PRIMER_RIGHT_NUM_RETURNED=0
PRIMER_INTERNAL_NUM_RETURNED=0
PRIMER_PAIR_NUM_RETURNED=0
=
$ primer3_core < example2 | ruby /Users/sean/Dropbox/bin/rb/read_primer3.rb
#!/usr/bin/ruby
puts 'Primer 3 hash'
primer3 = {}
while line = gets do
key, value = line.split(/=/, 2)
primer3[key.downcase.to_sym] = value.chomp
end
puts primer3
For fun, here are a couple of purely-functional solutions. Both assume that you've already pulled your data from the file, e.g.
my_data = ARGF.read # read the file passed on the command line
This one feels sort of gross, but it is a (long) one-liner :)
hash = Hash[ my_data.lines.map{ |line|
line.chomp.split('=',2).map.with_index{ |s,i| i==0 ? s.downcase.to_sym : s }
} ]
This one is two lines, but feels cleaner than using with_index:
keys,values = my_data.lines.map{ |line| line.chomp.split('=',2) }.transpose
hash = Hash[ keys.map(&:downcase).map(&:to_sym).zip(values) ]
Both of these are likely less efficient and certainly more memory-intense than your already-accepted answer; iterating the lines and slowly mutating your hash is the best way to go. These non-mutating variations are just a mental exercise.
Your final answer should use ARGF to allow filenames on the command line or via STDIN. I would write it like so:
#!/usr/bin/ruby
module Primer3
def self.parse( file )
{}.tap do |primer3|
# Process one line at a time, without reading it all into memory first
file.each_line do |line|
key, value = line.chomp.split('=', 2)
primer3[key.downcase.to_sym] = value
end
end
end
end
Primer3.parse( ARGF ) if __FILE__==$0
This way you can either call the file from the command line, with or without STDIN, or you can require this file and use the module function it defines in other code.
OK I have it (almost). The only problem is it is adding a \n at the end of each value.
puts 'Primer 3 hash'
primer3 = {}
while line = gets do
key, value = line.split(/\=/)
puts key
puts value
primer3[key.downcase] = value
end
puts primer3
{"sequence_id"=>"example\n", "sequence_template"=>"GTAGTCAGTAGACNATGACNACTGACGATGCAGACNACACACACACACACAGCACACAGGTATTAGTGGGCCATTCGATCCCGACCCAAATCGATAGCTACGATGACG\n", "sequence_target"=>"37,21\n", "primer_task"=>"pick_detection_primers\n", "primer_pick_left_primer"=>"1\n", "primer_pick_internal_oligo"=>"1\n", "primer_pick_right_primer"=>"1\n", "primer_opt_size"=>"18\n", "primer_min_size"=>"15\n", "primer_max_size"=>"21\n", "primer_max_ns_accepted"=>"1\n", "primer_product_size_range"=>"75-100\n", "p3_file_flag"=>"1\n", "sequence_internal_excluded_region"=>"37,21\n", "primer_explain_flag"=>"1\n", "primer_thermodynamic_parameters_path"=>"/usr/local/Cellar/primer3/2.3.4/bin/primer3_config/\n", "primer_left_explain"=>"considered 65, too many Ns 17, low tm 48, ok 0\n", "primer_right_explain"=>"considered 228, low tm 159, high tm 12, high hairpin stability 22, ok 35\n", "primer_internal_explain"=>"considered 0, ok 0\n", "primer_pair_explain"=>"considered 0, ok 0\n", "primer_left_num_returned"=>"0\n", "primer_right_num_returned"=>"0\n", "primer_internal_num_returned"=>"0\n", "primer_pair_num_returned"=>"0\n", ""=>"\n"}

Resources