:: Topo :: doCoding :: Linguagens de Programação :: Ruby :: FORPC101 ::

Week-04

Aprendizagem

  1. Ranges - usar simplesmente ”..” (max. inclusive) ou ”…” (max. exclusive).
  2. Closures - ou Ruby Code Blocks, definem-se através de ”{ … }” - se uma linha apenas de bloco - ou “do … end” - se várias linhas de bloco. Atenção: parêntesis curvos tem maior precedência do que do..end. Os blocos não são objectos, mas podem ser convertidos em objectos de classe Proc, através do método lambda().
    Nota: não é permitido passar métodos a outros métodos nem pode um método retornar outros métodos. Mas um método pode receber um objecto Proc e pode retornar um objecto Proc:
  3. rand() gera um número aleatório. Sem argumentos o número é um Float entre 0.0 (inclusive) e 1.0 (exclusive). Com argumento interior gera um Integer entre 0 e esse argumento (exclusive).
  4. Para trabalhar com ficheiros e pastas: File.open, Find.find, IO::seek

Exercícios

Exercício 1

exercise-01.rb

=begin
Why is the output of this program:
50
10
=end
 
def method
  # a is a local variable to method, hided from main object.
  a = 50
  puts a
end
 
# a is a local variable to main object.
a = 10
method
puts a
 
=begin
Thanks to Benjamin Davis:
  From the Scope: Ruby Study Notes PAge we find (3) things:
    - The top level (outside of all definition blocks) has its own local scope.
    - Every class or module definition block (class, module) has its own local scope, even nested class/module definition blocks.
    - Every method definition (def) has its own local scope.
 
As such, each variable a has a local scope: one belongs to the method local scope and the other to the top level local scope.
They are indeed 2 different variables and one doesn't "overlap" or "override" the other.
=end

Exercício 2

exercise-02.rb

=begin
This is a non-programming exercise.
Try and find out (maybe with examples) the difference between the methods:
 
to_str, to_s
=end
 
=begin
Both ri String#to_s and ri String#to_str return
------------------------------------------------------------ String#to_s
     str.to_s     => str
     str.to_str   => str
------------------------------------------------------------------------
     Returns the receiver.
 
Seems to be an alias. But, from the Ruby Documentation:
  http://www.ruby-doc.org/core/classes/String.html#M000805
  http://www.ruby-doc.org/core/classes/Exception.html#M001335
one sees that to_str() is a method of only String and Exception,
while to_s is a method of many objects. Why is so?
 
From Al Snow
  http://briancarper.net/2006/09/26/ruby-to_s-vs-to_str/
From Odracir Antunes Jr.
  http://osdir.com/ml/lang.ruby.core/2003-06/msg00083.html
 
How about a good example with Exception.to_str()?
=end

Exercício 3

exercise-03.rb

=begin
Thanks to Marcos Souza for this exercise.
 
A plain text file has the following contents:
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
test test word test test
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
 
Observe that in this file, there exists a word 'word'.
 
Write a nifty but readable Ruby program that updates this file and the final contents become like this:
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
test test inserted word test test
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
 
Do not hard-code the file name.
=end
 
def usage
 
  puts 'Usage:'
  puts "\t#{$0} «file-name» «search-word» «replace-word»"
  puts 'Where arguments are:'
  puts "\t«file-name»    - name of the local file to read"
  puts "\t«search-word»  - string, without spaces, to search for"
  puts "\t«replace-word» - string, without spaces, to replaced matching words"
 
end
 
def search_and_replace_line(line, search_word, replace_word)
 
  words = line.split(' ')
  # Search for occurrences of SearchString in each Word.
  words.each_with_index do | word, index |
    if word == search_word then
      words[index] = replace_word
    end
  end
  # Join the words, separated by whitespace, into a line.
  line = words.join(' ')
 
end
 
def open_file(file_name, search_word, replace_word)
 
  lines = []
 
  # ToDo: Check that file exists and is readable.
  open(file_name, 'r') do | file_handler |
    while line = file_handler.gets
      line = search_and_replace_line(line, search_word, replace_word)
      lines << line
    end
  end
 
  return lines
 
end
 
def save_lines_to_file(file_name, lines)
 
  # ToDo: Check that file exists and is readable.
  open(file_name, 'w') do | file_handler |
    lines.each do | line |
      file_handler.puts line
    end
  end
 
end
 
if ARGV.length == 3 then
 
  file_name    = ARGV[0]
  search_word  = ARGV[1]
  replace_word = ARGV[2] + ' ' + search_word
 
  lines = open_file(file_name, search_word, replace_word)
  save_lines_to_file(file_name, lines)
 
else
 
  usage
 
end

O uso de outros métodos, das classes String, File e IO, torna o código ainda mais legível:

exercise-03.rb - refactored

=begin
Thanks to Marcos Souza for this exercise.
 
A plain text file has the following contents:
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
test test word test test
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
 
Observe that in this file, there exists a word 'word'.
 
Write a nifty but readable Ruby program that updates this file and the final contents become like this:
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
test test inserted word test test
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
 
Do not hard-code the file name.
=end
 
def usage
 
  puts 'Usage:'
  puts "\t#{$0} «file-name» «search-word» «replace-word»"
  puts 'Where arguments are:'
  puts "\t«file-name»    - name of the local file to read"
  puts "\t«search-word»  - string, without spaces, to search for"
  puts "\t«replace-word» - string, without spaces, to replaced matching words"
 
end
 
def read_lines_from_file(file_name)
 
  lines = []
 
  open(file_name, 'r') do | file_handler |
    lines = file_handler.readlines
  end unless !File.readable?(file_name) or
             !File.exists?(file_name)
 
  return lines
 
end
 
def save_lines_to_file(file_name, lines)
 
  open(file_name, 'w') do | file_handler |
    lines.each do | line |
      file_handler.puts line
    end
  end unless !File.writable?(file_name) or
             !File.exists?(file_name)
 
end
 
def replace_words_in_lines(lines, search_word, replace_word)
 
  aux_lines = []
 
  lines.each do | line |
    line = line.sub(search_word, replace_word)
    aux_lines << line
  end
 
  return aux_lines
 
end
 
if ARGV.length == 3 then
 
  file_name    = ARGV[0]
  search_word  = ARGV[1]
  replace_word = ARGV[2] + ' ' + search_word
 
  lines = read_lines_from_file(file_name)
  lines = replace_words_in_lines(lines, search_word, replace_word)
  save_lines_to_file(file_name, lines)
 
else
 
  usage
 
end

Exercício 4

exercise-04.rb

=begin
Make use of the class Dir for the following:
  - Display your current working directory.
  - Create a new directory tmp under your working directory.
  - Change your working directory to tmp.
  - Display your current working directory.
  - Go back to your original directory.
  - Delete the tmp directory.
=end
 
# Display your current working directory.
puts 'Current Working Directory: ' + Dir.pwd
 
# Create a new directory tmp under your working directory.
Dir.mkdir('tmp')
 
# Change your working directory to tmp.
Dir.chdir('tmp') do
  # Display your current working directory.
  puts 'Current Working Directory: ' + Dir.pwd
end
# Go back to your original directory.
# Done implicitly after end of block of Dir.chdir
 
# Delete the tmp directory.
Dir.rmdir('tmp')

Exercício 5

exercise-05.rb

=begin
Given the following Ruby code snippet,
when you run this program,
which of the following values will not be displayed?
   1. 1929
   2. 1930
   3. 1945
   4. 1950
   5. 1951
   6. 1952
=end
 
a = (1930...1951).to_a
puts a[rand(a.size)]
 
# a is an array converted from a Range from 1930 up to 1950,
#   since "..." constructor excludes upper boundary.
# a.size is the number of items in the array = 1950 - 1030 + 1 = 21.
# rand(int) returns a random integer number from 0 to int, excluding int.
#   in this case int = array.size = 21.
# a[index] is short-hand for "return the item of Array a in position index",
#   where position starts at 0 and goes up to Array.size, excluding it.
#
# As such, the value never displayed is 1951, because it's excluded by "..."
# And because 1929 and 1952 are never within range, these are never displayed as well.
 
r = (1930...1951)
puts 'Range is: ' + r.to_s
a = r.to_a
puts 'Array is: ' + a.join(', ').to_s
puts 'Array Size = ' + a.size.to_s
puts 'Random Number = ' + rand(a.size).to_s

Exercício 6

exercise-06.rb

=begin
Given a string s = 'key=value',
create two strings s1 and s2
such that s1 contains key and s2 contains value.
Hint: Use some of the String functions.
Call this program p021rangesex.rb
=end
 
s = 'key=value'
 
# Only uses on String method and uses an Array.
a = s.split('=')
s1 = a[0]
s2 = a[1]
# Improved from Victor Brylew
s1, s2 = s.split('=')
puts 'One-Liner'
puts '  s1 is ' + s1
puts '  s2 is ' + s2
 
# Too complicated, just to show-off method each(separator).
# Could be better if String had each_with_index, like Array.
# Declared here to avoid losing value in local block:
s1       = ''
s2       = ''
s.each('=') do | element |
  s2 = !s1.empty? ? element : s2
  s1 = s1.empty? ? element.chop : s1
end
puts 'Cumbersome'
puts '  s1 is ' + s1
puts '  s2 is ' + s2
 
# Using method index and accessing String with Ranges.
index = s.index('=')
s1 = s[0...index]
index += 1
s2 = s[index...s.size]
puts 'Slick'
puts '  s1 is ' + s1
puts '  s2 is ' + s2

Exercício 7

exercise-07.rb

=begin
Write a Deaf Grandma program (call it p026zdeafgm1.rb).
 
Whatever you say to grandma (whatever you type in),
she should respond with HUH?! SPEAK UP, SONNY!,
unless you shout it (type in all capitals).
 
If you shout, she can hear you (or at least she thinks so) and yells back,
NO, NOT SINCE 1938!
 
To make your program really believable,
have grandma shout a different year each time;
maybe any year at random between 1930 and 1950.
 
You can't stop talking to grandma until you shout BYE.
=end
 
def cannot_hear_you
  puts 'HUH?! SPEAK UP, SONNY!'
end
 
def yells_back
  years_range = 1930..1950
  years = years_range.to_a
  year = years[rand(years.size)]
  puts "NO, NOT SINCE #{year}!"
end
 
def shouted?(phrase = 'hi')
  phrase.eql?(phrase.upcase)
end
 
puts 'Start chatting with Deaf Grandma'
STDOUT.flush
while (input = gets.chomp) != 'BYE'
  if shouted?(input) then
    yells_back
  else
    cannot_hear_you
  end
  STDOUT.flush
end

Exercício 8

exercise-08.rb

=begin
Call this program (p026zdeafgm2.rb)
Extend your Deaf Grandma program (Exercise7):
What if grandma doesn't want you to leave?
When you shout BYE, she could pretend not to hear you.
Change your previous program so that you have to shout BYE three times in a row.
Make sure to test your program: if you shout BYE three times, but not in a row,
you should still be talking to grandma.
=end
 
def cannot_hear_you
  puts 'HUH?! SPEAK UP, SONNY!'
end
 
def yells_back
  years_range = 1930..1950
  years = years_range.to_a
  year = years[rand(years.size)]
  puts "NO, NOT SINCE #{year}!"
end
 
def shouted?(phrase = 'hi')
  phrase.eql?(phrase.upcase)
end
 
asked_to_leave = 0
puts 'Start chatting with Deaf Grandma'
while asked_to_leave < 3
  STDOUT.flush
  input = gets.chomp
  if input == 'BYE' then
    asked_to_leave += 1
    cannot_hear_you
  else
    asked_to_leave = 0
    if shouted?(input) then
      yells_back
    else
      cannot_hear_you
    end
  end
end

Exercício 9

exercise-09.rb

=begin
Write a Ruby program (call it p028swapcontents.rb) to do the following.
Take two text files say A and B.
The program should swap the contents of A and B ie. after the program is executed,
A should contain B's contents and B should contains A's.
=end
 
def usage
 
  puts 'Usage:'
  puts "\t#{$0} «file-name-a» «file-name-b»"
  puts 'Where arguments are:'
  puts "\t«file-name-a» - name of the local 'A' file to read"
  puts "\t«file-name-b» - name of the local 'B' file to read"
 
end
 
def read_lines_from_file(file_name)
 
  lines = []
 
  open(file_name, 'r') do | file_handler |
    lines = file_handler.readlines
  end unless !File.readable?(file_name) or
             !File.exists?(file_name)
 
  return lines
 
end
 
def save_lines_to_file(file_name, lines)
 
  open(file_name, 'w') do | file_handler |
    lines.each do | line |
      file_handler.puts line
    end
  end unless !File.writable?(file_name) or
             !File.exists?(file_name)
 
end
 
if ARGV.length == 2 then
 
  file_name_a = ARGV[0]
  file_name_b = ARGV[1]
 
  lines_a = read_lines_from_file(file_name_a)
  lines_b = read_lines_from_file(file_name_b)
  save_lines_to_file(file_name_a, lines_b)
  save_lines_to_file(file_name_b, lines_a)
 
else
 
  usage
 
end

Exercício 10

exercise-10-inventory.rb

=begin
[Difficulty level: MEDIUM]
Write a one-line Ruby script that displays on the screen
all the files in the current folder
as well as everything in all its sub folders,
in sorted order.
 
Make use of Dir.glob method as follows:
  Dir.glob('**/*')
 
Name this program inventory.rb.
 
Create an inventory file by typing the following at the command prompt:
  ruby inventory.rb > old-inventory.txt
 
After a few days, when some files would have been added / deleted from this folder,
run the program again like:
  ruby inventory.rb > new-inventory.txt
 
Now, write another Ruby script that displays on the screen
all the files that have been added in this folder
since the time the old-inventory.txt was created.
=end
 
puts Dir.glob('**/*').sort

exercise-10-newer.rb

=begin
[Difficulty level: MEDIUM]
Write a one-line Ruby script that displays on the screen
all the files in the current folder
as well as everything in all its sub folders,
in sorted order.
 
Make use of Dir.glob method as follows:
  Dir.glob('**/*')
 
Name this program inventory.rb.
 
Create an inventory file by typing the following at the command prompt:
  ruby inventory.rb > old-inventory.txt
 
After a few days, when some files would have been added / deleted from this folder,
run the program again like:
  ruby inventory.rb > new-inventory.txt
 
Now, write another Ruby script that displays on the screen
all the files that have been added in this folder
since the time the old-inventory.txt was created.
=end
 
old_inventory_ctime = File.stat('old-inventory.txt').ctime
Dir.glob('**/*').each do | file |
  if File.stat(file).ctime > old_inventory_ctime
    puts file
  end
end
 
# From Julien Faivre
puts (IO.read('new-inventory.txt').split("\n") - 
      IO.read('old-inventory.txt').split("\n"))
 
# From Leo Poiesz
old_inventory_files = File.open('old-inventory.txt' ,'r').readlines
new_inventory_files = File.open('new-inventory.txt' ,'r').readlines
new_inventory_files.reject! do | file |
  old_inventory_files.include?(file)  
end
puts new_inventory_files

Mid-Week Challenge

mid-week-challenge.rb

=begin
First of all, I'd like to thank Peter Cooper for allowing me to use this exercise.
 
The application you’re going to develop will be a text analyzer.
You would be developing this over the weeks.
Your Ruby code will read in text supplied in a separate file, analyze it for various patterns and statistics, and print out the results for the user.
 
Your text analyzer will provide the following basic statistics:
  * Character count
  * Character count (excluding spaces)
  * Line count
  * Word count
  * Sentence count
  * Paragraph count
  * Average number of words per sentence
  * Average number of sentences per paragraph
 
Before you start to code, the first step is to get some test data that your analyzer can process. You can find the text at: http://rubylearning.com/data/text.txt
 
Your application will read from text.txt by default (although you’ll make it be more dynamic and able to accept other sources of data later on).
 
Let me outline the basic steps you need to follow, for writing the application:
  - Load in a file containing the text or document you want to analyze.
  - As you load the file line by line, keep a count of how many lines there were (one of your statistics taken care of).
  - Put the text into a string and measure its length to get your character count.
=end
 
# Quick-N-Dirty Solution
lines = File.open('text.txt', 'r').readlines
count_lines = lines.size
count_characters = lines.to_s.size
 
# Complete Solution
lines            = []
count_lines      = 0
count_characters = 0
 
File.open('text.txt', 'r') do | file_handler |
  while line = file_handler.gets
    count_lines += 1
    lines << line
  end
end
 
text = lines.to_s
# text = text.split("\n").join
count_characters = text.size

Weekend Pratice

WeekendPractice.rb

=begin
Here’s code for the part of a game that saves the game state to a file.
As a deterrent against cheating, when the game loads a save file
it performs a simple check against the file’s modification time.
If it differs from the timestamp recorded inside the file,
the game refuses to load the save file.
 
The save_game method is responsible for recording the timestamp.
The load_game method is responsible for comparing the timestamp
within the file to the time the filesystem has associated with the file.
 
Write the load_game(file) method.
 
This mechanism can detect simple forms of cheating.
 
Since it’s possible to modify a file’s times with tools like the Unix touch command,
you shouldn’t depend on these methods to defend you against a skilled attacker actively trying to fool your program.
 
Read up on sleep method and Time class.
=end
 
def save_game(file)
 score = 1000
 open(file, 'w') do |f|
   f.puts(score)
   f.puts(Time.now.to_i)
 end
end
 
def load_game(file)
  modified_time = File.mtime(file)
  puts 'Modified Time: ' + modified_time.to_s
  open(file, 'r') do |f|
    score     = f.gets
    timestamp = f.gets
    puts 'Saved Time:    ' + Time.at(timestamp.to_i).to_s
    if modified_time == Time.at(timestamp.to_i) then
      puts 'Your saved score is ' + score + '.'
    else
      puts 'RuntimeError: I suspect you of cheating.'
    end
  end
end
 
save_game('game.sav')
sleep(2)
load_game('game.sav') # => "Your saved score is 1000."
# Now let's cheat by increasing our score to 9000
open('game.sav', 'r+b') { |f| f.write('9') }
load_game('game.sav') # => RuntimeError: I suspect you of cheating.

Questionário

Referências

Considerações

Foi uma semana intensa, com aprendizagem constante de novos métodos, principalmente das classes String, Array, File e IO.

Pontos Altos

Esta secção regista o que considero serem os pontos altos da linguagem Ruby quando comparada com outras linguagens que conheço.

  1. cada método retorna um tipo de objecto.
  2. apesar dos métodos não podem retornar nem receber outros métodos, podem no entanto receber e retornar objectos de classe Proc.
  3. ainda não me apercebi de todo o potencial dos Closures - Ruby Blocks - mas parece-me que são muito poderosos e flexíveis.
  4. é simples e divertido trabalhar com ficheiros.
  5. o duck typing parece-me realmente uma grande característica do Ruby, se bem que ainda só tenha um conhecimento superficial, graças ao exercício 2 de distinção entre to_s() e to_str().

Pontos Baixos

Esta secção regista o que considero serem os pontos baixos da linguagem Ruby quando comparada com outras linguagens que conheço.

  1. o método String.upcase() não converte correctamente caracteres acentuados. Por exemplo: upcase('é') = 'é' em vez de 'É'. O Satish informou que na versão 1.9 o Ruby já tem um melhor suporte Unicode.
 
docoding/languages/ruby/forpc101/week-04.txt · Modificado em: 2008/09/25 10:09 por straider     Voltar ao topo