:: Topo :: doCoding :: Linguagens de Programação :: Ruby :: FORPC101 ::
Week-04
Aprendizagem
- Ranges - usar simplesmente ”..” (max. inclusive) ou ”…” (max. exclusive).
- 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: - 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).
- 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.
- cada método retorna um tipo de objecto.
- apesar dos métodos não podem retornar nem receber outros métodos, podem no entanto receber e retornar objectos de classe Proc.
- ainda não me apercebi de todo o potencial dos Closures - Ruby Blocks - mas parece-me que são muito poderosos e flexíveis.
- é simples e divertido trabalhar com ficheiros.
- 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.
- 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.