How to use a Hash before defining it in Ruby - ruby

I have a Ruby script with a very long Hash with more than 300 associations.
The script looks like this:
#!/usr/bin/env ruby
Array_A = []
myHash = {
"x1" => "2",
"x2" => "0",
"x3" => "1",
.
.
.
"X350" => "1"
}
myHash.keys.each do |z|
Array_A << "This is key " + z
end
puts myHash.values.join("|")
puts Array_A.join("|")
But since the Hash is very large, for reading purposes I'd like to put the Hash at the end of the script and the each loop and puts command first, something like this:
Array_A = []
myHash.keys.each do |z|
Array_A << "This is key " + z
end
puts myHash.values.join("|")
puts Array_A.join("|")
myHash = {
"x1" => "2",
"x2" => "0",
"x3" => "1",
.
.
.
"X350" => "1"
}
Is there a way to do this?

It's a little bit weird, but this is basically what DATA is for. The catch is that it's a file containing the contents of the section after __END__, so you'll need to go from that to a hash. So something like:
Array_A = []
myHash = eval DATA.read
myHash.keys.each do |z|
Array_A << "This is key " + z
end
puts myHash.values.join("|")
puts Array_A.join("|")
__END__
{
"x1" => "2",
"x2" => "0",
"x3" => "1",
…
"X350" => "1"
}

Related

how do I map one csv to another with ruby

I have two csv's with different headers.
lets say csv 1 has headers one, two, three, four and I want to create a csv with headers five, six, seven, eight.
I'm having a hard time writing the code to open the first CSV and then creating the second CSV.
Here is the current code that I have.
require 'csv'
wmj_headers = [
"Project Number",
"Task ID",
"Task Name",
"Status Comment",
"Act Complete",
"Plan Complete",
"Description"]
jir_headers_hash = {
"Summary" => "Task Name",
"Issue key" => "Status Comment",
"Resolved" => "Act Complete",
"Due date" => "Plan Complete",
"Description" => "Description"
}
puts "Enter path to a directory of .csv files"
dir_path = gets.chomp
csv_file_names = Dir["#{dir_path}*.csv"]
csv_file_names.each do |f_path|
base_name = File.basename(f_path, '.csv')
wmj_name = "#{base_name}_wmj.csv"
arr = []
mycount = 0
CSV.open(wmj_name, "wb") do |row|
row << wmj_headers
CSV.foreach(f_path, :headers => true) do |r|
r.headers.each do |value|
if jir_headers_hash[value].nil? == false
arr << r[value]
end
end
end
row << arr
end
end
People tend to overcomplicate things. You don’t need any CSV processing at all to substitute headers.
$ cat /tmp/src.csv
one,two,three
1,2,3
4,5,6
Let’s substitute the headers and stream everything else untouched.
subst = {"one" => "ONE", "two" => "TWO", "three" => "THREE"}
src, dest = %w[/tmp/src.csv /tmp/dest.csv].map { |f| File.new f, "a+" }
headers = src.readline() # read just headers
dest.write(headers.gsub(/\b(#{Regexp.union(subst.keys)})\b/, )) # write headers
IO.copy_stream(src, dest, -1, headers.length) # stream the rest
[src, dest].each(&:close)
Check it:
$ cat /tmp/dest.csv
ONE,TWO,THREE
1,2,3
4,5,6
If you want to substitute CSV column names, here it is:
require 'csv'
# [["one", "two", "three"], ["1", "2", "3"], ["4", "5", "6"]]
csv = CSV.read('data.csv')
# new keys
ks = ['k1', 'k2', 'k3']
# [["k1", "k2", "k3"], ["1", "2", "3"], ["4", "5", "6"]]
k = csv.transpose.each_with_index.map do |x,i|
x[0] = ks[i]
x
end.transpose
# write new file
CSV.open("myfile.csv", "w") do |csv|
k.each do |row|
csv << row
end
end

Count the occurence of values in Ruby

I'm trying to count numbers of different values in mysql table columns. Range of possible values is integer and from 0-10. The following code is working, but I'm wondering if there is a more elegant way to do this?
# Example data from Mysql
result = [{ :column1 => "2", :column2 => "3", :column3 => "1"},{ :column1 => "2", :column2 => "3", :column3 => "1"},{ :column1 => "1", :column2 => "2", :column3 => "3"}]
# Init hash
final_result = Hash.new { |h, k| h[k] = { } }
# Loop result columns
result.each do |single_column|
# Loop single items inside columns
single_column.each do |single_result|
# Create column if does not exist
if final_result[single_result[0]][single_result[1]].nil? then
final_result[single_result[0]][single_result[1]] = 1
else
final_result[single_result[0]][single_result[1]] += 1
end
end
end
puts final_result
# => {:column1=>{"2"=>2, "1"=>1}, :column2=>{"3"=>2, "2"=>1}, :column3=>{"1"=>2, "3"=>1}}
There's some room for cleaning up here. The most obvious part is that long, clunky if statement. The test vs nil? is pointless, remember in Ruby the only things that are logically false are false and nil, so since false is never going to show up here, the test vs. nil specifically can be removed.
More than that, though, you're on the right track with the custom Hash.new call, but you don't go far enough. Why not initialize the second tier with zero?
That results in code that looks like:
result = [
{ :column1 => "2", :column2 => "3", :column3 => "1"},
{ :column1 => "2", :column2 => "3", :column3 => "1"},
{ :column1 => "1", :column2 => "2", :column3 => "3"}
]
# Init hash
final_result = Hash.new { |h, k| h[k] = Hash.new(0) }
# Loop result columns
result.each do |single_column|
single_column.each do |r|
final_result[r[0]][r[1]] += 1
end
end
puts final_result.inspect
Have a look at the active record count method (doc link). You can use that in combination with group to do what you are trying to achieve.
[:column1, :column2, :column3].inject({}) do |hash, column|
hash[column] = Model.group(column).count
hash
end

method similar to array#pop for hash

I have a hash of people keyed by job and sorted by salary:
person = Struct.new(:salary)
people = {
:butchers => [
person.new(10),
person.new(6),
person.new(4)
],
:bakers => [
person.new(16),
person.new(8),
person.new(7)
],
:candlestick_makers => [
person.new(25),
person.new(21),
person.new(18)
]
}
I want to remove the last x people of each job from their respective array and do something:
def this_example_method
people.each do |job, people|
people.pop(number_for(job)).each do |person|
#do something
end
end
end
the 'do something' works okay, but pop removal doesn't. After running this_example_method, the people hash should look this, but at the moment it's not changing:
people = {
butchers = [
<butcher_1 salary:10>
<butcher_2 salary:6>
],
bakers = [
<baker_1 salary:16>
<baker_2 salary:8>
],
candlestick_makers = [
<candlestick_maker_1 salary:25>
<candlestick_maker_2 salary:21>
]
}
Hash has a shift method that returns the first item and removes it from the hash. If the order matters you could perhaps try to sort it reversed when the hash is created.
Just do as below :
def this_example_method
people.each do |job, persons|
persons.tap { |ob| ob.pop(x) }.each do |person|
#do something
end
end
end
Example :
hash = { :a => [1,2,3], :b => [3,5,7] }
hash.each do |k,v|
v.tap(&:pop).each { |i| # working wit i }
end
hash # => {:a=>[1, 2], :b=>[3, 5]}

Break an Each statement once an inside IF statement is run once in Ruby

In the following code how do I break the each statement once the if code successfully changes the very first 1 it comes across to 2.
hash = {:key1 => "1", :key2 => "2", :key3 => "1", :key4 => "3" :key5 => "3"}
array = [:key1,:key2,:key3,:key4,:key5]
array.each do |x|
if hash[x] == "1"
hash[x] = 2
end
Something like below you want
array = [1,2,2,1,2]
array.each do |x|
break x = 2 if x == 1
end

Ruby seem to reference a variable instead of coping it's value

Why does the value in my data_dummy hash increase? I’d like to use it to initiate another hash with zero values!
fau[f.label][:hash] = data_dummy # ==>{"name 1" => 0, "name 2" => 0} but in the second loop it contains data from the first loop e.g. {"name 1" => 2, "name 2" => 0}
When using the string instead of variable dummy_data the code works as expected.
fau[f.label][:hash] = {"name 1" => 0, "name 2" => 0}
I can't do that because 'name X' is changing....
That's strange to me!
complete code
fau = {}
series = []
labels = [{:value => 0, :text => ''}]
data_dummy = {}
source.each do |c|
data_dummy[c.name] = 0
end
i = 0
data_dummy.each do |k,v|
i += 1
labels.push({:value => i, :text => k})
end
source.each do |s|
logger.debug "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
logger.debug "Source: '#{s.name}'|'#{fault_labels[s.fault_id.to_s].label}' => #{s.cnt}"
con_name = s.name #TODO: Cut name (remove location like left,right, ...)
f = fault_labels[s.fault_id.to_s]
unless fau.has_key?(f.label)
# init faults-hash 'fau'
fau[f.label] = {:total => 0, :hash => {}, :color => f.color, :name => f.label} #, :data => []
# add all connector_names as keys with value = 0
logger.debug "init :hash with #{data_dummy}" # ==>{"name 1" => 0, "name 2" => 0} but in the second loop it contains data from the first loop e.g. {"name 1" => 2, "name 2" => 0}
fau[f.label][:hash] = data_dummy
# this way the number of incidents are all in the same order for each fault (first dimension key)
# and all get at least a value of 0
end
logger.debug "Count up fau['#{f.label}'][:total] = #{fau[f.label][:total]} + #{s.cnt} (where connector '#{s.name}' and fault '#{f.label}')"
logger.debug "Count up fau['#{f.label}'][:hash]['#{con_name}'] = #{fau[f.label][:hash][con_name]} + #{s.cnt}"
fau[f.label][:total] += s.cnt
fau[f.label][:hash][con_name] += s.cnt
logger.debug "result :hash with #{fau[f.label][:hash].inspect}}"
end
Because Ruby hashes, like all Ruby objects, are references and copying one, such as hash2 = hash1 only creates a copy of the reference. Modifying hash2 will modify hash1, as really, they are just different aliases for the same thing.
You want to use the clone method instead.
hash2 = hash1.clone
See also How do I copy a hash in Ruby?
Note that even this only creates a shallow copy, if you have a nested hash (such as myhash = {"key1" => "value1", "key2" => {"key2a" => "value2a"}}), you will have to make a deep copy. According to Wayne Conrad's answer to the question above, the way to do that is this:
def deep_copy(o)
Marshal.load(Marshal.dump(o))
end
If you want to make a copy of the hash, you need to use the dup method:
foo = {"name 1" => 0, "name 2" => 0}
bar = foo
foo["name 2"] += 1
foo
=> {"name 2"=>1, "name 1"=>0}
bar
=> {"name 2"=>1, "name 1"=>0}
baz = foo.dup
foo["name 2"] += 1
foo
=> {"name 2"=>2, "name 1"=>0}
baz
=> {"name 2"=>1, "name 1"=>0}

Resources