Ruby Axlsx how to add row with merged cells - ruby

I want to generate the below excel:
I tried bellow code
row = [1, 2, [31, 32]]
p = Axlsx::Package.new
wb = p.workbook
wb.add_worksheet(:name => "Sheet1") do |sheet|
sheet.add_row row
end
But I get the bellow result
|column1|column2| column3 |
| 1 | 2 | [31, 32]|

axlsx cell merging cannot be performed during row insertion.
What you want to do here is insert row 1 using [1, 2, 31] and row 2 using [nil, nil, 32] and then perform your merging after insert.
Have a look at the example:
require 'axlsx'
package = Axlsx::Package.new
package.workbook do |workbook|
workbook.add_worksheet name: 'merged_cells' do |sheet|
4.times do
sheet.add_row %w(a b c d e f g)
end
sheet.merge_cells "A1:A2"
sheet.merge_cells "B1:B2"
end
end
https://github.com/randym/axlsx/blob/master/examples/merge_cells.rb
It will likely get you most of the way there.

Related

How to extract multiple columns from a csv file with ruby?

Right now I can extract 1 column (column 6) from the csv file. How could I edit the script below to extract more than 1 column? Let's say I also want to extract column 9 and 10 as well as 6. I would want the output to be such that column 6 ends up in column 1 of the output file, 9 in the 2nd column of the output file, and column 10 in the 3rd column of the output file.
ruby -rcsv -e 'CSV.foreach(ARGV.shift) {|row| puts row [5]}' input.csv &> output.csv
Since row is an array, your question boils down to how to pick certain elements from an array; this is not related to CSV.
You can use values_at:
row.values_at(5,6,9,10)
returns the fields 5,6,9 and 10.
If you want to present these picked fields in a different order, it is however easier to map each index explicitly:
output_row = Array.new(row.size) # Or row.dup, depending on your needs
output_row[1] = row[6]
# Or, if you have used row.dup and want to swap the rows:
output_row[1],output_row[6] = row[6],row[1]
# and so on
out_csv.puts(output_row)
This assumes that you have defined before
out_csv=CSV.new(STDOUT)
since you want to have your new CSV be created on standard output.
Let's first create a (header-less) CSV file:
enum = 1.step
FNameIn = 't_in.csv'
CSV.open(FNameIn, "wb") { |csv| 3.times { csv << 5.times.map { enum.next } } }
#=> 3
I've assumed the file contains string representations of integers.
The file contains the three lines:
File.read(FNameIn).each_line { |line| p line }
"1,2,3,4,5\n"
"6,7,8,9,10\n"
"11,12,13,14,15\n"
Now let's extract the columns at indices 1 and 3. These columns are to be written to the output file in that order.
cols = [1, 3]
Now write to the CSV output file.
arr = CSV.read(FNameIn, converters: :integer).
map { |row| row.values_at(*cols) }
#=> [[2, 4], [7, 9], [12, 14]]
FNameOut = 't_out.csv'
CSV.open(FNameOut, 'wb') { |csv| arr.each { |row| csv << row } }
We have written three lines:
File.read(FNameOut).each_line { |line| p line }
"2,4\n"
"7,9\n"
"12,14\n"
which we can read back into an array:
CSV.read(FNameOut, converters: :integer)
#=> [[2, 4], [7, 9], [12, 14]]
A straightforward transformation of these operations is required to perform these operations from the command line.

Exporting unequal number of array output to a file using Ruby

I have keys and data [sic] as follows, which I need to export in a text file.
keys = %w[ID No time]
Data = ["a", ["1", "2", "3", "4"], 20]
My desired output is:
ID No time
a 1 20
a 2 20
a 3 20
a 4 20
I had attempted the following code so far:
File.open('test1.txt', 'w') {|f| f.write Data.join("\t")}
But it doesn't show my desired output.
Any direction regarding this would be highly appreciated.
Update :
Just extending the question :
if there are same Keys and a block of Data (Data1,Data2, Data3 ,...) how to efficiently concatenate and export the total output to a text file?
Data1 = [a, [1, 2, 3, 4], 20]
Data2 = [b,[5,6,7,8],8]
Data3 =[c,[9,10,11,13],10]
require 'csv'
keys = %w(ID No time)
data = ['a', [1, 2, 3, 4], 20]
id, numbers, time = data
CSV.open('test1.txt', 'w', headers: keys, write_headers: true, col_sep: "\t") do |csv|
numbers.each do |number|
csv << [id, number, time]
end
end
Without using csv library:
keys = %w[ID No time]
data = ["a", ["1", "2", "3", "4"], 20]
File.open('test1.txt', 'w') do |file|
file.write(keys.join("\t")+"\n")
data[1].map { |x| file.write("#{data[0]}\t#{x}\t#{data[2]}\n") }
end
For multiple data:
data_array = []
data_array << data1
data_array << data2
data_array << data3
.....
Which results data_array as:
data_array = [['a', [1, 2, 3, 4], 20], ['b',[5,6,7,8],8], ['c',[9,10,11,13],10]]
File.open('test1.txt', 'w') do |file|
file.write(keys.join("\t")+"\n")
data_array.each do |data|
data[1].map { |x| file.write("#{data[0]}\t#{x}\t#{data[2]}\n") }
end
end
Just extending the question :
if there are same Keys and a block of Data (Data1,Data2, Data3 ,...) how to efficiently concatenate and export the total output to a text file?
Data1 = [a, [1, 2, 3, 4], 20]
Data2 = [b,[5,6,7,8],8]
Data3 =[c,[9,10,11,13],10]

How to remove some items from a big array

I want to check the files that don't exist, so I write a code as follows:
MAX_ID = 43148178
def extract_ids
done = Dir['res/*.html'].map {|name| name[/\d+/].to_i}
all = (1..MAX_ID).to_a
all.delete_if { |i| done.include?(i) }
all.shuffle
end
ls res | wc -l returns 35854.
I find that this is slow. How do I do this effectively?
If 'done' is an array of items you wish to remove from the 'all' array, you can simply do this:
all = [1,2,3,4,5,6,7,8,9,10]
done = [1,3,5]
all - done
# => [2, 4, 6, 7, 8, 9, 10]
Or, as you want to change the all array
all -= done

How to change more than one string variables to integer and minus 1?

I want to change 8 strings to integer type and minus 1. I wrote the code as:
foo1, foo2, too3 ... = foo1.to_i - 1, foo2.to_i - 1, foo3.to_i -1, ...
But I think it's too complex. Are there some better ways to achieve this goal?
[:foo1, :foo2, ... etc. ...].each { |foo| eval "#{foo} = #{foo}.to_i - 1" }
Although it's a bad idea if you've decided to do it.
Putting them in an Array would be the simplest thing.
%w{ 1 2 3 4 5 6 7 8 }.map!(&:to_i).map!(&:pred)
=> [0, 1, 2, 3, 4, 5, 6, 7]
You should not use this, eval is dangerous and dynamic. I would recommend moving your values into a hash where you can control the keys. If you want to do literally what you asked:
(1..2).map {|n | eval("foo#{n}").to_i - 1}
Example:
> foo1 = 2
=> 2
> foo2 = 3
=> 3
> (1..2).map {|n | eval("foo#{n}").to_i - 1}
=> [1, 2]
... non-terrifying way to store/process these values:
> hash = { :foo1 => 2, :foo2 => 3 }
=> {:foo1=>2, :foo2=>3}
hash.keys.inject({}) { |h, key| h[key] = hash[key].to_i - 1; h }
=> {:foo1=>1, :foo2=>2}
When you are working with 8 variables and need to do the same operation on them it usually means that they are linked somehow and can be grouped, for example in a hash:
data = {:foo1 => "1", :foo2 => "2", ...}
data2 = Hash[data.map { |key, value| [key, value.to_i - 1] }]
Note that instead of updating inplace values I create a new object, a functional approach is usually more clear.
Based on your comment to #Winfields solution this might be enough:
foo1 = "123"
foo2 = "222"
too3 = "0"
def one_less_int(*args) # the splat sign (#) puts all arguments in an array
args.map{|str| str.to_i-1}
end
p one_less_int(foo1, foo2, too3) #=> [122, 221, -1]
But putting everything in an array beforehand, as others are suggesting, is more explicit.

Active Record order by group size

I've got an active record query where I'm using group_by
#foo = Foo.group_by(&:relation)
Then in the view i'm using
#foo.each do |group, values|
group x has values.count elements
end
Is there a way I could sort these by the count of each group?
group_by is not a ActiveRecord method, group is. group_by is a Enumerator method.
What about
#foo = Foo.group('relation').order('count_id asc').count('id')
Taken from "Order by" result of "group by" count?.
Otherwise, if you want to sort it on Ruby level, you could do
disordered_hash = {:two=>[1, 2], :one=>[1], :three=>[1, 2, 3]}
ordered_array = disordered_hash.sort {|k, v| k[1].count <=> v[1].count} # add .reverse if you want
# => [[:one, [1]], [:two, [1, 2]], [:three, [1, 2, 3]]]

Resources