Implementing ’15 Exercises for Learning a new Programming Language’

A short time ago in a galaxy not so far, far away I came across a nice blog post: 15 Exercises for Learning a new Programming Language.

One could argue if these are *really* the most appropriate 15(+) exercises to learn a new programming language – however, the task of answering this rather complex question is left as an exercise for the reader. Instead of this I will show you their implementation in Ruby – rubyrailways.com style.

Why did I bother to solve these problems (including not really trivial ones, like a scientific calculator with a GUI) ? Well, actually to learn a new programming language! I still consider myself a beginner Ruby apprentice just playing it by ear in my somewhat scarce free time, so I thought that systematically implementing a task list like this will mean great step forward for me compared to just coding random things at random times. Fortunately I was perfectly right!

Before we move onto the code, one last disclaimer: the fact that I am still a Ruby n00b implies that the code can be somewhat hairy/not optimal/[insert any other language than Ruby here]-ish so don’t use these snippets as a textbook solution of the problems or anything like that. I would be glad if someone could suggest a bit of refactoring of the bad parts but I also hope that that there are some nice parts which you can learn from (actually I am quite sure about this since I used some magick formulas from a few Ruby (grand)masters in some cases).

OK, enough talk for now. Let’s see the stuff!

1. Problem: “Display series of numbers (1,2,3,4, 5….etc) in an infinite loop. The program should quit if someone hits a specific key (Say ESCAPE key).”

Solution: Hmm, well, errr…uh-oh… I could not solve this problem fully (what a terrific start :-) ). If Henry Ford would sit beside me now, he would say : You can hit any key to exit – so long as it’s ‘C’ – and one more advice: don’t forget to hold CTRL during this action :-) . More on this after the code snippet:

i = 0
loop { print "#{i+=1}, " }

Comments :
If anyone knows how to add code which will cause this program to stop with a specific keyhit (say ‘ESC’) please, please, please drop me a note. I have been researching this for at least 10% of the time of solving all the tasks, nearly spitting blood when I gave up :-) . It seems (to me) that there is no simple (i.e. no threads and similar) and clean platform-independent solution for this problem. I guess (hope) the author’s idea here was different than to introduce threading or writing platform specific-code…

2. Problem: “Fibonacci series, swapping two variables, finding maximum/minimum among a list of numbers.”

Solution:

#Fibonacci series
Fib = Hash.new{ |h, n| n < 2 ? h[n] = n : h[n] = h[n - 1] + h[n - 2] }
puts Fib[50]

#Swapping two variables
x,y = y,x

#Finding maximum/minimum among a list of numbers
puts [1,2,3,4,5,6].max
puts [7,8,9,10,11].min

Comments: The Fibonacci code was written by Andrew Johnson (found via Ruby Quiz). I like it so much that I think it would be a shame to present a trivial version here. I guess the rest of the code is self-explanatory.

3. Problem: "Accepting series of numbers, strings from keyboard and sorting them ascending, descending order."

Solution:

a = []
loop { break if (c = gets.chomp) == 'q'; a << c }
p a.sort
p a.sort { |a,b| b<=>a }

Comments: This version is accepting strings - I think anybody who got to this point can adapt it to work with numbers.

4. Problem: "Reynolds number is calculated using formula (D*v*rho)/mu Where D = Diameter, V= velocity, rho = density mu = viscosity Write a program that will accept all values in appropriate units (Don't worry about unit conversion) If number is < 2100, display Laminar flow, If it’s between 2100 and 4000 display 'Transient flow' and if more than '4000', display 'Turbulent Flow' (If, else, then...)"

Solution:

vars = %w{D V Rho Mu}

vars.each do |var|
  print "#{var} = "
  val = gets
  eval("#{var}=#{val.chomp}")
end

reynolds = (D*V*Rho)/Mu.to_f

if (reynolds < 2100)
  puts "Laminar Flow"
elsif (reynolds > 4000)
  puts "Turbulent Flow"
else
  puts "Transient Flow"
end

Comments: Can you spot the trick in the part which is filling up the variables? They don't go out of scope after the loop ends because they are constants. Other possibility would be to use $global variables but I guess it is usually not a very good programming practice to do that.

5. Problem: "Modify the above program such that it will ask for 'Do you want to calculate again (y/n), if you say 'y', it'll again ask the parameters. If 'n', it'll exit. (Do while loop)
While running the program give value mu = 0. See what happens. Does it give 'DIVIDE BY ZERO' error? Does it give 'Segmentation fault..core dump?'. How to handle this situation. Is there something built in the language itself? (Exception Handling)"

Solution:

vars = { "d" => nil, "v" => nil, "rho" => nil, "mu" => nil }

begin
  vars.keys.each do |var|
    print "#{var} = "
    val = gets
    vars[var] = val.chomp.to_i
  end

  reynolds = (vars["d"]*vars["v"]*vars["rho"]) / vars["mu"].to_f
  puts reynolds

  if (reynolds < 2100)
    puts "Laminar Flow"
  elsif (reynolds > 4000)
    puts "Turbulent Flow"
  else
    puts "Transient Flow"
  end

  print "Do you want to calculate again (y/n)? "
end while gets.chomp != "n"

Comments: As you can see, I could not use the same trick here when asking for the variables, because when somebody wants to calculate again, Ruby will complain (although by printing a warning only) that the constants have been already set up. Therefore I went for the hash solution. I think the do-you-want-to-calculate-again part is straightforward so I won't analyze that here.

"While running the program give value mu = 0."

Ruby gives a rather interesting result in this case: infinity :-) .

"Is there something built in the language itself?"

Sure: exception handling. Division by zero could be caught with a ZeroDivisionError rescue clause.

6. Problem: "Scientific calculator supporting addition, subtraction, multiplication, division, square-root, square, cube, sin, cos, tan, Factorial, inverse, modulus"

Solution:

Since this code snippet is longer It would look ugly here - you can download it from here instead.

Screenshot:


screenshot of the scientific calculator in action

If you would like to try it, you will need the Tk bindings for Ruby (maybe you have them already, here on Ubuntu I did not). Also note that only the regular 0-9 keys (and of course the mouse) work, the numpad ones do not. One more little detail: % stands for modulo, not percent.

Comments: Phew, this was a real challenge, mostly because I never did any GUI in Ruby before. I was amazed that I could code up a relatively feature rich calculator in 100+ lines of code, without any golfing or trying to optimize for shortness. What I wanted to say with this is that the shortness does not praise my programming skills (since I did not eve try to golf) but the superb terseness of Ruby. OK, of course there are some problems (e.g. cube, cos, tan, inverse are not implemented) but the usability/amount of code ratio is unbelievably high.

The GUI is also not the nicest since I have used Tk - wxRuby or qt-ruby would produce much nicer results, but since I did not code any GUI in Ruby previously, I have decided to try the good-old-skool Tk for the first time.

7. Problem: "Printing output in different formats (say rounding up to 5 decimal places, truncating after 4 decimal places, padding zeros to the right and left, right and left justification)(Input output operations)"

Solution:

#rounding up to 5 decimal pleaces
puts sprintf("%.5f", 124.567896)

#truncating after 4 decimal places
def truncate(number, places)
  (number * (10 ** places)).floor / (10 ** places).to_f
end

puts truncate(124.56789, 4)

#padding zeroes to the left
puts 'hello'.rjust(10,'0')

#padding zeroes to the right
puts 'hello'.ljust(10,'0')

#right justification
puts ">>#{'hello'.rjust(20)}<<"

#left justification
puts ">>#{'hello'.ljust(20)}<<"

Comments: Amazingly lot of things can be done with sprintf() - I could solve nearly all the problems with it - but that would not really be rubyish, so I have decided for built-in (and one homegrown) functions. However, mastering (s)printf() is a very handy thing, since nearly all big players (C (of course :-) ), C++, Java, PHP, ... ) have it so you get a powerful function in more languages for the price of learning one). As you can see, r/ljust is a nice one, too.

8. Problem: "Open a text file and convert it into HTML file. (File operations/Strings)"

Solution: Well, this problem was not specified in a great detail, to say the least - or to put it otherwise, the solvers are given a great freedom to provide a solution spiced up with their fantasy. This is what I came up with:

doc = <strong tag.
DOC

final_doc = <
  
    
  
  
    

embed_doc_here

FINAL_DOC rules = {'*something*' => 'something', '/something/' => 'something'} rules.each do |k,v| re = Regexp.escape(k).sub(/something/) {"(.+?)"} doc.gsub!(Regexp.new(re)) do content = $1 v.sub(/something/) { content } end end doc.gsub!("\n\n") {"

\n

"} final_doc.sub!(/embed_doc_here/) {doc} puts final_doc

Comments: As you can see, besides that the text is wrapped around with a minimal HTML, every occurrence of words between asterisks is outputted in strong and between slashes in italic. You can add as many such rules as you like, they will be (hopefully) substituted in the final output.

9. Problem: "Time and Date : Get system time and convert it in different formats 'DD-MON-YYYY', 'mm-dd-yyyy', 'dd/mm/yy' etc."

Solution: Well, it was not really clear (for me) what should be the difference between 'yyyy' and 'YYYY' (resp. 'dd' vs 'DD') so again I had to use my imagination. However, I guess it does not matter too much, the solution has to be changed by 1-2 characters only if the original author had something different on his mind.

require 'date'

time = Time.now
#'DD-MON-YYYY', e.g. 12-Nov-2006 in my interpetation
puts time.strftime("%d-%b-%Y")

#'mm-dd-yyyy', e.g. 11-12-2006 in my interpetation
puts time.strftime("%m-%d-%Y")

#'dd/mm/yy', e.g. 12/11/2006 in my interpetation
puts time.strftime("%d/%m/%Y")

10. Problem: "Create files with date and time stamp appended to the name"

Solution:

#Create files with date and time stamp appended to the name
require 'date'

def file_with_timestamp(name)
  t = Time.now
  open("#{name}-#{t.strftime('%m.%d')}-#{t.strftime('%H.%M')}", 'w')
end

my_file = file_with_timestamp('test.txt')
my_file.write('This is a test!')
my_file.close

Comments: Maybe a more elegant solution could be to subclass File and override its constructor - but maybe that would be an overkill. I have voted for the latter option in this case :-) .

11. Problem: "Input is HTML table. Remove all tags and put data in a comma/tab separated file."

Solution: Since web extraction is both my PhD topic and my everyday job (and even my free-time activity :-) ) I will present 3 solutions for this problem. First, the classic old-school regexp way (by Paul Lutus), then with HPricot and finally with scRUBYt!, a simple yet powerful Ruby web extraction framework currently developed by me.

table = <
  
    1
    2
  
  
    3
    4
    5
  
  
    6
  

DOC

rows = table.scan(%r{.*?}m)

rows.each do |row|
   fields = row.scan(%r{(.*?)}m)
   puts fields.join(",")
end

Now for the HPricot solution (in the further examples let's consider that table is initialized as in the previous example):

require 'rubygems'
require 'hpricot'

h_table = Hpricot(table)

rows = h_table/"//tr"
rows.each do |row|
  child_text = (row/"//td").collect {|elem| elem.innerHTML }
  puts child_text.join(',')
end

and last, but not least scRUBYt!

require 'scrubyt'

table_data = P.table do
               P.cell '1'
             end

table_data.generalize :cell

puts table_data.to_csv

Some explanation: first of all, at the moment scRUBYt! is avaliable on my hard disk (and partially in my head) only - it should be released around XMAS 2006. I am using this solution for a little bit of self-promotion :-) .

The example works like this: extract something (in this case a HTML <table>) which has something (in this case <td>) which has '1' as its text (well in reality much more is going on in the background, but roughly along these lines). This little code snippet will extract the first <td>s of ALL <tables> on a HTML page. With the 'generalize' call we tell the extractor that it should not extract just the first <td> in a table (which is the default setting), but all of them.

scRUBYt! can handle much, much, MUCH more complicated examples than this (like an ebay or amazon page) and has loads of sophisticated functions... so stay tuned!

12. Problem: "Extract uppercase words from a file, extract unique words."

Solution: (you can find some_uppercase_words.txt here and some_repeating_words.txt here

open('some_uppercase_words.txt').read.split().each { |word| puts word if word =~ /^[A-Z]+$/ }

words = open('some_repeating_words.txt').read.split()
histogram = words.inject(Hash.new(0)) { |hash, x| hash[x] += 1; hash}
histogram.each { |k,v| puts k if v == 1 }

13. Problem: "Implement word wrapping feature (Observe how word wrap works in windows 'notepad')."

Solution: Unfortunately I am not a Windows user and I have seen notepad a *quite* long time ago - so I am not sure the task and it's implementation are fully in-line - I have tried my best. Here we go:

input = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

def wrap(s, len)
  result = ''
  line_length = 0
  s.split.each do |word|
    if line_length + word.length + 1  < len
      line_length += word.length + 1
      result += (word + ' ')
    else
      result += "\n"
      line_length = 0
    end
  end
  result
end

puts wrap(input, 30)

14. Problem: "Adding/removing items in the beginning, middle and end of the array."

Solution:

x = [1,3]

#adding to beginning
x.unshift(0)

#adding to the end
x << 4

#adding to the middle
x.insert(2,2)

#removing from the beginning
x.shift

#removing from the end
x.pop

#removing from the middle
x.delete(2)

#we have arrived at the original array!

15. Problem: "Are these features supported by your language: Operator overloading, virtual functions, references, pointers etc."

Solution: Well this is not a real problem (not in Ruby, at least). Ruby is a very high level language ant these things are a must :) .

Finally, you can download all the solutions in a single archive from here.
I would like to see the implementation of these tasks in both Ruby (different (more optimal) solutions of course) as well as in anything else. If you set out to do something like that, be sure to drop me a note.

Internet contains huge number of opportunities to earn money online. Simply create a site that you think has the potential to sell hot items using ruby on rails. Register a relevant domain name and purchase a web hosting service through hostgator, one of the better web host out there today. Get a internet connection through one of the wireless internet providers to upload your site. Work on search engine optimization to get a better traffic and also use affiliate marketing program for the same reason. Finally get a free voip phone service to contact customers directly. The pc to phone system is the most effective method of marketing.


32 thoughts on “Implementing ’15 Exercises for Learning a new Programming Language’

  1. The “break calculation at a keypress” is as follows – essentially just do a nonblocking read and rescue the “resource not available” exceptions as they come. Just hit enter to kill the loop.

    i=0
    loop do
    begin
    break if STDIN.read_nonblock(1000)
    rescue Errno::EAGAIN
    end
    puts i
    i += 1
    end

    For #4, I’d recommend using @instance variables. Use instancevariableset instead of eval. Eval is very dangerous, especially when you’re not checking it like there. Storing it in a hash like you have for #5 is good as well.

    For #8, the redcloth and bluecloth (textile and markdown, respectively) are dead-simple html converters.

  2. Jamie:

    Thanks for the reply!
    I have modified your code a bit:

    require 'io/nonblock'
    
    i=0
    loop do
      begin
        break if STDIN.nonblock{ STDIN.sysread(100) }
      rescue Errno::EAGAIN
      end
      puts i += 1
    end
    

    Which is cool – but there is one problem: You need Henry Ford again :-) . The original task was to stop on a specified key, say ‘ESC’ or ‘x’ (and no enter afterwards) and this code stops solely on enter. Can it be modified to stop on ‘esc’?

    As for the eval – tanks for the suggestion. For #4, I wanted to write the shortest code possible for better understanding. While #5 is more secure and this is how it should like in a real-life app, I think it is also harder to comprehend for a beginner. But anyway, the moral of the story is that we should not use eval too much :-)

    RedCloth/BlueCloth – I will check them out and ad them to the solution.

  3. Satish:

    Cool! I hope a conversation, beneficial for both parties, will be started :-) . There are tons of other ways to code these examples and I would like to hear other ideas as well. Thanks.

  4. Hey i had no clue about Ruby until this morning.But think i am falling in love with it..your codes were really helpful for me to have a look at how basic ruby programming will be…
    a single glitch :
    the link in the problem No:6 to download the code for it is not proper.It redirects to the same page.

  5. Pingback: Ruby, Rails, Web2.0 » Blog Archive » Site Updates

  6. Hello, (?????) I don’t think I saw your name………. sorry if I’m blind…….

    Just heard about Ruby yesterday and it looks fascinating. I programmed a lot in QuickBasic loooooong ago but Win95 put me out of that business.

    Everybody shows bits of Ruby code, but nobody starts far enough back to tell me HOW to do that; do I just type the code in Notepad or Wordpad and then save it as abcd.exe and run it? (or compile it into .exe first)

    Can we start at “square one” please? Some of us are n00bs.

  7. Hi Ken,

    My name is Peter (if you go to http://www.rubyrailways.com, there is a ‘whoami’ link on the right)… It’s kinda linux-style so it may look weird to some non-*nix guys :-)

    QuickBasic was cool… I also used to program a lot in it (after I got my first PC – before that I had a Sinclair ZX Spectrum and a Commodore 64, so those were my first BASICs :-) but ruby is much, much cooler…

    As for the HOWTO part: Ruby is (like BASIC) an intepreted language – this means that the code is not compiled to a binary format (though it is posible to do so) but interpreted on the fly. So you enter the stuff into notepad, then save the file as ‘mycoolprogram.rb’ and either type

    ruby my_cool_program.rb
    

    to a commadline shell, or set up a file association on the type ‘.rb’ and just double click it (however, some further tweaking may be needed here , since if I remember correctly a shell pops up with the output then it closes immediately so you can not inspect the output).

    First you should install Ruby: Check out this page: http://rubyinstaller.rubyforge.org/

    Then you should make sure that ruby is in the path… and this should be about it.

    If you still have questions, don’t hesitate to write me (at peter@[name of this site].com). The Ruby mailing list is also a good place to start, no matter how noob or expert you are – the people are really helpful.

    Hope this helps…

  8. Great read. It shows a lot of the ways that Ruby establishes itself from the other languages. My only real comment is that on problem 5. The question says “if you say ‘y’, it’ll again ask the parameters. If ‘n’, it’ll exit”. The way you have it implemented is so that anything other than ‘n’ will continue the loop. It wouldn’t be a bad idea to throw in a method that check for y/n and if gets is !y/n then shout out some type of “Invalid char.” nonsense and have it re-loop the y/n question. This isn’t really an issue with the programming, just a statement to those confused by why when they accidentally hit ‘/[^n]/’ on their keyboard. All in all, I think it is great that you put this up. Now, if others would just make more like yours but in different languages.

  9. Hi Peter,

    very nice blog.
    as for problem #1, you can do something like the ff (in windows),

    C:\family\ruby\key_press>cat a1b.rb

    ——————————

    require ‘Win32API’

    @@kbhit = Win32API.new(“msvcrt”, “kbhit”, [], ‘I’)
    @@getch = Win32API.new(“msvcrt”, “
    getch”, [], ‘I’)

    def exit_onkey key
    unless @@kbhit.call.zero?
    c = @@getch.call
    if c == key
    exit
    end
    end
    end

    KEY_Esc = 27
    $stdout.sync=true

    i=0
    loop do
    exitonkey KEYEsc
    puts “#{i+=1}”
    end

    ——————————

    note, that we created another method exit_onkey just for readability and flexibility purposes.
    kind regards -botp

  10. Pingback: links for 2007-05-23 « mriror

  11. In lines 11-12 of example 13, I think you mean:
    result += “\n” + word + ‘ ‘
    linelength = word.length + 1
    instead of
    result += “\n”
    line
    length = 0
    otherwise the words that trigger line breaks are lost.
    Or, with slightly less repetition:
    s.split.each do |word|
    if linelength + word.length + 1 >= len
    result += “\n”
    line
    length = 0
    end
    line_length += word.length + 1
    result += (word + ‘ ‘)
    end

  12. Pingback: links for 2007-08-23 « Bloggitation

  13. I cant get the break on ENTER loop working on ruby 1.8.6

    require ‘io/nonblock’

    i=0
    loop do
    begin
    break if STDIN.nonblock{ STDIN.sysread(100) }
    rescue Errno::EAGAIN
    end
    puts i += 1
    end

    any ideas why?

    I get this error

    loopESC.rb:6: undefined method nonblock' for #<IO:0x2846b04> (NoMethodError)
    from loopESC.rb:4:in
    loop’
    from loopESC.rb:4

  14. are your platform windows?nonblock.rb requires fcntl,but it didn’t implement on all platforms?

  15. I see there’s already a Windows specific answer to the first problem, so I shall provide the Unix solution:

    #!/usr/bin/env ruby -wKU
    
    require "io/wait"
    
    state = `stty -g`
    begin
      system "stty raw -echo cbreak isig"
    
      1.upto(1.0/0.0) do |n|
        puts n
        exit if $stdin.ready? and $stdin.getc == 27
      end
    
    ensure
      system "stty #{state}"
    end
    
    __END__
    

    The reason you need the platform specific code here is that you are doing complex interactions (reading and writing at the same time) with a terminal and all terminals are different.

  16. In solution #3 instead of << p a.sort { |a,b| b<=>a } >> you could use << p a.sort.reverse >>…just more readable

  17. Pingback: links for 2008-03-29 | Moewes.com

  18. Wow, thank you for this post. I will go home and do the ’15 exercises for learning a new Programming Language’. And I’ll use the above coding as my reference. I’ve just recently started developing in Ruby on Rails, so I’m at a point where I just want as many challenges and new applications to develop as possible, because I want to fully explore RoR.

    Thank you again,

  19. For a simpler UNIX solution, you can trap SIGINT (Ctrl-C)

    i = 0
    go = true
    while go do
     print "#{i+=1}, "
     Signal.trap("INT") do
      go = false
     end
    end
    puts "Terminated by user."
    
  20. In response to problem 1, interrupting an infinite sum by hitting a character:

    Without doing threads in Ruby (like you would in Python, for ex), maybe you can do something simple like check for a kbhit (received character from std in) in your loop. This is a common technique when reading rs-232 serial lines on a microcontroller. You want to get data if its in a buffer, but if it is not then you absolutely can’t hang in a get operation.

    something like:

    if(kbhit())
    car=getc()

    I’m 2 hours into this language, so I don’t know if this method exists, but it seems like it probably does.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>