I need to sort (ascending order) a table by a field.
This is my table:
vTable=Text::Table.new
vTable.head= ["Value", "Rest", "NumberRun"]
I store my data in the table inside a loop
lp= [value, rest, num]
vTable.rows << lp
After completing my table, it looks like:
33183 | 109 | 5.10.0.200
34870 | 114 | 5.4.1.100
28437 | 93 | 5.6.0.050
31967 | 105 | 5.6.2.500
29942 | 98 | 5.7.2.900
I would like to sort, ascending order, this table by "NumberRun" considering that 5.10.0.200 is bigger than 5.7.2.900.
My idea was to remove "." from "NumberRun", convert it into a number, and then sort. In this way 5.10.x > 5.9.x
You can sort the rows the following way. It uses the Array#sort_by!() method, which was introduced with Ruby 1.9.2.
vTable.rows.sort_by! { |_,_,num| num.split('.').map(&:to_i) }
It also uses Destructuring for the block parameters of sort_by!().
For Ruby versions older than 1.9.2 you can sort the rows and reassign them.
vTable.rows = vTable.rows.sort_by { |_,_,num| num.split('.').map(&:to_i) }
I have created a NumberRun class for you that can do the comparision.
#number_run.rb
class NumberRun
include Comparable
attr_accessor :run_number
def initialize(str)
#run_number = str
end
def <=> str1
str_split = #run_number.split(".")
str1_split = str1.run_number.split(".")
output = 0
str_split.each_with_index do |num, index|
if((num.to_i <=> str1_split[index].to_i) != 0 && index != (str_split.count - 1))
output = (num.to_i <=> str1_split[index].to_i)
break
elsif index == (str_split.count - 1)
output = (num.to_i <=> str1_split[index].to_i)
end
end
output
end
end
For an example:
a = [
NumberRun.new('5.10.0.200'),
NumberRun.new('5.4.1.100'),
NumberRun.new('5.6.0.050'),
NumberRun.new('5.6.2.500'),
NumberRun.new('5.7.2.900')
]
a.sort.map(&:run_number)
Related
I have 2 CSV files with columns like A, B, C.. & D, E, F. I want to join these two CSV files into a new file with rows where File1.B = File2.E and the row would have columns A, B/E, C, D, F. How can I achieve this JOIN without using tables?
Givens
We are given the following.
The paths for the two input files:
fname1 = 't1.csv'
fname2 = 't2.csv'
The path for the output file:
fname3 = 't3.csv'
The names of the headers to match in each of the two input files:
target1 = 'B'
target2 = 'E'
I do assume that (as is the case with the example) the two files necessarily contain the same number of lines.
Create test files
Let's first create the two files:
str = [%w|A B C|, %w|1 1 1|, %w|2 2 2|, %w|3 4 5|, %w|6 9 9|].
map { |a| a.join(",") }.join("\n")
#=> "A,B,C\n1,1,1\n2,2,2\n3,4,5\n6,9,9"
File.write(fname1, str)
#=> 29
str = [%w|D E F|, %w|21 1 41|, %w|22 5 42|, %w|23 8 45|, %w|26 9 239|].
map { |a| a.join(",") }.join("\n")
#=> "D,E,F\n21,1,41\n22,5,42\n23,8,45\n26,9,239"
File.write(fname2, str)
#=> 38
Read the input files into CSV::Table objects
When reading fname1 I will use the :header_converters option to convert the header "B" to "B/E". Note that this does not require knowledge of the location of the column with header "B" (or whatever it may be).
require 'csv'
new_target1 = target1 + "/" + target2
#=> "B/E"
csv1 = CSV.read(fname1, headers: true,
header_converters: lambda { |header| header==target1 ? new_target1 : header})
csv2 = CSV.read(fname2, headers: true)
Construct arrays of headers to be written from each input file
headers1 = csv1.headers
#=> ["A", "B/E", "C"]
headers2 = csv2.headers - [target2]
#=> ["D", "F"]
Create the output file
We will first write the new headers headers1 + headers2 to the output file.
Next, for each row index i (i = 0 corresponding to the first row after the header row in each file), for which a condition is satisfied, we write as a single row the elements of csv1[i] and csv2[i] that are in the columns having headers in headers1 and headers2. The condition to be satisfied to write the rows at index i is that i satisfies:
csv1[i][new_target1] == csv2[i][target2] #=> true
Now open fname3 for writing, write the headers and then the body.
CSV.open(fname3, 'w') do |csv|
csv << headers1 + headers2
[csv1.size, csv2.size].min.times do |i|
csv << (headers1.map { |h| csv1[i][h] } +
headers2.map { |h| csv2[i][h] }) if
csv1[i][new_target1] == csv2[i][target2]
end
end
#=> 4
Let's confirm that what was written is correct.
puts File.read(fname3)
A,B/E,C,D,F
1,1,1,21,41
6,9,9,26,239
If you have CSV files like these:
first.csv:
A | B | C
1 | 1 | 1
2 | 2 | 2
3 | 4 | 5
6 | 9 | 9
second.csv:
D | E | F
21 | 1 | 41
22 | 5 | 42
23 | 8 | 45
26 | 9 | 239
You can do something like this:
require 'csv'
first = CSV.read('first.csv')
second = CSV.read('second.csv')
CSV.open("result.csv", "w") do |csv|
csv << %w[A B.E C D F]
first.each do |rowF|
second.each do |rowS|
csv << [rowF[0],rowF[1],rowF[2],rowS[0],rowS[2]] if rowF[1] == rowS[1]
end
end
end
To get this:
result.csv:
A | B.E | C | D | F
1 | 1 | 1 | 21 | 41
6 | 9 | 9 | 26 | 239
The answer is to use group by to create a hash table and then iterate over the keys of the hash table. Assuming the column you're joining on is unique in each table:
join_column = "whatever"
csv1 = CSV.table("file1.csv").group_by { |r| r[join_column] }
csv2 = CSV.table("file2.csv").group_by { |r| r[join_column] }
joined_data = csv1.keys.sort.map do |join_column_values|
csv1[join_column].first.merge(csv2[join_column].first)
end
If the column is not unique in each table, then you need to decide how you want to handle those cases since there will be more than just the first element in the arrays csv1[join_column] and csv2[join_column]. You could do an O(mxn) join as suggested in one of the other answers (i.e. nested map calls), or you could filter or combine them based on some criteria. The choice really depends on your usecase.
I'm trying to create a tic-tac-toe game. I listed only the code for the relevant class and methods I got so far:
class Box
attr_reader :name, :row, :column
attr_accessor :is_marked, :contents
def initialize(name, row, column, is_marked=false, contents)
#name = name
#row = row
#column = column
#is_marked = is_marked
#contents = contents
end
def self.display_box
print '|#{contents}|'
end
end
#generate box instances
(1..9).each do |i|
if i > 3
col = i % 3
else
col = i
end
box#{i} = Box.new('box#{i}', (i/3).ceil, col, false, '_')
end
board = [[box1, box2, box3], [box4, box5, box6], [box7, box8, box9]]
def display_board
box1.display_box; box2.display_box; box3.display_box; print '\n'
box4.display_box; box5.display_box; box6.display_box; print '\n'
box7.display_box; box8.display_box; box9.display_box; print '\n'
end
display_board
I can't figure out why creating an instance of my class throws an error. The error is:
undefined local variable or method `box1' for <Context:0x000000024df8a8>
(repl):44:in display_board'
(repl):61:in initialize'
I tried running it with and without the 'self' in the display_box method, same error.
The problem is this line.
box#{i} = Box.new('box#{i}', (i/3).ceil, col, false, '_')
It seems like you're trying to create a bunch of variables named box1, box2, ... There might be away way to do that in ruby, but that's not it. You could do it with eval, but you don't want to this. As you can see from the rest of your code, working with a big pile of variables like that is annoying.
Instead, make an Array of boxes.
boxes[i] = Box.new("box#{i}", (i/3).ceil, col, false, '_')
You need to declare boxes = [] before the loop.
Then create your board from that list using ranges.
board = [boxes[1..3], boxes[4..6], boxes[7..9]]
And display_board becomes less repetitive.
def display_board(board)
board.each { |row|
row.each { |box|
box.display_box
}
puts "\n"
}
end
Finally, if you want to interpolate variables in strings you have to use ". For example, in display_box.
def display_box
print "|#{contents}|"
end
This is an error:
box#{i}
You can interpolate into Strings(and regexes), but a variable name is not a
String. A String has quotes around it.
Even if you could interpolate into variable names, you would never do this:
box#{i} = Box.new('box#{i}', (i/3).ceil, col, false, '_')
Instead, you would create an Array and add instances to the Array:
boxes = []
(1..9).each do |i|
if i > 3
col = i % 3
else
col = i
end
boxes << Box.new('box#{i}', (i/3).ceil, col, false, '_')
end
Then the names of your instances are boxes[0], boxes[1], etc.
You also need to know that a def creates a new scope and variables outside the def cannot be seen inside the def. So any box1, box2, etc. variables created outside the def cannot be seen inside the def. What you need to do is pass the boxes Array to the display_board() method, like this:
def display_board(board_boxes)
...
end
display_board(boxes)
Ruby then lines up your method call and the method header like this:
display_board(boxes)
|
V
def display_board(board_boxes)
and the boxes Array gets assigned to a variable called board_boxes:
board_boxes = boxes
Then inside the def, board_boxes will be the Array containing the boxes.
Don't combine lines of code on one line using semi colons. When you use an Array to store your box instances, you can display your boxes like this:
board_boxes.each do |box|
box.display_box
end
If you want to print a newline after every three boxes, you can do this:
count = 1
board_boxes.each do |box|
box.display_box
print "\n" if count%3 == 0
count += 1
end
You get undefined local variable or method `box1' because box1 is not defined.
Apparently, you expected this line to create box1:
box#{i} = ...
but it is really just this:
box
because # starts a comment.
Double-quotes strings allow interpolation, but you can't interpolate variable names like this.
How to solve it?
It is almost always a bad idea to create variables dynamically. If you have a large or a varying number of values, you should store them in a collection - either an Array, a Hash, or a custom one.
The box object
Setting up the boxes is quite complex, because you are passing name, row and column to each box. Does a box really have to know its position within the board? Think about other objects that are part of a larger structure: an array element doesn't know its index (the array does), a character doesn't know its position (the string does) and a hash value doesn't know its key (the hash does).
Let's remove these extra attributes from Box and have it just store its content:
class Box
attr_accessor :content
def initialize
#content = '_'
end
def marked?
content != '_'
end
end
b = Box.new #=> #<Box:0x007fab7a9f58a8 #content="_">
b.content #=> "_"
b.marked? #=> false
b.content = 'X'
b.marked? #=> true
The board object
To create a 3×3 array of boxes, we can write: (I've removed the object ids from the output)
board = Array.new(3) { Array.new(3) { Box.new } }
#=> [[#<Box #content="_">, #<Box #content="_">, #<Box #content="_">],
# [#<Box #content="_">, #<Box #content="_">, #<Box #content="_">],
# [#<Box #content="_">, #<Box #content="_">, #<Box #content="_">]]>
Single boxes can be accessed via: (indices are zero-based)
board[0][0] #=> #<Box #content="_">
But since board is such an important object, I'd write a custom class:
class Board
def initialize
#boxes = Array.new(3) { Array.new(3) { Box.new } }
end
def box(x, y)
#boxes[x][y]
end
end
The class does not expose #boxes, because we don't want the array to be changed. It provides a method Board#box instead that returns the box for the given x and y coordinates.
Formatting
In my opinion neither Box nor Board should contain code that handles formatting, so let's write a small utility class to handle this part:
class BoardFormatter
def draw(board)
template.gsub(/[0-8]/) do |n|
x, y = n.to_i.divmod 3
board.box(x, y).content
end
end
def template
<<-STR
0 | 1 | 2
---+---+---
3 | 4 | 5
---+---+---
6 | 7 | 8
STR
end
end
BoardFormatter#template returns the string for the board. You might not have seen <<- before: it's called a heredoc and creates a multiline string between the given STR delimiters:
formatter = BoardFormatter.new
formatter.template #=> " 0 | 1 | 2\n---+---+---\n 3 | 4 | 5\n---+---+---\n 6 | 7 | 8\n"
BoardFormatter#draw uses String#gsub to replace each digit ([0-8]) with the respective box' content.
divmod is the key here, it returns both, a / b and a % b as an array. And because we're using zero-based indices, these are just the box coordinates for the nth box:
0.divmod(3) #=> [0, 0]
1.divmod(3) #=> [0, 1]
2.divmod(3) #=> [0, 2]
3.divmod(3) #=> [1, 0]
4.divmod(3) #=> [1, 1]
5.divmod(3) #=> [1, 2]
6.divmod(3) #=> [2, 0]
7.divmod(3) #=> [2, 1]
8.divmod(3) #=> [2, 2]
Complete example:
board = Board.new
board.box(0, 2).content = 'X'
board.box(0, 0).content = 'O'
board.box(2, 0).content = 'X'
board.box(1, 1).content = 'O'
formatter = BoardFormatter.new
puts formatter.draw(board)
Output:
O | _ | X
---+---+---
_ | O | _
---+---+---
X | _ | _
One-based indices
The players should probably enter one-based indices when playing your game. This conversion should be handled when processing the user input. Within your core code, you should always use zero-based indices, just like Ruby.
As other users mentioned you can create an array to hold the instances of Box.
If you are dead set on using variables you can use instance_variable_set although using an array is probably better. See here for more details.
With instance_variable_set:
class Box
attr_reader :name, :row, :column
attr_accessor :is_marked, :contents
def initialize(name, row, column, is_marked=false, contents)
#name = name
#row = row
#column = column
#is_marked = is_marked
#contents = contents
end
def display_box
print '|#{contents}|'
end
end
#generate box instances
(1..9).each do |i|
if i > 3
col = i % 3
else
col = i
end
instance_variable_set("#box#{i}", Box.new('box#{i}', (i/3).ceil, col, false, '_'))
end
board = [[#box1, #box2, #box3], [#box4, #box5, #box6], [#box7, #box8, #box9]]
def display_board
#box1.display_box; #box2.display_box; #box3.display_box; print '\n'
#box4.display_box; #box5.display_box; #box6.display_box; print '\n'
#box7.display_box; #box8.display_box; #box9.display_box; print '\n'
end
display_board
With an array:
class Box
attr_reader :name, :row, :column
attr_accessor :is_marked, :contents
def initialize(name, row, column, is_marked=false, contents)
#name = name
#row = row
#column = column
#is_marked = is_marked
#contents = contents
end
def display_box
print '|#{contents}|'
end
end
#generate box instances
arr = [];
(1..9).each do |i|
if i > 3
col = i % 3
else
col = i
end
name = 'box' + i.to_s
arr.push(Box.new(name, (i/3).ceil, col, false, '_'))
end
board = [[arr[0], arr[1], arr[2]], [arr[3], arr[4], arr[5]], [arr[6], arr[7], arr[8]]]
def display_board(arr)
arr[0].display_box; arr[1].display_box; arr[2].display_box; print '\n'
arr[3].display_box; arr[4].display_box; arr[5].display_box; print '\n'
arr[6].display_box; arr[7].display_box; arr[8].display_box; print '\n'
end
display_board(arr)
This question already has an answer here:
How to input integer value to an array, based preceeding row + column values? [duplicate]
(1 answer)
Closed 8 years ago.
For this following project, I am supposed to take input in the following format : R1C5+2 , which reads it as "in the table, Row 1 Column 5 ,add 2. Or in this format : R1C2C3-5 , which reads : "in the table, Row 1 Column 2-3, subtract 5. This is assuming that all numbers in the table are initially all 0.
Where I left Off:
I am having trouble finding a way to detect for a "+" or "-" to either add/subtract values in the table. Also, in providing a range to allow multiple additions when provided two C's or R's. For example: R1R5C2C3+2 (Row Range 1 - 5, Column Range 2 - 3, add 2).
Here is the following code:
puts 'Please input: '
x = gets.chomp
col = []
row = []
x.chars.each_slice(2) { |u| u[0] == "R" ? row << u[1] : col << u[1] }
p col
p row
puts "Largest # in Row array: #{row.max}"
puts "Largest # in Columns array: #{col.max}" #must be in "" to return value
big_row = row.max.to_i
big_col = col.max.to_i
table = Array.new (big_row) { Array.new(big_col) }
I thank you all for the help!
You've accidentally posted this twice. Here is the answer I gave on the other copy:
The method you are looking for is the =~ operator. If you use it on a string and give it a regexp pattern it will return the location of that pattern in the string. Thus:
x = 'R1C2C3-5'
x =~ /R/
returns: 0 since that is the position of 'R' in the string (counted just like an array 0,1,2...).
If you are unfamiliar with regexp and the =~ operator, I suggest you check out the Ruby doc on it, it is very valuable. Basically the pattern between the forward slashes get matched. You are looking to match + or -, but they have special meaning in regexp, so you have to escape them with a backslash.
x =~ /\+/
x =~ /\-/
but you can combine those into one pattern matcher with an OR symbol (pipe) |
x =~ /\+|\-/
So now you have a method to get the operator:
def operator(my_string)
r = my_string.slice(my_string =~ /\+|\-/)
end
I would also use the operator to split your string into the column/row part and the numeric part:
op = operator(x) # which returns op = '-'
arr = x.split(my_string(x)) # which returns an array of two strings ['R1C2C3', '5']
I leave further string manipulation up to you. I would read through this page on the String class: Ruby String Class and this on arrays: Ruby Array Class as Ruby contains so many methods to make things like this easier. One thing I've learned to do with Ruby is think "I want to do this, I wonder if there is already a built in method to do this?" and I go check the docs. Even more so with Rails!
Your homework, sir.
puts 'Please input: '
x = gets.chomp
col = []
row = []
sign = ''
val = ''
x.chars.each_slice(2) do |u|
case u[0]
when 'R' then
row << u[1]
when 'C' then
col << u[1]
when '+', '-'
sign, val = u[0], u[1]
else
puts 'Invalid input.'
exit
end
end
big_row = row.max.to_i
big_col = col.max.to_i
table = Array.new (big_row) { Array.new(big_col) }
# Initialize table to all zeros
table.map! do |row|
row.map! { |col| 0 }
end
rows_range = row.length == 1 ? row[0]..row[0] : row[0]..row[1]
cols_range = col.length == 1 ? col[0]..col[0] : col[0]..col[1]
table.each_with_index do |row, ri|
if rows_range.include? (ri + 1).to_s
row.each_with_index do |col, ci|
if cols_range.include? (ci + 1).to_s
table[ri][ci] = (sign + val).to_i
end
end
end
end
# Padding for fields in table.
padding = 4
# Table
table.each do |row|
row.each do |col|
print "#{col.to_s.rjust(padding)}"
end
print "\n"
end
I need to group numbers that are in numerical order from an array.
(using ruby 1.9.2, rails 3.2)
Example1:
[1,2,4,5,6]
Example2:
[1,3,4,6,7]
Example3:
[1,2,3,5,6]
Example4:
[1,2,4,5,7]
After grouping
Example1:
[[1,2],[4,5,6]]
Example2:
[[1],[3,4],[6,7]]
Example3:
[[1,2,3],[5,6]]
Example4:
[[1,2],[4,5],[7]]
You get the idea.
(What I'm actually doing is grouping days, not relevant though)
Thanks in advance!
I'm not sure what you'd call this operation, but it's a sort of grouping method based on the last element processed. Something like:
def groupulate(list)
list.inject([ ]) do |result, n|
if (result[-1] and result[-1][-1] == n - 1)
result[-1] << n
else
result << [ n ]
end
result
end
end
The Enumerable module provides a large number of utility methods for processing lists, but inject is the most flexible by far.
Perfect problem to use inject (aka reduce) with:
def group_consecutive(arr)
arr.inject([[]]) do |memo, num|
if memo.last.count == 0 or memo.last.last == num - 1
memo.last << num
else
memo << [ num ]
end
memo
end
end
See it run here: http://rubyfiddle.com/riddles/0d0a5
a = [1,2,4,5,7]
out = []
a.each_index do |i|
if out.last and out.last.last == a[i]-1
out.last << a[i]
else
out << [a[i]]
end
end
puts out.inspect
I have array which I read from excel (using ParseExcel) using the following code:
workbook = Spreadsheet::ParseExcel.parse("test.xls")
rows = workbook.worksheet(1).map() { |r| r }.compact
grid = rows.map() { |r| r.map() { |c| c.to_s('latin1') unless c.nil?}.compact rescue nil }
grid.sort_by { |k| k[2]}
test.xls has lots of rows and 6 columns. The code above sort by column 3.
I would like to output rows in array "grid" to many text file like this:
- After sorting, I want to print out all the rows where column 3 have the same value into one file and so on for a different file for other same value in column3.
Hope I explain this right. Thanks for any help/tips.
ps.
I search through most posting on this site but could not find any solution.
instead of using your above code, I made a test 100-row array, each row containing a 6-element array.
You pass in the array, and the column number you want matched, and this method prints into separate files rows that have the same nth element.
Since I used integers, I used the nth element of each row as the filename. You could use a counter, or the md5 of the element, or something like that, if your nth element does not make a good filename.
a = []
100.times do
b = []
6.times do
b.push rand(10)
end
a.push(b)
end
def print_files(a, column)
h = Hash.new
a.each do |element|
h[element[2]] ? (h[element[column]] = h[element[column]].push(element)) : (h[element[column]] = [element])
end
h.each do |k, v|
File.open("output/" + k.to_s, 'w') do |f|
v.each do |line|
f.puts line.join(", ")
end
end
end
end
print_files(a, 2)
Here is the same code using blocks instead of do .. end:
a = Array.new
100.times{b = Array.new;6.times{b.push rand(10)};a.push(b)}
def print_files(a, column)
h = Hash.new
a.each{|element| h[element[2]] ? (h[element[column]] = h[element[column]].push(element)) : (h[element[column]] = [element])}
h.map{|k, v| File.open("output/" + k.to_s, 'w'){|f| v.map{|line| f.puts line.join(", ")}}}
end
print_files(a, 2)