output to csv in a similar manner 'print' outputs to terminal? - ruby

the logic included in my ruby code is a little difficult to translate into a csv output, is there a way to append each value in a similar manner that print works when outputting to the terminal?
cop_log is a hash of thousands of records
cop_log = { 'A1' => 'val1', 'B1' => 'val2', 'C1' => 'val3', 'D1' => 'val4, 'E1' => 'val5', 'F1' => 'val6', 'G1' => 'val7', 'H1' => 'val8', 'I1' => 'val9', 'J1' => 'val10, 'K1' => 'val11', 'A2' => 'val12', 'B2' => 'val13', 'C2' => 'val14', 'D2' => 'val15, 'E2' => 'val16', 'F2' => 'val17', 'G2' => 'val18', 'H2' => 'val19', 'I2' => 'val20', 'J2' => 'val21, 'K2' => 'val22'}
cop_log.each do |key, value|
if key.include?('K')
print "#{value}\n"
elsif key.include?('A')
print "#{job_number},#{pm_lookup[job_number]},#{value},"
elsif key.include?('B') || key.include?('C') || key.include?('D') ||
key.include?('E') || key.include?('F') || key.include?('G') ||
key.include?('H') || key.include?('I') || key.include?('J')
print "#{value},"
end
end
currently outputs in the terminal like this, i want it to print to a csv in the same way:
val1, val2, val3, val4, val5, val6, val7, val8, val9, val10, val11
val12, val13, val14, val15, val16, val17, val18, val19, val10, val21, val22
reading the documentation it looks like the common route is to do the following,
CSV.open("path/to/file.csv", "wb") do |csv|
csv << ["row", "of", "CSV", "data"]
csv << ["another", "row"]
# ...
end
unfortunately the structure of my code is going to make that a little difficult....

This is very confusing and, if cleaned up, would help make your code much more readable and maintainable:
if key.include?('K')
print "#{value}\n"
elsif key.include?('A')
print "#{job_number},#{pm_lookup[job_number]},#{value},"
elsif key.include?('B') || key.include?('C') || key.include?('D') ||
key.include?('E') || key.include?('F') || key.include?('G') ||
key.include?('H') || key.include?('I') || key.include?('J')
print "#{value},"
end
What is key? A string of characters or a single character? If you're matching single characters you could do something like:
if key == 'K'
print "#{value}\n"
elsif key == 'A'
print "#{job_number},#{pm_lookup[job_number]},#{value},"
elsif ('B'.. 'J').include?(key)
elsif
print "#{value},"
end
and even convert:
elsif ('B'.. 'J').include?(key)
to:
elsif ('B'..'J') === key
If you're searching for a character match inside strings:
if key['K']
print "#{value}\n"
elsif key['A']
print "#{job_number},#{pm_lookup[job_number]},#{value},"
elsif key[/[B-J]/]
print "#{value},"
end
At this point you're ready to tackle cleaning up the CSV output. I'd recommend starting over using one of the basic examples of CSV output in the class documentation. I'd also recommend carefully reading "Comma-separated values" so you're more familiar with the expectations of a CSV formatted file.
(Note: At this point the input sample hash was added to the question showing the actual format of the keys.)
I'll let you figure out how to incorporate CSV, but meditate on this:
cop_log = { 'A1' => 'val1', 'B1' => 'val2', 'C1' => 'val3', 'D1' => 'val4', 'E1' => 'val5', 'F1' => 'val6', 'G1' => 'val7', 'H1' => 'val8', 'I1' => 'val9', 'J1' => 'val10', 'K1' => 'val11', 'A2' => 'val12', 'B2' => 'val13', 'C2' => 'val14', 'D2' => 'val15', 'E2' => 'val16', 'F2' => 'val17', 'G2' => 'val18', 'H2' => 'val19', 'I2' => 'val20', 'J2' => 'val21', 'K2' => 'val22'}
cop_log.values.each_slice(11) do |slice|
puts slice.join(',')
end
# >> val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11
# >> val12,val13,val14,val15,val16,val17,val18,val19,val20,val21,val22
You need to rely on the CSV class to create CSV files; The format isn't as simple as it seems.
Here's a first pass of how I'd write the code:
require 'csv'
cop_log = { 'A1' => 'val1', 'B1' => 'val2', 'C1' => 'val3', 'D1' => 'val4', 'E1' => 'val5', 'F1' => 'val6', 'G1' => 'val7', 'H1' => 'val8', 'I1' => 'val9', 'J1' => 'val10', 'K1' => 'val11', 'A2' => 'val12', 'B2' => 'val13', 'C2' => 'val14', 'D2' => 'val15', 'E2' => 'val16', 'F2' => 'val17', 'G2' => 'val18', 'H2' => 'val19', 'I2' => 'val20', 'J2' => 'val21', 'K2' => 'val22'}
job_number = 1
pm_lookup = [0, 1]
CSV.open('./foo.csv', 'w') do |csv|
cop_log.group_by{ |k, v| k[1] }.values.each do |row|
csv << [ job_number, pm_lookup[job_number], *row.map{ |k, v| v } ]
end
end
After running, foo.csv contains:
1,1,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11
1,1,val12,val13,val14,val15,val16,val17,val18,val19,val20,val21,val22
We don't know what your expected output is, since you didn't tell us, but you could work from that code and figure it out.
Here's a break-down of the preprocessing of the data:
cop_log.group_by{ |k, v| k[1] } # => {"1"=>[["A1", "val1"], ["B1", "val2"], ["C1", "val3"], ["D1", "val4"], ["E1", "val5"], ["F1", "val6"], ["G1", "val7"], ["H1", "val8"], ["I1", "val9"], ["J1", "val10"], ["K1", "val11"]], "2"=>[["A2", "val12"], ["B2", "val13"], ["C2", "val14"], ["D2", "val15"], ["E2", "val16"], ["F2", "val17"], ["G2", "val18"], ["H2", "val19"], ["I2", "val20"], ["J2", "val21"], ["K2", "val22"]]}
.values # => [[["A1", "val1"], ["B1", "val2"], ["C1", "val3"], ["D1", "val4"], ["E1", "val5"], ["F1", "val6"], ["G1", "val7"], ["H1", "val8"], ["I1", "val9"], ["J1", "val10"], ["K1", "val11"]], [["A2", "val12"], ["B2", "val13"], ["C2", "val14"], ["D2", "val15"], ["E2", "val16"], ["F2", "val17"], ["G2", "val18"], ["H2", "val19"], ["I2", "val20"], ["J2", "val21"], ["K2", "val22"]]]
In the code above I'm making sure the input is in an expected order, in this case I'm grouping by single-digit rows. More robust code would use \d+ instead of 1 and would also sort to force the appropriate order.
This is really important any time you're massaging data from one format into another for later reuse. While current Rubies guarantee that a hash will maintain its insertion order, it's not a good practice to assume since old versions of Ruby didn't do that, and other languages you port the code to might not maintain order. Instead always program defensively, making sure you're going to return consistent results. To what extent you do that is something you'll learn when you find out a previous attempt wasn't enough. Programming is fun that way.
Finally, any time your code feels awkward or isn't flowing it's time to back up and look at what you're doing.

Couldn't you do this?
CSV.open("path/to/file.csv", "wb") do |csv|
cop_log.each do |key, value|
if key.include?('A')
csv << [ job_number, pm_lookup[job_number], value ]
elsif key.include?('K') || key.include?('B') || key.include?('C') || key.include?('D') ||
key.include?('E') || key.include?('F') || key.include?('G') ||
key.include?('H') || key.include?('I') || key.include?('J')
csv << [ value ]
end
end
end

def write_csv(cop_log, io = $stdout)
cop_log.each do |key, value|
if key.include?('K')
io.print "#{value}\n"
elsif key.include?('A')
io.print "#{job_number},#{pm_lookup[job_number]},#{value},"
elsif key.include?('B') || key.include?('C') || key.include?('D') ||
key.include?('E') || key.include?('F') || key.include?('G') ||
key.include?('H') || key.include?('I') || key.include?('J')
io.print "#{value},"
end
end
end
Then
File.open('path/to/file.csv', 'w') do |f|
write_csv(cop_log, f)
end

re fector the code
CSV.open("test.csv", "ab") do |csv|
b = {}
check_num = 0
cop_log.each do |key, value|
num = key.gsub(/[^\d]/, '').to_i
next if num < 8 ||
key.include?('L') || key.include?('M') || key.include?('N') ||
key.include?('O') || key.include?('P') || key.include?('Q') ||
key.include?('R') || key.include?('S') || key.include?('T') ||
key.include?('U') || key.include?('V')
a = { key => value }
b.merge!(a)
end # end of each loop
i = 8
while ((b.length / 9) - 7) > i do
csv << [ job_number, pm_lookup[job_number], b["A#{i}"], b["B#{i}"],
b["C#{i}"], b["D#{i}"], b["E#{i}"], b["F#{i}"], b["G#{i}"],
b["H#{i}"], b["I#{i}"], b["J#{i}"], b["K#{i}"] ]
i += 1
end
end # end of CSV.open

Related

Ruby Program not performing as expected? If block executes despite failing the condition

The Following program is intended to iterate over the holiday_hash, capitalise all keys and values and puts them out:
def all_supplies_in_holidays(holiday_hash)
holiday_hash.each do |key, value|
key_string = key.to_s
key_string.capitalize!
puts "#{key_string}:"
value.each do |key2, value2|
if key2 == :new_year || :fourth_of_july || :memorial_day
key_to_string = key2.to_s
key_string1 = key_to_string.split
final_array = []
key_string1.each do |splitted_string|
final_array = splitted_string.capitalize!
end
final_string = final_array.join(" ")
print "#{final_string1}:"
else
key_string1 = key2.to_s
print "#{key_string1}"
end
value2.each do |array_value|
new_array_capitalized = []
new_array_capitalized << array_value.capitalize!
new_array.join(" ")
end
end
end
end
The expected output format is:
Winter:
Christmas: Lights, Wreath
New Years: Party Hats
Summer:
Fourth of July: Fireworks, BBQ
Fall:
Thanksgiving: Turkey
Spring:
Memorial Day: BBQ
The holiday_hash is as follows:
{
:winter => {
:christmas => ["Lights", "Wreath"],
:new_years => ["Party Hats"]
},
:summer => {
:fourth_of_july => ["Fireworks", "BBQ"]
},
:fall => {
:thanksgiving => ["Turkey"]
},
:spring => {
:memorial_day => ["BBQ"]
}
}
The problem is:
The if block executes even if the conditions fails to evaluate to true.
Suggestions needed:
How can I simplify the process of capitalising symbols who have two words in them like :new_york to be capitalised to New York? (As the #capitalised method will return New york)
The if block executes even if the conditions fails to evaluate to true
Let's see:
key2 = :new_year
key2 == :new_year || :fourth_of_july || :memorial_day
#=> true
and now a key that is supposed to return false:
key2 = :thanksgiving
key2 == :new_year || :fourth_of_july || :memorial_day
#=> :fourth_of_july
That's not your expected result, but your assumption isn't correct either: the if block is executed simply because the condition is always truthy.
Why? Because it's equivalent to:
false || :fourth_of_july || :memorial_day
#=> :fourth_of_july
You want:
key2 == :new_year || key2 == :fourth_of_july || key2 == :memorial_day
#=> false
Or a little shorter:
[:new_year, :fourth_of_july, :memorial_day].include? key2
#=> false
How can I simplify the process of capitalising symbols who have two words in them [...]
I'd split them by underscore, capitalize each word and join the result:
:new_year
.to_s #=> "new_year"
.split('_') #=> ["new", "year"]
.map(&:capitalize) #=> ["New", "Year"]
.join(' ') #=> "New Year"
I've separated the method calls to show the intermediate results. You can write the above in one line:
:new_year.to_s.split('_').map(&:capitalize).join(' ')
#=> "New Year"

Ruby - How do I shorten my method

I have a hash here:
VALID_CHOICES = {
'r' => 'rock',
'p' => 'paper',
'sc' => 'scissors',
'l' => 'lizard',
'sp' => 'spock'
}
And a method which basically compares here:
def win?(first, second)
(first == 'sc' && second == 'p') ||
(first == 'p' && second == 'r') ||
(first == 'r' && second == 'l') ||
(first == 'l' && second == 'sp') ||
(first == 'sp' && second == 'sc') ||
(first == 'sc' && second == 'l') ||
(first == 'l' && second == 'p') ||
(first == 'p' && second == 'sp') ||
(first == 'sp' && second == 'r') ||
(first == 'r' && second == 'sc')
end
How can I rewrite my method in very short concise code that means exactly the same thing? Any idea? Is it possible to do it using hashes?
You should define clear rules for what each token can win:
WINS = {
'r' => %w{l sc},
'p' => %w{r sp},
'sc' => %w{p l},
'l' => %w{p sp},
'sp' => %w{r sc}
}
Now you can determine wins using a simple lookup:
def win?(first, second)
WINS[first].include?(second)
end
While there may be several 'clever' ways to avoid an explicit structure like WINS, explicit rules are much more understandable - and therefore, more maintainable. Conciseness in code is considered a positive attribute where it improves the readability of the code. Conciseness to the extreme that causes the code to be difficult to understand is not something to strive for.
In addition to user2864740's comment and Cary Swoveland's explanation, you could also use a hash to map "winning pairs" to their respective verb:
WINS = {
%w[scissors paper] => 'cuts',
%w[paper rock] => 'covers',
%w[rock lizard] => 'crushes',
%w[lizard spock] => 'poisons',
%w[spock scissors] => 'smashes',
%w[scissors lizard] => 'decapitates',
%w[lizard paper] => 'eats',
%w[paper spock] => 'disproves',
%w[spock rock] => 'vaporizes',
%w[rock scissors] => 'crushes'
}
It returns the corresponding verb if the key's first item beats the second:
WINS[['paper', 'rock']] #=> "covers"
and nil if it doesn't:
WINS[['rock', 'paper']] #=> nil
In your method:
def win?(first, second)
WINS.has_key?([first, second])
end
Or to check both sides:
if WINS.has_key?([first, second])
# first wins
elsif WINS.has_key?([second, first])
# second wins
else
# tie
end
Or more verbose:
def result(first, second)
if verb = WINS[[first, second]]
"first wins: #{first} #{verb} #{second}"
elsif verb = WINS[[second, first]]
"second wins: #{second} #{verb} #{first}"
else
"tie"
end
end
result('rock', 'scissors')
#=> "first wins: rock crushes scissors"
result('spock', 'lizard')
#=> "second wins: lizard poisons spock"
result('paper', 'paper')
#=> "tie"
Of course, you can also use your abbreviations (sc, p, r, l, sp) instead of whole words.

Set the value as a range of numbers in Ruby

My question is whether I can use a range as the value in a key:value pair in a hash. I am working on a problem where I am trying to return a letter grade (A-F) for an average of numerical grades (array of numbers). I have a working solution, but I came across something intriguing. Here is my code:
def get_grade(array)
avg = (array.inject {|num, x| num + x}) / array.length
grades = {
"A" => [90..10]
"B" => [80..89],
"C" => [70..79],
"D" => [60..69],
"F" => [0..59],
}
grades.default = "Error"
puts grades.key(avg)
end
arraya = [100,90,100,99,99]
puts get_grade(arraya)
I know I could return the letter grade with either a case or an if statement. It seems like I should be able to use a hash instead but it doesn't work. Why can't I set up a hash with a range as value? Thanks.
You could use a case statement:
def get_grade(scores)
case scores.inject(&:+) / scores.length
when 90..100; 'A'
when 80..89; 'B'
when 70..79; 'C'
when 60..69; 'D'
when 0..59; 'F'
else; 'Error'
end
end
arraya = [100,90,100,99,99]
puts get_grade(arraya)
#=> A
You may want to rewrite your method as the following:
def get_grade(array)
avg = array.inject(:+) / array.length
grades = {
"A" => (90..100),
"B" => (80..89),
"C" => (70..79),
"D" => (60..69),
"F" => (0..59),
}
grade = grades.find{|key, range| range.include?(avg) }
grade.nil? ? "Unknown" : grade.first
end
arraya = [100,90,100,99,99]
puts get_grade(arraya) # => A

Ruby "storing" format string

How can I store a format string like this
s = "test with #{value}"
so that later on I can do this
puts s % {:value => 'hello'}
If I write the first thing, it complains that value is not found (true, I want to provide it later). If I use the raw string s = 'test with #{value}' it is not interpolated.
I specifically tried this:
#format_html = "%{who} receives %{got[1]} from %{from} and sends %{given[1]} to %{to}"
puts #format_html % {:who => 'who',
:given => 'given',
:from => 'from',
:got => 'got',
:to => 'to'}
and I get this:
KeyError (key{who.sub ' ', '+'} not found):
This works only with ruby 1.9+:
s = "test with %{value}"
puts s % { value: 'hello' } # => test with hello
The pickaxe http://pragprog.com/book/ruby3/programming-ruby-1-9 says under String#%:
If the format specification contains more than one substitution, then arg must be an Array containing the values to be substituted.
#format_html = "%s receives %s from %s and sends %s to %s"
h = {:who => 'who',
:given => ['given1', 'given2'],
:from => 'from ',
:got => ['got1', 'got2'],
:to => 'to '}
who, given, from, got, to = h.values
who_plus = who.gsub(' ', '+')
got0 = got[0]
got1 = got[1]
from_plus = from.gsub(' ', '+')
given0 = given[0]
given1 = given[1]
to_plus = to.gsub(' ', '+')
puts #format_html % [who_plus, who, got0, got1, from_plus, from, given0, given1, to_plus, to]
Execution :
$ ruby -w t.rb
who receives got2 from from and sends given2 to to

gsub - how to be efficient leet generator

for fun I am creating in ruby a simple leet (1337) generator
so i am doing something like this, which works but doesn't look very efficient, i am sure it can be accomplished with one line only...
def leet
words = words.gsub(/a/, '4')
words = words.gsub(/e/, '3')
words = words.gsub(/i/, '1')
words = words.gsub(/o/, '0')
words = words.gsub(/s/, '5')
words = words.gsub(/t/, '7')
puts words
end
Can you give me a help here? :) thanks!
def leet(word)
puts word.gsub(/[aeiost]/,'a'=>'4','e'=>'3','i'=>'1','o'=>'0','s'=>'5','t'=>'7')
end
def leet s
s.tr 'aeiost', '431057'
end
A more general version of megas's:
class Leet
##map = {
'a' => '4',
'e' => '3',
'i' => '1',
'o' => '0',
's' => '5',
't' => '7'
}
##re = Regexp.union(##map.keys)
def self.speak(str)
str.gsub(##re, ##map)
end
end
puts Leet.speak('leet')
# l337
Adjust ##map as needed and away you go.

Resources