[Ruby-tut] Practical example: File I/O (and odds and ends)
Author |
Message |
wtd
|
Posted: Fri Jul 16, 2004 10:08 pm Post subject: [Ruby-tut] Practical example: File I/O (and odds and ends) |
|
|
File I/O seems to be a popular topic in other language forums here, so I thought I'd write up a quick tutorial on doing it in Ruby.
The first step will be getting a File object for the file we want to read. There are a few possible methods of doing this. Firstly, we can create a File object in the same way any other object is created, by using the File class' new method.
For these examples, we'll use the input file "infile.txt" and the output file "outfile.dat".
code: | my_file = File.new("infile.txt", "r") |
The "r" indicates the mode the file is being opened in. In this case, read-only. Other possibilities are "w", "rw", and "b" indicates binary. Binary cannot be used with "rw".
Alternatively, the File class has an "open" class method.
code: | my_file = File.open("infile.txt", "r") |
But this all very boring, so let's approach this with a problem in mind. We have a file that looks like:
code: | Chris | aa | com | 3 | 9
Bob | bb | net | 1 | 16
Joe | ccccc | edu | 5 | 27
Chris | wooble | org | 6 | 3 |
The first column very simply contains a customer's name. The second is the domain you're hosting for them. The third is the top level domain tha domain is in. The fourth is the number of years they're signed for, and the fifth is how much they pay per year.
The output should look like:
code: | Chris owes us {some amount} over the next {so many} years for the domains {such and such}. |
Step 1: Open the input file.
code: | input_file = File.open("customer_data.txt", "r") |
Step 2: Get the contents of the file, then close the input file.
code: | contents = input_file.readlines
input_file.close |
Step 3: Split each line on the delimiter of "|", into at most 5 columns.
code: | contents.collect! { |line| line.split(/\s*\|\s*/, 5) } |
Step 4: Create an associative array (Hash) which will hold info about each customer.
Step 5: Iterate through the contents of the file. For each user, if they're already in the customers hash, add the domain and add its cost to their total, as well as the number of years they're signed for.
code: | contents.each do |line|
customer_name = line[0]
customer_domain = "#{line[1]}.#{line[2]}"
customer_rate = line[3].to_i
customer_duration = line[4].to_i
customer_cost = customer_rate * customer_duration
unless customers.has_key? customer_name
customers[customer_name] = {"domains" => [], "cost" => 0, "duration" => 0}
end
customers[customer_name]["domains"] << customer_domain
customers[customer_name]["cost"] += customer_cost
customers[customer_name]["duration"] = [customer_duration, customers[customer_name]["duration"]].max
end |
This can be simplified by using the ||= operator which only assigns if the left hand side is nil.
code: | contents.each do |line|
customer_name = line[0]
customer_domain = "#{line[1]}.#{line[2]}"
customer_rate = line[3].to_i
customer_duration = line[4].to_i
customer_cost = customer_rate * customer_duration
customers[customer_name] ||= {"domains" => [], "cost" => 0, "duration" => 0}
customers[customer_name]["domains"] << customer_domain
customers[customer_name]["cost"] += customer_cost
customers[customer_name]["duration"] = [customer_duration, customers[customer_name]["duration"]].max
end |
Step 6: Open the output file. To ensure that it is created if it doesn't exist, pass the File::CREAT constant to the open method.
code: | output_file = File.open("customer_info.dat", "w", File::CREAT) |
Step 7: Iterate over the customers hash and for each customer, collect a string containing the output expected in the problem summary.
code: | output_strings = customers.collect do |customer_name, customer_info|
cn, ci = customer_name, customer_info
"#{cn} owes us $#{ci["cost"]} over the next #{ci["duration"]} years for the domains #{ci["domains"].join(", ")}."
end |
Step 8: Output those lines to the file, then close the file.
code: | output_file.puts(output_strings)
output_file.close |
One note
If a block is passed to File.open, it will accept the file as an argument, run the code inside the block, and at the end, ensure that the file is closed, freeing us from having to remember to close the file.
However, since any variables created inside the block are local to the block and die when the block finishes, it's less useful for reading the input file.
The code
code: | input_file = File.open("customer_data.txt", "r")
contents = input_file.readlines
input_file.close
contents.collect! { |line| line.split(/\s*\|\s*/, 5) }
customers = {}
contents.each do |line|
customer_name = line[0]
customer_domain = "#{line[1]}.#{line[2]}"
customer_rate = line[3].to_i
customer_duration = line[4].to_i
customer_cost = customer_rate * customer_duration
customers[customer_name] ||= {"domains" => [], "cost" => 0, "duration" => 0}
customers[customer_name]["domains"] << customer_domain
customers[customer_name]["cost"] += customer_cost
customers[customer_name]["duration"] = [customer_duration, customers[customer_name]["duration"]].max
end
output_strings = customers.collect do |customer_name, customer_info|
cn, ci = customer_name, customer_info
"#{cn} owes us $#{ci["cost"]} over the next #{ci["duration"]} years for the domains #{ci["domains"].join(", ")}."
end
File.open("customer_info.dat", "w", File::CREAT) { |f| f.puts(output_strings) } |
|
|
|
|
|
|
Sponsor Sponsor
|
|
|
wtd
|
Posted: Fri Jul 16, 2004 11:14 pm Post subject: (No subject) |
|
|
Just for the heck of it, I translated the Ruby example, as closely as possible into Python code.
code: | import re
try:
input_file = file("customer_data.txt", "r")
except IOError, e:
file("customer_data.txt", "w").close()
input_file = file("customer_data.txt", "r")
contents = input_file.readlines()
input_file.close()
contents = [re.split(r"\s*\|\s*", line, 5) for line in contents]
customers = {}
for line in contents:
customer_name = line[0]
customer_domain = "%s.%s" % (line[1], line[2])
customer_rate = int(line[3])
customer_duration = int(line[4])
customer_cost = customer_rate * customer_duration
if customer_name not in customers:
customers[customer_name] = {"domains": [], "cost": 0, "duration": 0}
customers[customer_name]["domains"].append(customer_domain)
customers[customer_name]["cost"] += customer_cost
customers[customer_name]["duration"] = max(customer_duration, customers[customer_name]["duration"])
customer_strings = ["%s owes us $%d over the next %d years for the domains %s." % (cn, ci["cost"], ci["duration"], ", ".join(ci["domains"])) for cn, ci in customers.items()]
output_file = file("customer_info3.dat", "w")
output_file.writelines([line + "\n" for line in customer_strings])
output_file.close() |
|
|
|
|
|
|
|
|