I'm trying to parse the output some JSON values in Ruby, but I keep getting an unexpected result.
articlelist = client.get('/v1/my_data/articles')
#debug
puts (articlelist.body)
#Parse the article list and get values
parsed = JSON.parse(articlelist.body)
puts parsed.count
parsed.count do
|currentfile|
inputfile = File.open ('file.example')#file.example should be replaced with local file
filehash = OpenSSL::Digest.new('sha256', 'inputfile')
puts "#{inputfile} has #{filehash.name} hash of #{filehash}"#debug
end
I get the following result:
{"count": 0, "items": []}
2
#<File:0x00000001a83b48> has SHA256 hash of 3de6c8f12dc4c9efe67c0a5bbfe21502cde5ee22e7ef0bc8d348c696db9a4363
#<File:0x00000001a83238> has SHA256 hash of 3de6c8f12dc4c9efe67c0a5bbfe21502cde5ee22e7ef0bc8d348c696db9a4363
If the value of count is zero, why is it giving me a count value of 2 (inputfile is just local example file)?
parsed is an hash with two elements, so parsed.count is 2. parsed['count'], on the other hand, is 0.
Related
This is my code, which is supposed to hash the 2 columns in fotoFd.csv and then save the hashed columns in a separate file, T4Friendship.csv:
require "csv"
arrayUser=[]
arrayUserUnique=[]
arrayFriends=[]
fileLink = "fotoFd.csv"
f = File.open(fileLink, "r")
f.each_line { |line|
row = line.split(",");
arrayUser<<row[0]
arrayFriends<<row[1]
}
arrayUserUnique = arrayUser.uniq
arrayHash = []
for i in 0..arrayUser.size-1
arrayHash<<arrayUser[i]
arrayHash<<i
end
hash = Hash[arrayHash.each_slice(2).to_a]
array1 =hash.values_at *arrayUser
array2 =hash.values_at *arrayFriends
fileLink = "T4Friendship.csv"
for i in 0..array1.size-1
logfile = File.new(fileLink,"a")
logfile.print("#{array1[i]},#{array2[i]}\n")
logfile.close
end
The first columns contains users, and the second column contains their friends. So, I want it to produce something like this in the T4Friendship.csv:
1 2
1 4
1 10
1 35
2 1
2 8
2 11
3 28
3 31
...
The problem is caused by the splat expansion of a large array. The splat * can be used to expand an array as a parameter list. The parameters are passed on the stack. If there are too many parameters, you'll exhaust stack space and get the mentioned error.
Here's a quick demo of the problem in irb that tries to splat an array of one million elements when calling puts:
irb
irb(main):001:0> a = [0] * 1000000; nil # Use nil to suppress statement output
=> nil
irb(main):002:0> puts *a
SystemStackError: stack level too deep
from /usr/lib/ruby/1.9.1/irb/workspace.rb:80
Maybe IRB bug!
irb(main):003:0>
You seem to be processing large CSV files, and so your arrayUser array is quite large. Expanding the large array with the splat causes the problem on the line:
array1 =hash.values_at *arrayUser
You can avoid the splat by calling map on arrayUser, and converting each value in a block:
array1 = arrayUser.map{ |user| hash[user] }
Suggested Code
Your code appears to map names to unique ID numbers. The output appears to be the same format as the input, except with the names translated to ID numbers. You can do this without keeping any arrays around eating up memory, and just use a single hash built up during read, and used to translate the names to numbers on the fly. The code would look like this:
def convertCsvNamesToNums(inputFileName, outputFileName)
# Create unique ID number hash
# When unknown key is lookedup, it is added with new unique ID number
# Produces a 0 based index
nameHash = Hash.new { |hash, key| hash[key] = hash.size }
# Convert input CSV with names to output CSV with ID numbers
File.open(inputFileName, "r") do |inputFile|
File.open(outputFileName, 'w') do |outputFile|
inputFile.each_line do |line|
# Parse names from input CSV
userName, friendName = line.split(",")
# Map names to unique ID numbers
userNum = nameHash[userName]
friendNum = nameHash[friendName]
# Write unique ID numbers to output CSV
outputFile.puts "#{userNum}, #{friendNum}"
end
end
end
end
convertCsvNamesToNums("fotoFd.csv", "T4Friendship.csv")
Note: This code assigns ID numbers to user and friends, as they are encountered. Your previous code assigned ID numbers to users only, and then looked up the friends after. The code I suggested will ensure friends are assigned ID numbers, even if they never appeared in the user list. The numerical ordering will different slightly from what you supplied, but I assume that is not important.
You can also shorten the body of the inner loop to:
# Parse names from input, map to ID numbers, and write to output
outputFile.puts line.split(",").map{|name| nameHash[name]}.join(',')
I thought I'd include this change separately for readability.
Updated Code
As per your request in the comments, here is code that gives priority to the user column for ID numbers. Only once the first column is completely processed will ID numbers be assigned to entries in the second column. It does this by first passing over the input once, adding the first column to the hash, and then passing over the input a second time to process it as before, using the pre-prepared hash from the first pass. New entries can still be added in the second pass in the case where the friend column contains a new entry that doesn't exist anywhere in the user column.
def convertCsvNamesToNums(inputFileName, outputFileName)
# Create unique ID number hash
# When unknown key is lookedup, it is added with new unique ID number
# Produces a 0 based index
nameHash = Hash.new { |hash, key| hash[key] = hash.size }
# Pass over the data once to give priority to user column for ID numbers
File.open(inputFileName, "r") do |inputFile|
inputFile.each_line do |line|
name, = line.split(",") # Parse name from line, ignore the rest
nameHash[name] # Add name to unique ID number hash (if it doesn't already exist)
end
end
# Convert input CSV with names to output CSV with ID numbers
File.open(inputFileName, "r") do |inputFile|
File.open(outputFileName, 'w') do |outputFile|
inputFile.each_line do |line|
# Parse names from input, map to ID numbers, and write to output
outputFile.puts line.split(",").map{|name| nameHash[name]}.join(',')
end
end
end
end
convertCsvNamesToNums("fotoFd.csv", "T4Friendship.csv")
Below is my test.cgi file. The params coming in from the html are:
{"FirstNum"=>["3"], "SecondNum"=>["3"]}
Why isn't the a.class showing up as an integer? I need to do math with the values, and the form sent them as an integers. Even the Ruby .to_i doesn't work.
#!/RailsInstaller/Ruby2.1.0/bin/ruby
require 'cgi'
cgi = CGI.new
puts "Content-type: text/html\n\n"
arr = cgi.params
a = arr['FirstNum']
a.each do |a|
puts a.class
end
From the fine manual:
The method params() returns a hash of all parameters in the request as name/value-list pairs, where the value-list is an Array of one or more values. The CGI object itself also behaves as a hash of parameter names to values, but only returns a single value (as a String) for each parameter name.
So when you say:
arr = cgi.params
you get a Hash in arr whose keys are the CGI parameter names and whose values are arrays of strings; arr is a bad name here because it is a Hash, not an Array. That means that this:
a = arr['FirstNum']
gives you an array of strings in a. If you only want one value then use the [] method on cgi to get a string and then String#to_i to get a number:
a = cgi['FirstNum'].to_i
I am having trouble in comparing values in two hashes, getting the error "Can't convert String into Integer".
First hash has values captured from a web page using the method "capture_page_data(browser)" and the second hash has data parsed from a report.
Code looks like below:
# Open the web application
# Navigate to a specific page and capture page data
loan_data = Hash.new
loan_data = capture_page_data(browser)
Second hash has values captured from a report generated from the web application.
Code looks like below:
#report_data[page] = Hash.new
# we have written some logic to parse the data from the report into hash variable
Now I am trying to compare the values in theses two hashes to ensure the data in report is matching with the data in application using below code which is giving me the error "Can't convert String into Integer".
loan_data.map{|ld| ld['MainContent_cphContent_LoanOverViewGeneralInfoCtrl_lblRelName']} &
#report_data.map{|rd| rd['Relationship']}
Please help me out in resolving this issue.
Regards,
Veera.
Hash#map iterates through the hash like it was an array of key/value pairs.
{a:1,b:2}.map{|x| puts x.inspect }
# prints
# [:a,1]
# [:b,2]
{a:1,b:2}.map{|k,v| puts "#{k} => #{v}" }
# prints
# a => 1
# b => 2
It applies the block you provide to each pair and collects the results into a new array.
result = {a:1,b:2}.map{|k,v| "#{k} => #{v}" }
puts result.inspect
# prints
# [ "a => 1", "b => 2" ]
I would guess what you are trying to do is compare a single key from each array... in which case...
if loan_data[:id][:span]['MainContent_cphContent_LoanOverViewGeneralInfoCtrl_lblRelName'] == #report_data[1]['Relationship']
log_message("pass")
else
log_message("fail")
end
might be what you are trying to do.. but I am only guessing.
It all depends on the shape of your data.
If you inspect the ld variable inside your block, you will find that it is an array. You can get an element of it with ld[0] or ld[1], but ld[string] does not make sense and results in the exception you are seeing. The ld array will actually be an array with two elements: key and value.
Thanks for your suggestions.. but I found a different solution to compare a single key from two hashes/Arrays using the below code which worked fine.
string_equals?(loan_data[:id][:span]['MainContent_cphContent_LoanOverViewGeneralInfoCtrl_lblRelName'], #report_data[1]['Relationship'] )
Thanks,
Veera.
It's best to debug the content of loan_data and #report_data directly, but you can try .to_sym to convert the key into symbol.
loan_data.map{|ld| ld['MainContent_cphContent_LoanOverViewGeneralInfoCtrl_lblRelName'.to_sym]} &
#report_data.map{|rd| rd['Relationship'.to_sym]}
I'm fairly new to ruby. I have the following csv:
Office (1), Test
Office (Test)(2), Test
In "data.csv".
Then in my ruby script I have;
CSV.foreach("data.csv") do |line|
registeredOffice = line[0].to_s()
macOffice = registeredOffice.scan(/\(([^\)]+)\)/).last
csvText = "#{csvText}\n#{macOffice}"
end
Which gives me
["1"]
["2"]
However I want to know how to convert the above to a string so the output is
1
2
Using .join or [0] returns a nil:NilClass (NoMethodError)
You probably want something like:
macOffice = registeredOffice[/(\d+)\)$/, 1]
scan with capture groups will give you multi-d arrays
The following line:
macOffice = registeredOffice.scan(/\(([^\)]+)\)/).last
returns array since scan returns array of array. For the first line of data.csv, it is ["1"].
I guess you need scalar value for macOffice, thus, you want to use match which only returns non-repetive match using match, which returns array of matches once. For example, you can grab first match from the returned array using [1] subscript, thus:
macOffice = registeredOffice.match(/\(([^\)]+)\)/)[1]
which returns 1.
Assuming you want an array you can write like this:
out = []
CSV.foreach("data.csv") do |line|
registeredOffice = line[0].to_s()
macOffice = registeredOffice.match(/\((\d+)\)/)[1]
out.push(macOffice)
end
puts out.join(",")
to produce 1,2
I tried this in irb:
x = 123456
Then I wanted to get a specific position of the number like:
puts x[2]
it returns 0
why is that?
The only (sensible) way to do this is to first convert it to a string then use the [] method:
x_str = x.to_s
puts x_str[0..2] #prints "12"
If you want to retrieve the position of a string within another string, use the index method
puts x_str.index('2') #prints 1
Fixnum does supply a [] method, but it's obviously not what you want.
In your code, it's returning 0 because that is the 3rd (zero-indexed) bit in the binary representation of 123456.