Learn Ruby Programming by Examples en
Learn Ruby Programming by Examples en
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean
Publishing process. Lean Publishing is the act of publishing an in-progress ebook using
lightweight tools and many iterations to get reader feedback, pivot until you have the right
book and build traction once you do.
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
What is unique about this book? . . . . . . . . . . . . . . . . . . . . . . . . . . . ii
Who should read this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
How to read this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iv
Send me feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iv
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 Ruby on Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Ruby on Mac OS X . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3 Online Ruby Tutorials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4 Rhythm for Working on the exercises . . . . . . . . . . . . . . . . . . . . . 8
1.5 Suggestions on Windows Layouts . . . . . . . . . . . . . . . . . . . . . . . 9
1.6 Type of errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.7 Interactive Ruby Shell (IRB ) . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2. Printing Shapes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.1 Print out Triangle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 Print out a half diamond . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.3 Print out diamond shape . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.4 Print big diamond, name your size . . . . . . . . . . . . . . . . . . . . . . . 26
2.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3. Quiz Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.1 Simple Add Calculator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.2 Addition Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.3 Subtraction Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.4 Number Guessing Game . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
CONTENTS
6. Fun Math . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.1 Finding Divisors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.2 Finding the Highest Common Factor . . . . . . . . . . . . . . . . . . . . . 71
6.3 Finding the Least Common Multiple (LCM) . . . . . . . . . . . . . . . . . . 76
6.4 Finding Prime Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
6.5 Fibonacci sequence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
6.6 Consecutive Sums . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
6.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
7. Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
7.1 Finding the Highest Common Factor (using method) . . . . . . . . . . . . . 90
7.2 Generate Lotto Numbers (using a method) . . . . . . . . . . . . . . . . . . 93
7.3 Finding the LCM for multiple numbers (using method) . . . . . . . . . . . 94
Chapter 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Chapter 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Chapter 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Chapter 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Chapter 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
Chapter 11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
Code Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
Ruby Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
Ruby Tutorials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
More Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
Test Automation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
Others . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
Preface
On December 8, 2013, US President Barack Obama asked every American to give it a shot
to learn to code (watch it here), kicking off the Hour of Code campaign for Computer
Science Education Week 2013. Learning these skills isnt just important for your future, its
important for our countrys future, President Obama said.
In February 2013, Mark Zuckerberg and Bill Gates and several other big names in IT want
kids to learn code (video). I particularly like the quote at the beginning of the video:
You dont have to be an American to get the message: coding (aka. programming) is an
important skill for this information age. Besides the importance of programming, the other
message those VIPs also tried to convey is that you can do it.
As a programmer and software test automation coach, I have worked a lot with manual
testers. Manual testing can be repetitive and boring. However, automated testing (using test
scripts to drive software applications) is a fun, creative and satisfying job. Writing automated
test scripts requires programming skills.
When I introduced the idea of automated test scripts to manual testers, I could immediately
sense their fear: programming is too hard for me. This reaction is very typical and common.
Dont let the too hard phrase discourage you from learning. Let me tell you, programming
is not that hard, and it is fun.
Learning programming is a way to master communication with computers, by giving them
instructions to perform tasks for you. A programming language is a language that is used
to write instructions for computers to understand and execute. There are several popular
programming languages such as Java, C#, Ruby, and PHP. For beginners, dont fixate on
one. Computers internally work the same way, mastering thinking in programming is more
https://www.adafruit.com/blog/2013/12/09/president-obama-calls-on-every-american-to-learn-code/
http://www.psfk.com/2013/02/mark-zuckerberg-bill-gates-coding-school.html
i
Preface ii
important than one language syntax. In my opinion, different programming languages are
like dialects. I learned and taught myself over a dozen of programming languages. Once you
have mastered one, it is easy to learn another.
In this book, I will use Ruby, a popular and elegant programming language. Ruby is widely
used in enterprise business applications and software testing (Twitter was initially developed
in Ruby). The main reason I chose Ruby is that it is concise. Therefore, learners can focus
more on thinking rather than the syntax.
I motivated my 13-year old daughter Courtney to learn programming with this book (with
the help of President Obamas video). She is the first reader of this book. In fact, I included her
thoughts and questions in this book, as well as some of her finished code for the exercises.
I think the mistakes Courtney made and the hurdles she faced could be helpful to others.
Courtney also designed the book cover and cute illustrations for all the questions, which
entitled her the co-author of this book.
A typical how-to-program book will go through the programming concepts, syntax and
followed by demonstrations with simple examples. I have read dozens of them (for different
programming languages or tools) before and have taught this way at universities. It was not
an effective approach. It is more like a teacher dumping knowledge upon students. But I did
not know a better way, until I discovered The Michel Thomas Method.
http://www.michelthomas.com/
Preface iii
The Michel Thomas Method is developed by Michel Thomas for teaching foreign languages.
Thomas claimed that his students could achieve in three days what is not achieved in two to
three years at any college. My understanding of this method is that the teacher starts with
a simple conversation scenario, then gradually expands the scenario with a few new words
each time. That way, students are familiar with the conversation topic and the majority of
words or sentences, while learning some new, in real interesting conversations.
I believe this teaching method can be applied to programming. Not only a programming
language may also be considered as a language, but also very practical. The conversation
in speaking languages are exercises in programming. People learn better when they get
satisfaction or feedbacks and see their programs running.
As I said before, thinking in programming is much more important than being familiar with
a programming language. There is no better way than writing real programs for practical
exercises. In this book, I have chosen the exercises that are very simple to understand, besides
teaching values, they are useful and fun to do.
Besides programming tutorial books, there are also programming quiz books. I often find
some of those exercises are long and hard to understand. Quite commonly, the authors seem
to be fond of showing off their programming skills or smart solutions. It wont be the case in
this book. This book is a guide to programming and its purpose is to teach. After you finish all
the exercises, you will be able to write working programs, and with confidence to continue
to learn and grow.
In Chapter 11 (Automation), I will show what you have learnt may lead you to a promising
career: test automation engineer for web applications. Web applications are the main stream
nowadays. Due to its nature of rapid changes and multi-browser support, automated testing is
on demand. However, very few possess the skill. Programming + Automated Testing skills are
highly valued in software companies like Facebook: All Facebook engineers are responsible
for writing automated tests for their code.
Everyone, for whatever reasons: job needs, career change, writing apps or games, or simply
to better understand how a computer program works.
In particular, I would strongly encourage young people to give it a go.
http://www.theinquirer.net/inquirer/news/1720797/facebook-qa-team
Preface iv
It is highly recommended to read this book from page to page. The exercises are organized
into chapters, exercises within each chapter generally follows an easy-to-hard pattern.
The solutions for all exercises are also available on the books website.
Send me feedback
Wed appreciate your comments, suggestions, reports on errors in the book and code. You
may submit your feedback on the books site.
Zhimin Zhan and Courtney Zhan
November 2014
http://zhimin.com/books/learn-ruby-programming-by-examples
1. Introduction
I still remember my first programming lesson. The first sentence the coach said was
computers are not mysterious. Nobody uses the term mysterious to describe computers
nowadays. It was the case in 1980s, computers were rare back then.
We are in the Information Age now, computers are a large part of our lives. It seems to me
that programming remains mysterious and difficult to the majority of people despite the fact
that they spend most of their working hours in front of computers.
Once you have mastered programming, there are many things you can do, such as:
Instantly rename hundreds of file with a script instead of doing it one by one
Generate a nice Excel report from raw data instead of typing it in
Write a document once and use scripts to generate several different formats: HTML
(online) and PDF
Turn on or off certain electronic devices when a certain condition is met
Write a cool iOS or Android App
Develop a web application
The bottom line is that when you know how software works you will definitely use computers
better.
Before we start, just like my coach, I am telling you that programming is not mysterious
and you can master it. I believe this book can guide you to the wonderful programming
world.
Like many skills, you cannot master programming by reading the book only, you need to do
it. Lets begin.
First, we need install Ruby. Download the Ruby Installer for Windows (the latest version to
date is Ruby 2.1.3) and run it. Tick the Add Ruby executables to your PATH checkbox on
the installation dialog window (accept the defaults for the rest).
http://rubyinstaller.org/downloads
1
Introduction 2
The best way to interact with your programs is from the command line. You might have seen
these scenes in some Hollywood movies: a hacker types some commands in a window, and
something (usually big) happens. The windows that accept user commands (without using a
mouse) are called consoles or command windows. To start a command prompt on Windows
platform, press Start All Programs Accessories Command Prompt.
If you get the output like the above, that means the Ruby is installed successfully, and is ready
to use.
Text Editor is a tool for editing texts (e.g. NotePad). As all the code is in text format, we may
use NotePad to write our code in, however, that would not be effective.
Commercial
Free
SciTE. A free and lightweight programmers editor. There are several different
packages, the easiest one is probably the windows installer (scite-3.3.9x64.msi around
2.7MB).
http://www.sublimetext.com/
http://www.scintilla.org/SciTE.html
Introduction 4
I suggest creating a dedicated folder to put all your code in, for example, C:\Users\you\rubycode.
Open your editor (I use the free SciTE for illustration), and type in puts "Hello World!".
puts writes the followed text to the screen.
Execution
To run our program, open a command prompt and change the directory to the rubycode
folder. Type in the command ruby helloworld.rb.
> cd rubycode
> ruby helloworld.rb
Introduction 5
The application to access the command line in Mac OS X is called Terminal. Open in Finder:
Applications Utilities Terminal.
The ruby version number might be different on your machine, this wont matter.
Introduction 6
Commercial
TextMate. Its was called the editor of Mac and won the Apple Design Award for
best developer tool in 2006. It is very popular among Ruby programmers. Cost: 39.
Sublime Text. Sublime Text is inspired by TextMate. They are quite similar in many
ways. Cost: $70.
TextWrangler.
I suggest creating a dedicated folder to put all your code in, for example, /Users/YOURUSER-
NAME/rubycode.
Start your editor, I would recommend TextMate, but for now (before you decide to purchase
TextMate) I would use the free alternative TextWrangler. Open TextWrangler and type in
puts "Hello World!". puts writes the followed text to the screen.
http://macromates.com/
http://www.sublimetext.com/
http://www.barebones.com/products/textwrangler/
Introduction 7
Execution
To run our program. Open a Terminal and change the directory to the rubycode folder. Type
in the command ruby helloworld.rb.
$ cd rubycode
$ ruby helloworld.rb
(cd means change directory; ruby filename means running this ruby file.)
You will see the output:
Hello World!
While I believe you can learn basic Ruby programming with this book, there are online
tutorials that you may use as supplements. For example, read them on your iPad while
waiting at bus stops. Here are two good (and free) ones.
Ruby in Twenty Minutes is the official Ruby tutorial. As its name suggests, it takes only
about 20 minutes to go through.
Codecademy is a website offers free interactive coding courses. One of them is Introduction
to Ruby. Beside explaining concepts, the course also has simple exercises that you can edit
and submit code.
https://www.ruby-lang.org/en/documentation/quickstart
http://www.codecademy.com
Introduction 8
The problem to solve. It usually comes with sample output for easier understanding.
Make sure you understand it well.
Purpose. What you can learn from this exercise.
Analyse. Analyze a problem like a programmer. This is an important step. Quite often
we know how to do it but cannot describe it well. Take number sorting as an example;
you can sort 5 numbers instantly on top of your head. But how about 100 numbers? This
requires you to translate your understanding of sorting step by step into procedures
that a computer can execute.
Now write the code for the exercise. No one can learn programming by reading, you have
to actually do it. You may refer to the hints section to to help you get through.
Hints. I list the hints (in Ruby code format) that may help you to solve the problem
when you get stuck.
If you are struggling to solve an exercise, feel free to check out our solutions (at Appendix II).
The exercises are selected to introduce new programming skills/practices as well as previous
knowledge. So dont worry if you cannot get it right the first time, you will have chances to
apply in later exercises. As long as you are trying, you are learning.
Introduction 9
Solution(s). Solutions (can be found at Appendix II) to the most of exercises are
between 10 to 20 lines of code. I may show Courtneys solution first with her
comments. The runnable solution scripts can be downloaded at the book site.
Review. Think about what you have learnt.
To make it easier for you to write and run your code, I suggest you opening 3 windows as
below:
Code Editor on the left, where you edit the code. (I used free SciTE in the screenshot
below).
A Window explorer with the rubycode folder opened.
A Command Prompt (or Terminal on Mac or Linux) window with current directory is
set to rubycode folder (execute the command cd c:\Users\you\rubycode)
Here are the steps to write and run a new program (new_code.rb).
Introduction 10
1. In Window Explorer window, right click and create a new text file and rename it to
new_code.rb. It is important to change the file extension to .rb. On Windows, the
file extension is hidden in Windows Explorer by default. To change a file extension in
Window Explorer, we need to change this setting (to show file extension). Here are the
steps for Windows 7.
In a Window Explorer window, select Organize Folder and search options
Under View tab, uncheck the Hide extensions for known file types checkbox
3. Type and edit the code in the editor. Save when it is ready to run.
4. In Command Prompt window, type
ruby new_code.rb
Programmers (new or experienced) encounter code errors every day. I dont expect you to
get the exercises right on the first go. We learn from mistakes.
Syntax Error
Ruby checks the syntax of code before running it. If there are syntax errors in code, the error
messages are usually quite helpful for identifying the error.
Typo
It is normal that we make typing mistakes when writing code. For example, in the code below,
instead else at line 23, I typed elwe.
The error message means elwe is undefined (dont worry if this does not make sense, you
will soon understand). The more helpful part in the error trace is the line number 23 (the first
line number next to your code file) . It helps identify where the error is.
Introduction 12
Just like Math, if there is a left bracket ( in code, there shall be a matching right bracket ).
There are also matching keywords for certain code structures, such as if end. For example,
there are two errors in the code below.
When you run the program, the error message expecting ) is correct.
However, the line number 27 is not where the actual error is. That is because it is not possible
for Ruby to detect all error scenarios. If the right bracket is on the next line, the program is
valid. Only after line 27, Ruby detects the right bracket is actually missing.
After fixing the first error and rerunning the program, a second error occurs. Again, the error
line number is not exactly where the real error is.
Runtime error
A runtime error is a software problem that prevents a program from working correctly.
When a runtime error occurs, the execution of the program terminates with error messages
displayed.
Introduction 13
int a = 5
puts("OK so far")
b = 200 / (a - 5)
The above code runs with an error thrown at line 3, here is the output.
OK so far
runtime_error.rb:3:in `/': divided by 0 (ZeroDivisionError)
divided by 0 (ZeroDivisionError)
The above two kinds of errors are relatively easy to spot. The difficult errors for programmers
are code logic errors. That is, the code can run without syntax errors, but does not behave as
it is supposed to. Here is an example.
The above code reads a users exam score and gives the grade: Pass or Fail. It runs fine,
most of time, except when the score is 60. There is a code logic error on line 3, it shall be if
score >= 60.
The ability to debug code errors (find and fix them) separates good programmers from the
average. With practice, you will get better on this.
For beginners, I have two practical tips.
1. One step at a time. Write one line of code, run the code immediately. This may sound
uninteresting, but in practice, many find it is the most useful tip to learn programming.
If newly added or changed code fragment caused the error, a click of Undo button (in
your editor) will get back your code to previous working state.
Introduction 14
2. If feeling confused, restart. If you stuck with existing code, chances are the complex-
ity of the code is beyond your control. Try guessing around to get computers to work
as instructed (by your code) is highly unlikely. In this case, it is better to restart from
scratch. For most of exercises in this book, solutions are less than 20 lines of code.
IRB is a tool that allows the execution of Ruby commands with immediate response, which
can be very helpful for learning Ruby syntaxes and debugging code errors. IRB comes with
Ruby and it is launched from a command line. To run IRB, just run irb from a command line
window then try ruby code there.
In the screenshot above, the commands in the green boxes are what I entered. The rest were
the responses returned from the commands.
In Appendix 1 (Ruby in Nutshell) I summarized the core Ruby syntax and usage in examples
which you can conveniently run in IRB.
2. Printing Shapes
Printing out asterisk characters (*) in shapes is often used as beginners programming
exercises, as they are simple, easy to understand and visually interesting.
Write a program to print out asterisks like the triangle shape below:
*
**
***
****
*****
******
*******
********
*********
**********
Purpose
Analyse
15
Printing Shapes 16
Hints
puts '*'
puts "**"
For small ruby code fragments, you can try them out quickly in IRB.
Because Maths times operator is easy to get confused with the letter x in code , * is
commonly used as the multiplication operator in most programming languages.
Code Comment
Comments are annotations used in code for human to read, computers will ignore
them. In Ruby, statements after # are comments.
Besides writing down your notes in code as comments, you may also comment
out some code fragments when you are not sure to remove them yet.
star_count = 2
puts '*' * star_count # => '**'
Variables
You can think of a variable is a labeled box in computers to store data, its
data can be changed. The naming convention for Ruby variables is in lower case
separated b underscores, for example, my_birth_date.
Print out the same text multiple times in a loop (fixed number of times)
5.times do
puts '*'
end
Courtneys version
count = 0
10.times do
count = count + 1
stars = "*" * count
puts stars
end
Printing Shapes 19
Write a program that prints out half of the diamond shape using asterisks.
*
**
***
****
*****
******
*******
********
*******
******
*****
****
***
**
*
Purpose
Analyse
The key to this problem is to determine the number of stars for the corresponding rows.
Hints
score = 75
if score < 60
puts("Failed!")
else
puts("Pass!")
end
Output:
Pass!
If you change the score = 59 and run again, you will get Failed!.
Boolean condition
The statement score < 60 after if is called a boolean condition. Its value can only be either
true or false (which are called boolean values).
== equal to
!= not equal to
< less than
<= less than or equal to
> greater than
>= greater than or equal to
Examples:
a = 1 + 2 # assign 3 to a
Please note the assignment operator is different from the equality symbol in
Math. For example, the statement below increases the value of a by 1 (assign a
new value to a) in programming code. The same equation in Math is invalid.
a = a + 1 # increment a by 1
The double equal signs (==) is a comparison operator, it compares two values for
equality (returns true if a is equal to b, false otherwise).
if a == b
puts "Found a match!"
end
http://www.cprogramming.com/tutorial/common.html
Printing Shapes 22
Courtneys version
count = 0
8.times do
count += 1 # this is equivalent to count = count + 1
stars = "*" * count
puts stars
end
count = 10
8.times do
count -= 1
stars = "*" * count
puts stars
end
Courtneys version loops 16 times (8 + 8), but prints out OK (15 lines). This is because when
count is decrement to 0, an empty line is printed out instead.
Courtney uses two loops, which is fine and quite logical for beginners.
Printing Shapes 23
*
***
*****
*******
*****
***
*
Purpose
Analyse
Below are formulas to calculate the number of star; where row_number represents the row
number and total_rows represents the total number of rows,
1. The number of stars for the rows before the middle one is (row_number - 1) * 2 +
1.
2. the number of stars for the rows after the middle one is (total_rows - row_number)
* 2 + 1
Think about the spaces in front of each row, except for the 4th row (the longest middle one).
Hints
Write down the number of spaces and stars for each row.
Printing Shapes 24
If you have difficulty, do it step by step. You may try to print out the top triangle
first.
Printing Shapes 25
Courtneys version
if row < 4
space_count -= 1
star_count = row * 2 + 1
print space * space_count
else
space_count += 1
star_count = (7 - 1 - row) * 2 + 1
print space * space_count
end
puts '*' * star_count
end
Courtney says:
I was stuck on the number of stars and number of spaces. I had to sit down
with dad to work out the math formula. Also, the multiple variables makes it
confusing. So remember to name variables properly and meaningfully. If you are
stuck, you can print the variable you think may be the problem. This can help
you understand what is going on and how to fix it.
Courtney uses variable space to represent a space string, which is a good practice.
Printing Shapes 26
Ask the user for the size of diamond (based on the total number of rows) and then print out
a diamond shape using asterisks *.
Purpose
Analyse
The size of the diamond is not fixed, it depends on the number the user entered. The number
the program asks the user to enter is the total number of rows, which can be stored in an
variable.
If you divide the diamond into two parts (top and bottom), work out the number of rows for
each part.
Hints
The above gets accepts a line of text from keyboard and assigns the string typed and a \n
character to variable user_input. \n is a new line character, which is added when the user
press Enter key. Quite often, we want to remove this \n immediately. Here is code to do
that:
user_input = gets.chomp
a = "12"
b = "3"
a + b # => "123"
c = 12
d = 3
c + d # => 15
8 / 2 # => 4
9 / 2 # => 4, ignore the remainder
(1 + 2) * 3 + 3 / 2 # => 10
Printing Shapes 29
Courtneys version
size.times do |row|
if row < (size / 2 + 1)
space_count -= 1
star_count = row * 2 + 1
print space * space_count
else
space_count += 1
star_count = (size - 1 - row) * 2 + 1
print space * space_count
end
puts '*' * star_count
end
Courtney says:
When I first tried replacing diamond size with user input variable, I forgot to
change the variables and that made the outcome completely different. It is not
hard to fix it though.
2.5 Exercises
Write code to print out the shapes below, the width of shape is changeable.
Rhombus
*****
*****
*****
*****
*****
Hollow Square
*****
* *
* *
* *
*****
Heart
***** *****
******* *******
********* *********
*******************
*****************
***************
*************
***********
*********
*******
*****
***
*
Hints
Write a simple calculator that adds two integers (up to 99) from user inputs and
prints out the sum.
Purpose
Analyse
An adding operation requires two inputs which we need to collect from the user. The user-
inputted number must be stored (in variables) before we can add them up and output the
sum.
Hints
31
Quiz Time 32
name = "Courtney"
age = 13
puts "My name is " + name # OK
puts "My age is " + age # Error!
puts "My age is " + age.to_s # OK
The second statement will throw an error TypeError: no implicit conversion of Fixnum
into String. This is because Ruby detects a string plus an integer (age).
One way to overcome this problem is to embed the variables within a double quoted string
using #{variable}.
name = "Courtney"
age = 13
puts("Hello, My name is #{name}, I am #{age} years old.")
Output:
1 + 1 = (enter 2)
Correct.
2 + 7 = (enter 8)
Wrong!
...
6 + 3 =
Purpose
Analyse
To get a score, you will need to prepare a counter. If the users input is correct, increment the
counter by 1.
Quiz Time 34
Hints
Stop program
Press Ctrl+C to terminate the programs execution.
Quiz Time 35
9 - 1 = (enter 8)
Correct.
7 - 2 = (enter 8)
Wrong!
...
Your score: 8/10
Purpose
Analyse
It might seem like the previous exercise, however, this one can be a little tricky.
Hints
Subtraction in this context is using a bigger number to minus a smaller number. The random
number generator does not guarantee the first number is bigger.
Quiz Time 36
Courtneys version
count = 0
10.times do
num1 = rand(10)
num2 = rand(10)
if num1 > num2
print "#{num1} - #{num2} = "
answer = num1 - num2
else
print "#{num2} - #{num1} = "
answer = num2 - num1
end
input = gets.chomp.to_i
if answer == input
puts "Correct!"
count += 1
else
puts "Wrong!"
end
end
puts "Your score is #{count}/10"
Courtney says:
I didnt get it right first time as I forgot to change the answer. If you want to
change one thing you may have to change others because they affect each other.
This caused some confusion, where the answer was not matching the question. Because the
two numbers are randomly generated and the answer is only correct when the first number is
bigger, otherwise the answer will be a negative number. This kind of errors in programming
are called intermittent errors, which are hard to discover.
Also, the count increment statement works but is not in a good format.
The computer has a secret number (0 to 9), the program prompts the user to enter
a guess and give feedback such as too big or too small. The program ends when
a correct answer is entered.
Purpose
Infinity looping
Exit program or looping
Analyse
Different from the previous exercises, the number of times the computer prompts is
nondeterministic. The program ends when the player answered correctly.
Hints
Infinity loop
An infinity loop is a sequence of code loops endlessly.
while true
# ...
end
Quiz Time 39
(source Wikipedia)
# ...
break # ends the current loop
# ...
exit
For example,
http://en.wikipedia.org/wiki/Infinite_Loop_%28street%29
Quiz Time 40
while true {
# ... calculate result
if result > 60 {
puts("You passed")
break
}
}
puts "made here"
Quiz Time 41
Courtneys version
puts "I have a secret number (0-9) Can you guess it?"
count = 0
input = nil # nil means no value
answer = rand(10)
until input == answer
input = gets.chomp.to_i
count += 1
if input > answer
puts "TOO BIG"
elsif input < answer
puts "too small"
else
puts "CORRECT"
end
end
puts " The number is : #{answer}. and you guessed #{count} times!!"
Courtney says:
I had trouble with this one. I had two inputs which made one of them work and
the other not. I had also confused the loop with the if and that changed the whole
thing.
Courtney actually got stuck, probably due to doing it in a rush (so she could play her video
game). After I asked her to restart from scratch, she got it right.
Here is what she meant two inputs:
input = gets.chomp.to_i
until input == answer
input = gets.chomp.to_i
# ...
end
The first input = gets.chomp.to_i is what she used to have at the beginning of program (a
habit). Then she realized that the program needs to ask for the users input again, therefore she
Quiz Time 42
added the second input = gets.chomp.to_i in the loop. The program reads an unnecessary
user input, which confused her. Removing the first line input = gets.chomp.to_i got the
following error:
Not wanting to change her code, I hinted that the input variable can be set to nil at the
beginning.
Courtney used until instead the infinity looping while (suggested in the hint), which is fine.
Quiz Time 43
3.5 Exercises
Hangman
Write a program to guess a word by trying to guess the individual characters. The word to
be guessed shall be provided using the command-line argument.
Your program shall look like:
Hints
If you are not familiar with Array, come to to do this exercise after the next chapter.
4. Array and Hash
In previous exercises, we used simple data types such as Strings and Integers (called Fixnum in
ruby). There are composite data types that are a collection of other types. The most common
composite data types are Array and Hash. An array is like a list and a hash is like a dictionary-
like lookup.
In this chapter, we will write some programs using Array and Hash. The Ruby official
documentation has good explanation on them and all their methods are listed, even with
easy to understand examples.
Ask a list of names and then output them in alphabetical order. The program
ends when the user enters 0.
Kids in order:
Angela, Anna, Courtney, Dominic, Ella, Emma, Toby
http://www.ruby-doc.org/core-2.1.0/Array.html
http://www.ruby-doc.org/core-2.1.0/Hash.html
44
Array and Hash 45
Purpose
Analyse
Hints
Sorting an array.
Asks the user to enter a number between 1 to 26, and then print the character at
the alphabetical position. The program ends when the user enters 0.
I know the alphabet very well, enter the alphabetical order number (integer)\
and I will tell you the corresponding letter, 0 to quit:
1 (user enter)
is 'A'
5 (user enter)
is 'E'
0
Bye!
Purpose
Analyse
The number of times the user can input is undetermined. So it will be an infinite loop with
an exit condition: when 0 is entered.
We can use the alphabet position to look up in a predefined array for the corresponding
character.
Hints
Ask the user to enter a set of student scores (non-negative) integers and then
calculate the average. The program ends when the user enters -1.
Average score : 80
Analyse
Calculation of the average is easy (see the hints below) by using existing Rubys array
functions.
Purpose
Hints
Try to do the sum first and then calculate the average. There are two ways to sum all elements
in array:
Array and Hash 49
Courtneys version
array = []
count = 0
puts "Enter scores: "
while true
input = gets.chomp.to_i
if input == -1
break
else
array << input
count += 1
end
end
sum = 0
array.each do |number|
sum += number
end
average = sum / count
Purpose
Analyse
Hints
"ABC".split("").each do |ch|
# process ch as a character
end
# define a Hash
cities_abbrevs = { "LA" => "Los Angeles", "NY" => "New York" }
# look up in a Hash
cities_abbrevs["NY"] # => "New York"
cities_abbrevs["SF"] # => nil, no entry matching the key "SF"
Here is another Hash example: data type of keys and values are different (string integer).
Courtneys version
array = []
hash = { "A" => 1,"B" => 2,"C" => 3, "D" => 4, "E" => 5, "F" => 6, "G" => 7\
, "H" => 8, "I" => 9, "J" => 10, "K" => 11, "L" => 12, "M" => 13, "N" => 14,\
"O" => 15, "P" => 16, "Q" => 17, "R" => 18, "S" => 19, "T" => 20, "U" => 21\
, "V" => 22, "W" => 23, "X" => 24,"Y" => 25, "Z" => 26}
puts "Enter word in capitals"
input = gets.chomp
input.split("").each do |character|
array << hash[character]
end
Courtney says:
I found this one very difficult. After the users input is added, the loops make it
harder and I didnt realize that I needed to use the array until later. I also found
the hashes difficult and hard to use. Hashes are used to look up a key to find the
value (key value). Sometimes it helps to write what you are going to do on
paper then work it out using ruby.
Courtney got stuck here. After some hints and explanation, especially showing how to do it
manually on paper, she managed to get it done; but it took quite long time (45 minutes).
Array and Hash 54
4.5 Exercises
The Median is the middle of a sorted list of numbers. For example, the median of the data
set 1, 1, 2, 5, 6, 6, 9 is 5. If there is an even number of data values the median is the mean of
the two data values in the middle. For example, the median of the data set 4, 2, 1, 3, 5, 4 is 3.
It is the mean of 3 and 4 or, (3 + 4) /2 = 3.5.
Write a program to find the median of a list of numbers. The output shall be like this:
Hints
Can you write a program for Julie to convert Fahrenheit to Celsius? The result is rounded to
2 digits after the decimal point.
Purpose
Analyse
This is just simple calculation based on a given formula. So far our calculations are using
integers only. This one has floating numbers, which are decimal numbers.
Hints
55
Useful Utility Programs 56
"34.123".to_f
100 / 3 # => 33
Rounding.
Courtney says:
I initially used the inputs= gets.chomp.to_i at first, the output was 37.0 instead
of 37.46. I tried printing out the input after entering 100.5 (for debugging), the
number returned was 100! Changing to_i to to_f corrected it.
Useful Utility Programs 57
Here are Australian personal income tax rates for 2013-14 financial year.
Write a program to calculate how much tax an Australian needs to pay based on his or her
annual salary.
Analyse
The calculation is easy. The key here is to compare the user entered amount against thresholds
(note, I used plurals here).
Purpose
Complete this exercise using if, else if and else first, then change to switch
statement. Review the difference.
Useful Utility Programs 58
Hints
IF-ELSE IF
The branching concept is simple and self-explanatory.
a = 10
if a < 10
puts "single digit"
elsif a >= 100
puts "too big, I don't know"
else
puts "two digits"
end
The statements after if and elsif are conditions. Their value can only be either true or
false (which are called boolean values).
Logical operators
&& AND
|| OR
Example:
a = 10
if a > 0 && a < 10
puts "#{a} is a positive single digit number"
end
CASE-WHEN
When dealing with a large number of possible conditions, case statement is better than if-
else in terms of code clarity.
Useful Utility Programs 59
score = 70
Emily wrote a book review, but she is not sure whether it meets the required
word count. Assuming Emilys article is already saved in a string, can you write
a program to count the number of words?
(assuming the text is Practical Web Test Automation book is great. The end. and set in the
code)
Purpose
Analyse
Words are separated by spaces (we will use this rule for the exercise). Counting how many
spaces is not a good idea, as two consecutive spaces shall be counted as one in this case.
One way is to use tokenization, splitting the text into tokens by whitespace characters.
Hints
Use split method to split a string into an array with a given separator.
http://en.wikipedia.org/wiki/Tokenization
Useful Utility Programs 61
Courtney says:
I found this one rather easy, except I did not name my variables properly and had
to rename them.
Useful Utility Programs 62
Write a program to generate 6 Lotto numbers. Lotto numbers are between 1 and
49.
Your winning lotto numbers are [23, 34, 12, 6, 7, 49], good luck!
Purpose
Analyse
Obviously, we need the lotto numbers to be randomly generated, and they have to be unique.
We can use rand(). However, the random number generator may generate duplicate ones.
Here is one way. Lets say, you are the picker for the lotto and have a number of buckets.
Each bucket contains 49 individually numbered balls (from 1 to 49).
1. You go to the first bucket, close your eyes, pick up one ball and put on the table.
2. Go to the second bucket, close eyes, pick up another ball.
3. Before you putting it on the table, open your eyes, if this number was already on the
table, you put the ball back to the second bucket.
4. Repeat the step 2 again until find the number is not already on the table.
5. Repeat 4 more times, then you get all 6 unique lotto numbers.
Hints
Check whether an array contains one element, i.e. check the uniqueness.
Useful Utility Programs 63
Analyse
Imagine you have 5 coins with different values: 10c , 20c, 50c, $1, and $2. How do you sort
the coins (from small to big)? I know it is obvious to human. But by now, you probably know
that we need to translate our solution into steps that computers can understand.
Lets get back to our coin sorting problem. There are 5 coins on the table. You pick up the first
coin (from left to right) in your left hand, then compare it to the rest one by one (by picking
it up in your right hand). If the one in your left hand is smaller, put back the the coin in your
right hand and continue. If the one in your left hand is bigger, swap with the one in your
right hand. Basically, after each comparison, make sure the one in your left hand is smaller.
After one iteration, the one in your left hand is the smallest. Put this one aside on a cloth,
now there shall be 4 coins left on the table. Then starting another iteration, when done, put
the coin (in left hand) on the right side of the last coin on the cloth. Repeat until only one
coin left. The coins on the cloth are sorted (from small to large).
Here is the pseudocode for the above:
Useful Utility Programs 65
Purpose
Iterating an array
Swapping variables
Access elements in an array using index
Looping an array from specific index range
Hints
Swapping variables
To swap two variables, it is traditionally (in most programming languages) done via a third
variable:
c = a
a = b
b = c
This is a common pattern and to help me remember, I used the phrase ca-ab-bc.
In Ruby, it can be done much neater:
a, b = b, a
5.6 Exercises
Write a program to convert an input decimal string into its equivalent hexadecimal. Your
output shall look like:
Hints
Decimal to Hex
Convert the remains of the above based on all hex numbers ['0', '1', '2', '3', '4',
'5', '6', '7,' '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'], in reversing order.
1 => 1
10 => A
12 => C
Write a program to convert an input hexadecimal string into its equivalent decimal number.
Your output shall look like:
Hexadecimal : 1a
The equivalent decimal number for hexadecimal "1a" is 26
Hints
2 ** 3 # => 8
5 ** 3 # => 125
Hex to Decimal
ABC(2) DEF(3)
GHI(4) JKL(5) MNO(6)
PQRS(7) TUV(8) WXYZ(9)
Write a program thats prompts user for a String (case insensitive), and converts to a sequence
of Keypad digits.
Analyse
To determine whether a number is divisible by another, check the remainder of the division.
0 means divisible.
Purpose
Hints
69
Fun Math 70
8 % 3 # => 2
9 % 3 # => 0
Fun Math 71
Write a program to ask the user to enter two non-negative integers and then find
the highest common factor (HCF).
8 = 1 x 8 = 2 x 4
12 = 1 x 12 = 2 x 6 = 3 x 4
Purpose
Iterate an array
Check whether an element is in an array?
Use break to terminate a loop
Analyse
There are several methods to compute the HCF. I am going to focus on a simple way. As a
matter of fact, this method has the most steps but it is the easiest one to understand.
1. Compute all divisors of the first number and note them down, e.g. 8 => [1, 2, 4,
8]
Fun Math 72
2. Compute all divisors of the second number and note them down, e.g, 12 => [1, 2,
3, 4, 6, 12]
3. Find the largest one in both divisors list: Starting with one, work you way down, and
check whether the divisor is also in the second. The first divisor found in both divisors
list is HCF (check divisor from large to small).
Hints
divisors_list_1 = [1, 2, 4, 8]
divisors_list_2 = [1, 2, 4, 8, 12]
Iterate (i.e, go through one by one) one array (divisors_list_1). For each element in this
array (divisors_list_1), check whether the other array (divisors_list_2) contains the
divisor. The first divisor found in both arrays is HCF.
Fun Math 73
array = [1, 2, 3, 6]
array.each do |elem|
# process the elem.
end
array = [1, 2, 3, 6]
array.include?(2) # => true
array.include?(7) # => false
Fun Math 74
Courtneys version
Courtney says:
I found this one very hard as I didnt quite understand how to achieve my goal. At
first I made my code very confusing and ended up having it deleted and started
from scratch. This happened twice. On the first attempt, I was unsure how to
get to the HCF and on the second attempt I didnt do the main part of the code.
Instead, the outer part (like puts) made it hard to continue. To make a ruby code
you must first understand the objective: work on the main part, then do the other
bits.
divisors_list_1 = []
divisors_list_2 = []
puts "Enter first number: "
num1 = gets.chomp.to_i
(1..num1).each do |x|
check = num1 % x
if check == 0
divisors_list_1 << x
end
end
puts "Enter second number: "
num2 = gets.chomp.to_i
(1..num2).each do |x|
check = num2 % x
if check == 0
divisors_list_2 << x
end
end
d1sorted = divisors_list_1.sort.reverse
d1sorted.each do |elem|
# puts "elem = #{elem}"
if divisors_list_2.include?(elem)
puts "The HCF is #{elem}"
break
Fun Math 75
end
end
Fun Math 76
Least (also sometimes called Lowest) common multiple is the smallest (non-zero)
number that is a multiple of two or more examples. For example, 12 is the LCM
of 6 and 4, as 12 = 6 x 2 and 12 = 3 x 4. 24 is also a common multiple of 4 and 6,
but is not the lowest.
Purpose
Analyse
A simple way is to search the number from the larger one of the two to the multiple of the
two. For example,
(6..6*4).each do |n|
# check whether n is LCM?
end
The above brute-force way is not optimal. Obviously, it will take longer due to too many
looping (a * b) when the two numbers are big.
One of the most effective way to speed up your program is to reduce the number of loops.
For this example, it is not necessary to check each number from 6, 7 , 8, 9, 10, 11, 12, , 24.
We just need to check every multiple of 6, i.e, 6, 12, 18, 24.
Fun Math 77
Hints
(6..24).step(6) do |n|
puts n
end
# will print out 6, 12, 18, 24
start_time = Time.now
# ... the code
puts "Calculation took #{Time.now - start_time} seconds"
if the output of the above code like 4.2e-05 seconds, this means the time is too
short to be meaningful.
Fun Math 78
Courtneys version
At first I wrote that the LCM was divisible by num1 OR num2. This was wrong because this
made 6 the LCM of 6 and 12. Instead of using or (||), you need to use and (&&).
When working with the lowest number first, it took much longer to generate the LCM. So I
had to make the higher number always first, and it was quicker.
Prime Number
A prime number is a number that is bigger than one and has no divisors other
than 1 and itself. For example, 5 is prime, since no number except 1 and 5 divides
it. On the other hand, 6 is not a prime (it is a composite), since 6 = 2 x 3.
Analyse
Make sure you completely understand the logic to determine a prime number before coding.
Fun Math 80
Purpose
Ranges in Ruby
Use a flag variable to store a status
Hints
Ruby Range is a set of values within a specified begin and end. For example, (1..4) contains
1, 2, 3 and 4.
(2..5).each do |x|
puts x
end
# will print 2, 3, 4, 5 in four lines.
is_composite = false
[2,3,4,5,6,7].each do |num|
if 8 % num == 0
is_composite = true
break
end
end
if is_composite
puts "8 is a composite number"
end
In above code, the variable is_composite is a flag. It was initially set to false and its value
may be changed by the following computation code. So later we can use its value to determine
the prime numbers.
Fun Math 81
Courtneys version
Courtney learned prime numbers a few year ago in grade 4. However I dont think her
understanding of prime numbers is good enough to program.
I didnt include the hint of using flag (kind of my fault, now added)
While she understands the ruby ranges and check divisible by using remainder, putting
everything she learnt together still requires practice.
She started with outside loop (2..20) first, which caused confusion when issues came
up
After going through manual steps and further clarification, she managed to get it right.
array = []
(2..20).each do |num|
# check one number is a prime number or not
flag = true
(2..num - 1).each do |x| # try to check each possible divisor
if num % x == 0
flag = false # mark this has divisor
break # no point to check more - composite, moves on to next
end
end
if flag == true # the number has no divisors
array << num # add to prime number list
end
end
puts "Prime numbers (up to 20) are : #{array.join(', ')}"
(2..20).each do |num|
#...
end
This is not optimal. The key to this problem is to determine whether a number is
a prime number or not. The outside loop checking 2..20 is just an icing on the
cake. The correct order for programming this problem is
By working on the core problem, you will have a better focus. Once you sort out
the core, it is very easy to add the outside loop.
On the other hand, the outside loop and other non-core code might complicate
the issue during programming, especially to inexperienced programmers. Thats
what happened to Courtney. Again, it takes time and practice. Keep coding and
thinking to improve, you will get better on this.
Fun Math 83
Start with a pair of rabbits (one male and one female) born on January 1. Assume
that all months are of equal length and that :
1. the first pair of rabbits reproduced two months after their own birth;
2. after reaching the age of two months, each pair produces a mixed pair, (one male, one
female), and then another mixed pair each month thereafter; and
3. no rabbit dies.
How many pairs will there be in the end of each month of first year?
Purpose
Analyse
Clearly, there is a pattern on the number of rabbit pairs. To find out a pattern, we need to
work out the numbers in first several months, like below.
Fun Math 84
Fibonacci sequence
This is the famous Fibonacci sequence. If you read the book Da Vinci Code,
you might remember fibonacci sequence was used as the password for a safe.
Each new number in Fibonacci sequence is generated by adding the previous two
numbers. By starting with 1 and 1, the first 10 numbers will be:
1, 1, 2, 3, 5, 8, 12, 21, 34, 55
Hints
num1 = 1
num2 = 1
The algorithm behind this program might seem simple: just produce a number that is the
sum of the previous two. This will test your understanding of variable assignments. In
programming, x = 1 is not x equals to 1, instead means assign 1 to x. Thats why we
can do number increment using a = a + 1, which does not make sense in Math.
Fun Math 85
a = 10
a = a + 1 # now a = 11
The program seems simple with only a few lines of code, however, it is not as simple as you
might think. If you get stuck, think about assigning variables to a new number.
Also, after generating a correct Fibonacci sequence, you will need to think about stopping at
the 12th number (12 months).
Fun Math 86
Enter a number: 9
9 = 2 + 3 + 4
9 = 4 + 5
Purpose
Analyse
An adding operation involves at least 2 numbers. In other words, the maximum consecutive
natural number in the question can only be (x + 1) / 2. Lets call this number y.
Then we will try to find the consecutive numbers and add them as below:
1 + 2
1 + 2 + 3
# ...
1 + 2 + 3 + ... + y
2 + 3
2 + 3 + 4
# ...
2 + 3 + 4 + ... + y
# ...
(y-1) + y
Pseudocode
Hints
We have done looping with a range before. For convenience, here is another example:
x = 10
(1..x-1).each do |i|
print "#{i}, "
end
# will print 1, 2, 3, 4, 5, 6, 7, 8, 9,
To sum a range, we can convert a range into an array by using to_a first, then use the standard
array summing.
We have used array.join(',') to output an array in a string. In fact, you can use other join
characters, such as + below.
6.7 Exercises
Compute PI
Compute with different iteration count: 1, 100, 10000 and 1000000. Compare your result with
Rubys built-in constant Math::PI. Your program shall look like:
divisors_list_1 = []
(1..num1).each do |x|
check = num1 % x
if check == 0
divisors_list_1 << x
end
end
divisors_list_2 = []
(1..num2).each do |x|
check = num2 % x
if check == 0
divisors_list_2 << x
end
end
In programming, the methods (also known as functions) removes duplication to make the
code more readable, and most importantly, easy to maintain. A simple way of thinking the
use of a method: a group of code statements that performs a task and can be reused.
A method consists of
89
Methods 90
def add(a, b)
c = a + b
return c
end
Where add is the method name, a and b are the two parameters and c is the result returned
to the caller. In Ruby, return ends the method.
Method names in Ruby are in lowercase. For long method names that have more than one
word, the convention is to join them with an underscore character, such as register_user.
It is a good idea to name your method meaningfully that reveals its purpose.
Here are two code statements calling the add method.
add(1, 5) # => 6
add(100, 500) # => 600
The above looks like math functions such as f(x) = x2 - x + 1. Yes, thats why methods are
also called functions. For example, writing this function in Ruby will look like below:
def f(x)
return x ^ 2 - x + 1
end
Write a program to ask the user to enter two non-negative integers and then
find the highest common factor (HCF). The program must define a method that
return the divisors of a number.
Methods 91
Purpose
Hints
def get_divisors(num)
# ...
end
divisors_list_1 = get_divisors(num_1)
divisors_list_2 = get_divisors(num_2)
# ...
Courtney says:
This time I still spent a long time on it because I had trouble doing the last part
again and finding out the problem (I had put an array inside another array). I
managed to find the problem by printing out the array and using .inspect. The
method was easier than the previous one. Although I had to think about what to
return.
Methods 92
Refactoring
The new code works the same way as our previous version, but it is better, right?
The formal term to describe what you have just done in software programming
is Code Refactoring. Code refactoring is the process of restructuring existing
code without changing its external behavior. To put in my simple words: work
the same outside, improved inside.
Methods 93
Write a program to generate 6 Lotto numbers. Lotto numbers are between 1 and
49. This time, define a method that returns a valid lotto number.
Your winning lotto numbers are [23, 34, 12, 6, 7, 49], good luck!
Purpose
Analyse
This time, we use a method to simplify our code logic. Our target is to get 6 random numbers
from 1 to 49. If we have a method that returns a random and not-appeared-before number,
the solution is simple: calling the method 6 times.
Hints
lotto_numbers = []
def get_next_valid_lotto_number(existing_lotto_numbers)
# randomly generate a number
# check existing_lotto_numbers whether it appeared before
# return one that satisfy, otherwise try again
end
new_valid_number = get_next_valid_lotto_number
lotto_numbers << new_valid_number
Methods 94
Purpose
Analyse
This is to calculate the LCM for multiple numbers. We have written a program to calculate
the LCM for two numbers. If we divide 15 numbers into 15 calculations of LCM for two
numbers, we get the answer.
1, 2 => LCM
LCM, 3 => LCM
LCM, 4 => LCM
...
LCM, 15 => LCM
Hints
You may start with the existing solution of Ex6-3, and refactor the code into a method
def lcm(a, b)
# ...
end
lcm(6, 8) # => 24
Write a program that reads the contents of a text file containing scores of a class
(0-100, one each line) and calculates the average score.
The content of the text file looks like the following:
84
78
...
87
Purpose
95
File and Network 96
Analyse
The student scores are stored in a text file (the file content is in a text format, i.e. recognizable
when opened in text editors. Image files such as JPEG are called binary files).
Hints
To work with a file, we must identify its path. For example, c:\Users\you\rubycode\score.txt
on Windows; /Users/john/work/rubycode/score.txt on Mac or Linux. A file path is referred
as a string.
The file separator for Windows \ is a special character when specified in a string. When
used in a file path, replace the single backslash with a double backslash to escape the speical
character.
wrong_file_path = "C:\Users\you\rubycode\data\score.txt"
puts wrong_file_path # "C:Usersyou\rubycodedata core.txt"
file_path = "C:\\Users\\you\\rubycode\\data\\score.txt"
puts file_path # "C:\Users\you\rubycode\data\score.txt"
The above file paths are called absolute paths. If a file is referenced this way and the program
is running on another machine, it will fail as the referenced file does not exist. The safest way
is to use relative paths, i.e. the file path is relative to your program.
file_content = File.read(file_path)
puts file_content # will print out full content
file_content = File.read(file_path)
file_content.split.each do |line|
# ...
end
File and Network 98
Write a program that reads the contents of a file and counts the number of words
and lines in that file.
Purpose
Analyse
The program is a generic utility, which means we can use the program for different text files.
Therefore, we cannot hardcode the input file path. When running a program, we can pass
arguments to the program. These arguments are called command line arguments.
Hints
first_command_line_argument = ARGV[0]
puts "The first argument is: #{first_command_line_argument}"
The file path passed to the program can either be an absolute path or a relative path to the
current directory. If it is the latter, the following code can convert it to the absolute path.
File and Network 99
For counting words (refer to chapter 5) and lines, you can use Strings split method.
File and Network 100
Jessica is planning to invite her friends to her 12th birthday party. Instead of
writing individual invitations, she wants to print them out in cards. Instead of
creating documents one by one, she wants to generate a letter document (a text
file) using the template below:
Dear {{first_name}},
Jessica.
Her friends: Pokkle, Angela, Tonpa, Toby, Biscuit, Mito, Kate, Renee, Chloe, Kelly and Melody.
Write a program to help Jessica generate multiple invitation cards as text files such as
pokkle_invitation.txt and angela_invitation.txt.
Purpose
Analyse
This is a typical mail-merge type scenario: generating a set of documents. Each document
has the same kind of information, some of the content is unique. We can create a template
where parts of the document that can be substituted.
To perform a substitution, we need
File and Network 101
the section to be replaced in the template. For example, {{first_name}} is the one for
this exercise.
the text to replace into the template, i.e. friends first names.
Hints
Text Substitution
gsub replaces certain texts in a string and returns a modified string. It does not change the
original string. To make the changes, use gsub!.
The first argument c:/work/rubycode/output_1.txt is the file path, the second argument
w indicates openning the given file for writing. To append to an existing file, use a for the
second argument.
File and Network 102
Write a program to rename the following files in a directory so that the files are
always shown in alphabetical order and without spaces.
From
to
(the directory with the above sample files can be found at sources/files/book_dir).
Purpose
Analyse
The objective is quite clear and can be divided in the following steps:
As you have mastered looping, it is a good idea to start with just renaming a single file. Once
you have completed that, then process all the files in the directory.
Hints
a_directory = "c:/books"
Dir.foreach(directory) do |item|
next if item == '.' or item == '..' # skip this and parent directory
# do something to item
end
The . and .. have special meanings when used in a file path: current directory and parent
directory respectively. For example, the three commands below
> cd c:\foo\bar\ruby
> cd ..
> ..\run.exe
are equal to
> c:\foo\run.exe
Rename files
FileUtils defines a set of utility methods to manage files such as creating a new directory,
copying and renaming files. FileUtils.mv(src, dest) method moves one file (by path),
renaming is one form of moving.
http://www.ruby-doc.org/stdlib-2.0/libdoc/fileutils/rdoc/FileUtils.html
File and Network 104
Regular expression is very powerful and it does take some time to master it well. To get it
going for simple text matching, however, is not hard. Google ruby regular expression shall
return some good tutorials, and Rubular is a helpful tool to let you try out regular expression
online.
Here is an example regression expression for this program:
where
some_int = 7 # to use
some_int.to_s.rjust(3, '0') # => '007'
some_int.to_s.rjust(5, '0') # => '00005'
File and Network 106
If you are unable to connect to Yahoo Finance (e.g. no internet connection), the program shall
exit gracefully with an error message.
Purpose
DESCRIPTION,LOGIN,PASSWORD,EXPECTED_TEXT
Valid Login,agileway,test,Login Successful!
User name not exists,nonexists,smartass,Login is not valid
Analyse
Hints
As the requirement for this program is live exchange rate, we cannot use the downloaded
exchanges rates. In other words, the program shall use the current rate directly from the
currency exchange market. The ruby code below retrieves the exchange rate from a web
address.
require 'net/http'
require 'uri'
csv_data = Net::HTTP.get(URI.parse("http://download.finance.yahoo.com/d/quot\
es.csv?s=AUDJPY=X&f=sl1d1t1ba&e=.csv"))
puts csv_data # string text of rate information in CSV format
HTTP and URI are network terms (Hyper Text Transfer Protocol and Uniform Resource
Identifier).
Exception handling
The above code only works if you are connected to Internet and Yahoo Finance server is up
running. If not, you will get an error message like below:
https://au.finance.yahoo.com/q?s=AUDJPY=X
http://download.finance.yahoo.com/d/quotes.csv?s=AUDJPY=X&f=sl1d1t1ba&e=.csv
File and Network 108
begin
# ...
rescue => e
# code to handle the exception, usually print detail first
end
# ...
begin
5 / 0
rescue => e
puts "Error occurred: #{e}!"
end
During the execution, an exception is raised and then handled. Here is the output:
Read CSV
File and Network 109
require 'csv'
csv = CSV.parse(csv_data) # a string
csv_first_row = csv.shift # shift returns first row
exchange_rate = csv_first_row[1].to_f # => 95.324
The above code reads and parses CSV data from a string directly. To read from a file:
require 'csv'
customers = CSV.read('customers.csv')
Handling CSV files (reading from and writing to) is very useful. For more information, check
out this tutorial.
http://www.sitepoint.com/guide-ruby-csv-library-part/
File and Network 110
Jessica had a birthday party and received gifts from friends. She is now going to
send each of her friends a personalized thank-you email. Instead of sending the
emails one by one, she wants to a program to send emails of each of her friends
by running a single command.
Her thank-you email looks like this:
Dear Angela,
Thank you for coming to my 12th birthday party, I really like the gift you g\
ave me: Cat Statue, thank you very much!
Jessica.
And this is a list of people (with email) and gifts she received.
Purpose
Analyse
This exercise requires the use of more complex data structure and several new concepts.
By examining the sample email, we see three differences among the emails to be sent:
Therefore, we need to design a data structure to pass all the above data (for each person) to
the template to generate one email. Think about the data structures you have used in Chapter
04: Array and Hash. We need to substitute more than one attribute (first_name and gift) in
the template.
To send an email is quite easy. This kind of generic function is either included in Ruby or
in a third party library (called RubyGems in Ruby). A modern software application uses
many third party libraries, this way, no need to reinvent the wheel. The site Ruby Toolbox
categorizes popular ruby gems including rating and other information. The good news is that
most of Ruby gems are free or charge.
https://www.ruby-toolbox.com/
File and Network 112
[email protected]
[email protected]
The emails sent to the above two addresses will arrive into [email protected],
and the To: address will keep the +extra you provided.
Hints
Hash in Array
We have learnt about Array (a data structure that stores a list of values), such as
File and Network 113
But what about grouping a set of related data with the same keys. It is easier to explain with
an example. Lets say, I want a data structure to store the capital city and the state flower of
Australian states. This data structure is different from the above, because the capital city and
the state flower are different kind of data, but they both are associated to the same key: state
name. How do we do that?
The answer is simple: group the capital city and the state flower. In Ruby, you can put a list
of Hash in an array.
au_states_info = [
{:state => "QLD", :capital => "Brisbane", :state_flower => "Cooktown Orchid\
"},
{:state => "NSW", :capital => "Sydney", :state_flower => "Waratah"},
# ...
]
To iterate au_states_info
au_states_info.each do |entry|
entry[:state] # first loop will be "QLD", next will be "NSW"
entry[:capital] # first "Brisbane", then "Sydney"
entry[:state_flower] # first "Cooktown Orchid"
end
This works, however, it seems verbose. The one command achieves the same.
The important concept here is that the output of the first operation "We scare because we
care".gsub("scare", "smile") is then applied by another gsub("care", "like") method.
This syntax is used often in coding.
Install ruby gems
Ruby gems are centrally hosted at rubygems.org. To install or update a ruby gem, you need
to be connected to Internet.
The above command (run from a command line window) will download and install latest
mail gem. The command below lists all the gems installed on your machine.
http://rubygems.org
https://github.com/mikel/mail
File and Network 115
# Gmail options
require 'mail'
options = {
:enable_starttls_auto => true,
:address => 'smtp.gmail.com',
:port => 587,
:authentication => 'plain',
:user_name => '[email protected]', # replace with yours
:password => 'secret'
}
Mail.defaults do
delivery_method :smtp, options
end
Mail.deliver do
from '[email protected]'
to '[email protected]'
subject 'Here is the stuff I mentioned'
body 'Body content goes here' # or File.read('/file/path/to/body.txt')
# optional attachments
# add_file '/full/path/to/somefile.png'
end
test_mode = true
# ...
if test_mode
# ...
else
# ...
end
if test_mode
the_email = "[email protected]"
else
the_email = "[email protected]"
end
Mail.deliver do
to the_email
# ...
end
The code works, but it seems verbose. Rubys ternary operator(also called ternary condi-
tional) is a shorthand for if ... else. The general format of this expression is as follows:
condition ? true : false
The code above can be refactored (means enhanced without changing its outcome) as below
using Ternary Condition:
File and Network 117
Mail.deliver do
to test_mode ? "[email protected]" : "[email protected]"
# ...
end
8.7 Exercises
GradesHistogram
Write a program that reads grades (between 0 and 100, integer) from a text file and display
the histogram.
For example:
49 50 51 59 0 5 9 10 15 19 50 68 55 89 100 99
Output:
0 - 9: ***
10 - 19: ***
20 - 29:
30 - 39:
40 - 49: *
50 - 59: *****
60 - 69: *
70 - 79:
80 - 89: *
90 -100: **
9. Object Oriented Programming
Ruby is an object-oriented programming language. Please bear with me if you have no idea
what object-oriented is.
I remember that it took me quite a while to understand object-oriented concept when I was
at university back in early 90s. I had no tools to try, just read theories from books. It turned
out to be quite simple if it was illustrated with examples. Here is one:
Car is a class, a type of something, it has following two functions (plus many more):
accelerate
brake
My car (the one in my garage) is an object of Car, it can do brake and accelerate. I can
physically drive it.
Now have a think about statements below:
my_car = Car.new
my_car.accelerate
your_camry = Car.new
your_camry.brake
a_string is an object of class String, and iphones is an object of class Array. Here is how
to find out an objects class.
119
Object Oriented Programming 120
The reason we are able to call .size and .sort is because these methods are defined in the
String and Array classes respectively.
Purpose
Define a class
Define methods in a class
Create a new instance (object) of a class
Invoke a method on an object
Analyse
This program is quite easy if using standard methods (Chapter 7). The purpose of this exercise
is to do it in a class, object-oriented way. If you understand the basic Ruby Class syntax and
usage (see below), this exercise is quite straightforward.
Hints
define a class
A Ruby Class name needs to in capital case, for example, BankAccount.
Object Oriented Programming 121
class BankAccount
# ...
end
class BankAccount
end
saving_account.transfer(100, cheque_account)
Object Oriented Programming 122
A school consists of teachers and students, and students are grouped by grades.
Write a program to calculate
a teachers age
the average age of teachers
the average age of Grade 10 students
students = []
students << Student.new("John Sully", "1999-10-03", 10) # 10 is grade
students << Student.new("Michael Page", "1999-05-07", 11)
students << Student.new("Anna Boyle", "1998-12-03", 10)
students << Student.new("Dominic Chan", "1999-09-10", 10)
Output:
Purpose
Instance variables
Read and write instance variables
Class Constructor
Class Inheritance
Age calculation
Use of array operations (review)
Object Oriented Programming 123
Analyse
The core function is to calculate ages, and we have two identified Classes Teacher and
Student (from the code fragment). We could write two get_age functions in both Teacher
and Student classes, however this is not correct. You wouldnt want to write another get_age
method to support another class AdminStaff.
Calculating ages (from birth date) is a common function for human. In OOP, we can move
common methods into its parent class, then its child classes inherit them. This is called
Inheritance (see hints below). For this exercise, we can create another class Person with
age method, then make Teacher and Student inherit from Person. Here is the class diagram
for the design.
After deciding the class design at high level, we turn to Class internals. Each teacher or
student has name and birthdate (needed for calculating the age). Student also shall have
a grade. These are attributes of a Class, also known as class instance variables (see hints
below).
Let me illustrate with an example:
where john_sully is an object (or instance) of class Student and james_bond is an object of
class Teacher. The data in brackets were stored in each objects attributes.
Object Oriented Programming 124
john_sully.grade # => 10
james_bond.name # => "James Bond"
The business function of this exercise is to calculate age from birth date. We all know how
to do it: comparing the birth date against todays date. However, this can be a bit tricky. See
hints for how to use Rubys built-in date helper methods.
Hints
class Person
def name
return @name # instance variable
end
end
person = Person.new
person.name # = nil
person.name = "Courtney" # => no method error
We can read the name, but couldnt assign the name. So we add a writer method.
class Person
def name
@name
end
def name=(str)
@name = str
end
end
person = Person.new
person.name = 'Dominic'
person.name # => "Dominic"
Object Oriented Programming 125
Now we can read and write instance variable @name using reader and writer methods. As this
is used very often, Ruby has a simple way:
class Person
attr_reader :name
attr_writer :name
end
class Person
attr_accessor :name
end
Class Constructor
Assuming we have a class Person with two attributes name and birth_date (defined as above
using attr_accessor), the code below creates two Person objects.
john_smith = Person.new
john_smith.name = "John Smith"
john_smith.birth_date = "2000-01-01"
mary_boyle = Person.new
mary_boyle.name = "Mary Boyle"
mary_boyle.birth_date = "1999-10-21"
Calling Class.new creates an object of the class. There is a special method inside Class to
respond to this operation: initialize. This method is also called Constructor. Except its
special meaning (returning a new object) and predefined method name initialize, the
syntax of a constructor is pretty much like a method.
Object Oriented Programming 126
class Person
attr_accessor :name, :birth_date
# constructor
def initialize(a_name, a_birth_date)
@name = a_name
@birth_date = a_birth_date
end
end
By using our own constructor, we can create person objects in a more readable form.
Class Inheritance
Inheritance is a relation between two classes. For example, seagulls and parrots are both
birds, thus they share the common features of birds.
class Bird
def fly
puts "I am flying"
end
end
seagull = Seagull.new
seagull.fly # => "I am flying"
Object Oriented Programming 127
parrot = Parrot.new
parrot.fly # => "I am flying"
parrot.speak # => "if someone teaches me"
ostrich = Ostrich.new
ostrich.fly #=> "I'd rather run"
Date calculation
Ruby has built-in date helper methods,
The above date methods such as .month are defined in Date class. Commonly, we start with
a date string (user entered), to convert a Date object:
Object Oriented Programming 128
http://www.ruby-doc.org/stdlib-2.1.1/libdoc/date/rdoc/Date.html
Object Oriented Programming 129
The sales tax in Australia is called Goods and services tax (GST) of 10%, which
is applied to most products and services. For example, in a physio clinic, medical
services such as physiotherapy is GST-free, however GST applies to pilates classes
(also a service), so is to all products sold at the clinic. All business must show GST-inclusive
prices. For example, for a $10 meal, $9.09 is net amount and $0.91 is sales tax.
Complete the program below to calculate the net amount and sales tax of product and
services.
Output:
Purpose
Class Design
Module and Mixin
Class Constructor setting default value of instance variables
Analyse
From the given code fragment, two classes Goods and ServiceItem need to be defined.
The sales tax calculation code is shared between the two classes. There are two common
approaches to achieve code reuse:
Object Oriented Programming 130
Inheritance
Mixin
We have used Inheritance in the previous exercise. Here we will be using Mixin. Before
we get to Mixin, we need to understand a new concept Module. A Module is another way to
group a set of methods and constants. Below is an example:
module A
def a
end
def b
end
end
As you can see, it is quite similar to Class in terms of syntax and structure. You may
wonder, why bother module? When a module is included in a class (Mixin), its methods are
available to the class instances, i.e., code reuse. The difference of class inheritance: Mixin is
about providing methods that you can use across multiple classes; Inheritance is more about
structure (attributes and methods).
Effective use of Mixin can make your Ruby code more flexible and concise. Popular Ruby on
Rails framework uses Mixins extensively.
For this exercise, as GST calculation is the same, it makes sense to create a module GSTCalc
containing GST-calcluation methods.
module GSTCalc
GST_RATE = 10.0
def net_amount
# ...
end
def gst
# ...
Object Oriented Programming 131
end
end
class ServiceItem
include GSTCalc
# ...
end
class Goods
include GSTCalc
# ...
end
Hints
The concept of Mixin can be quite confusing to beginners, certainly was the case
for Courtney. I would recommend writing the program in the standard OO way:
Then try to introduce Mixin to optimize the program: remove the code duplication.
Setting default value to instance variables in the constructor
Quite common, the value of certain instance variables are the same for most cases. Instead
of an parameter in the class constructor, we could set the default value. If necessary, we can
change its value via a setter method (object.attr = ).
Object Oriented Programming 132
class Student
attr_accessor :name, :is_talented
def initialize(a_name)
@name = a_name
@is_talented = false
end
end
newton = Student.new("Newton")
newton.is_talented = true
newton.is_talented #=> true
module Logging
def log(message)
puts "[#{Time.now}] [#{self.class.name}] #{message}"
end
end
class A
include Logging
end
class B
include Logging
end
class C
end
a = A.new
b = B.new
c = C.new
c.log() failed because the class C did not include the Logging module, therefore the method
log is not available. Please note that output the log message printed out class names
dynamically based on which class it is called from.
Access instance variables in module methods
Once a module is loaded in a class (Mixin), its method is called as a method defined in the
class. In this exercise, the sales_tax_applicable attribute of class ServiceItem obviously
affects the GST calculation.
This means in our GSTCalc module, it must refer the instance variable @sales_tax_appli-
cable.
Object Oriented Programming 134
module GSTCalc
# ...
def gst
if @sales_tax_applicable # check the object's instance variable
# ...
else
return 0.0
end
end
end
Object Oriented Programming 135
Implement a simplified library system. Initially, the librarian can import book
records into the system via a CSV file. Members of the library can borrow and
return books.
Library.import_books(File.join(File.dirname(__FILE__), "files","books.csv"))
Library.book_count # => 10
TITLE, AUTHOR
The 4-Hour Workweek, Timothy Ferriss
How to Win Friends and Influence People, Dale Carnegie
Purpose
Class Methods
Class Variables
Class Design
Load CSV data into objects
Find matching objects in an array
Object Oriented Programming 136
Analyse
By examining the nouns in the description of this exercise, we can identify 3 classes:
Book
Book has two attributes: title and author.
Member
Member has two attributes: name and member_id.
Library
The main class for this exercise. Library members belong to the library, so are the
books. So Library has to attributes: books and members. Because there will be only
one instance of Library, we dont need to create it, just use Library class directly.
books and members are class variables (see hints below) of Library. borrow, return
and find_by_title are Librarys class methods.
Not all classes can be directly extracted from the problem description. For example, book
lending is associated to records, a Rental class.
Rental
borrowing records containing the member and the book (not the book title as there can
be multiple copies of the same book). The rental records can be used check a members
borrowing history and a books lending history.
Hints
class Sedan
attr_accessor :make, :model
Obviously, the make and model are instance variables, as different objects may have different
values. Now, if I ask you how many wheels does Johns car or Mikes car have? The answer
is 4. In fact, 4 is the answer to all sedans. The number_of_wheels is a Class Variable of Sedan:
class Sedan
@@number_of_wheels = 4
end
Sedan.number_of_wheels # => 4
class Library
@@books = []
@@members = []
@@rentals = []
# ...
end
class Library
def self.import_books(file)
# read CSV and create book objects (see the hint below)
end
A checked out book is not available to borrow, have a think about how to ensure that.
Load CSV data into objects
Parse the CSV file (we covered CSV in Chapter 8), then create objects from the data.
Object Oriented Programming 139
require 'csv'
CSV.foreach(csv_file) do |row|
next if row[0] == 'TITLE' # skip the heading row
a_book = Book.new
a_book.title = row[0]
a_book.author = row[1]
a_book.status = "available"
@@books << a_book
end
def self.find_by_title(book_title)
the_book = @@books.select{|x| x.title == book_title }.first
if the_book.nil?
puts "Book #{book_title} not found"
end
return the_book
end
Object Oriented Programming 140
Each step a zombie makes, there will be exchange of fire (the sunflower shoots seeds to
the zombie; the zombie throws stones to the sunflower), both the sunflower and the zombie
receive a certain degree of damage to their health. If the attacking zombies health down to
0%, the zombie dies and then comes another until no more. If the sunflowers health reaches
0%, game over.
Game rules:
F(100) ___ ___ ___ ___ ___ ___ ___ ___ ___ Z87
F( 98) ___ ___ ___ ___ ___ ___ ___ ___ Z38 ___
F( 95) ___ ___ ___ ___ ___ ___ ___ Z00 ___ ___
Object Oriented Programming 141
While the above show 3 lines of printed out text, in the simulation, there shall be only one
(see hints). The line text is re-printed again and again to achieve a Motion effect.
if the sunflower wins,
F( 11) ___ ___ ___ ___ ___ ___ ___ Z00 ___ ___
F( 0) ___ ___ ___ ___ ___ ___ Z18 ___ ___ ___
Purpose
Game Design
Use of Class Variables and Class Methods
Overwrite previously printed line
Analyse
This program is quite complex, as always, it is a good idea to start with class design.
Clearly, we have two classes: Sunflower and Zombie. There are one Sunflower instance and
15 Zombie instances. The health attributes of both classes are used to determine live or death
of an object.
The distance a zombie has travelled can be used for determining whether to apply close-
combat damage. Jump Zombie moves 2 steps at one go, so there should be a speed attribute.
After defined the attributes, we move on to the methods (or behaviours).
Flower
exchange_fire: called when a zombie move forward a step. This records the damage
to the sunflower and the only zombie in the field. We can add randomness there to
increase unpredictability of simulation.
Object Oriented Programming 142
Zombie
check and locate the next zombie. The game ends if no more.
the active zombie move forward a step (or two steps if is a Jumpy) and exchange fire
with the sunflower.
print out the battle scene
Hints
CONSTANTS
A constant is like a variable, except that its value is supposed to remain constant for the
duration of the program. A Ruby constants name starts with an upper case letter, commonly
all upper cases.
NUMBER_OF_ZOMBIES = 10
If you change the value of a constant later, Ruby will give a warning, though the value is still
modified.
# ...
NUMBER_OF_ZOMBIES = 15 # warning: already initialized constant
class Zombie
@@live_count = 0
def initialize
# ...
@@live_count += 1
end
def self.remaining_lives
@@live_count
end
end
zombies = []
3.times { zombies << Zombie.new }
puts Zombie.remaining_lives # => 3
puts "Good"
puts "Morning"
print "See "
print "You"
print "Later"
Output:
Good
Morning
See YouLater
To overwrite previously printed line, add \r (carriage return without a line feed).
Object Oriented Programming 144
9.6 Exercises
Define four classes: Shape, Rectangle, Triangle and Square. Define area method and
attributes in these classes to calculate the shape area. The usage like below:
Hints
Solve this cryptic equation, every letter represent a distinct number between 0 -
9. No leading zeros are allowed. (This was a Google Interview question).
WWWDOT
- GOOGLE
--------
DOTCOM
Purpose
Analyse
W D O T G L E C M
W, G and D cannot be 0.
A brute-force way is to try every combination of 26 alphabetic characters using fast
computing power of the computers:
W = 1, D = 0, O = 0, T = 0, G = 0, L = 0, E = 0, C = 0, M = 0
146
Classic Puzzles 147
W = 1, D = 0, O = 0, T = 0, G = 0, L = 0, E = 0, C = 0, M = 1
W = 1, D = 0, O = 0, T = 0, G = 0, L = 0, E = 0, C = 0, M = 2
W = 1, D = 0, O = 0, T = 0, G = 0, L = 0, E = 0, C = 0, M = 9
W = 1, D = 0, O = 0, T = 0, G = 0, L = 0, E = 0, C = 1, M = 0
A brute force approach usually takes quite a while to solve, due to a large number of looping.
It is important to add constraints to reduce the number of loops (the code checking the
calculation). For example, by adding checking for uniqueness (if W is 1, D cannot be 1, O
cannot be equal to W and D, , etc), the number of effective loops is reduced to 2,540,160,
from possible 729,000,000!
Hints
Nested Loops
The code below prints a 9x9 times table.
for i in 1..9 do
for j in 1..9 do
puts "#{i}x#{j} = #{i*j}"
end
end
Output:
Classic Puzzles 148
1 x 1 = 1
1 x 2 = 2
1 x 3 = 3
1 x 4 = 4
1 x 5 = 5
1 x 6 = 6
1 x 7 = 7
1 x 8 = 8
1 x 9 = 9
2 x 1 = 2
2 x 2 = 4
...
9 x 9 = 81
a = 3
b = 6
c = 5
num_days = a * 100 + b * 10 + c # => 365
Classic Puzzles 149
We have done the Fibonacci exercise before. This time, we will use Recursion.
Before I explain what is Recursion, lets examine the formula of Nth Fibonacci
number:
fib(1) = 1
fib(2) = 1
fib(n) = fib(n-1) + fib(n-2)
For example, if I asked you what is the 6th fibonacci number?. Some may say easy, I can
answer straight away. What if I change the question to 674th Fibonacci number? By now, I
hope you have developed the habit of thinking like a computer. Back to the question, have
a look at this worksheet?
As you see, we dont know the answer to fib(6), fib(5) and fib(4). Until we reach fib(3)
= fib(2) + fib(1), as fib(2) and fib(1) are known. Then we use the result to work
backwards to get fib(4), fib(5) and fib(6). This is a Recursion method.
Recursion refers to a method which solves a problem by solving a smaller version of the
problem and then using that result to work out the answer to the original problem. In the
context of programming, a recursive method calls itself. This might seem complex and hard
to comprehend. If we change the thinking angle, it actually can be quite natural to certain
problems.
Recursion can only be a applied to certain problems; where the problem (usually in a form of a
formula) can be broken into smaller problems, like Fibonacci. Here is a solution to Fibonacci:
Classic Puzzles 150
def fibonacci(n)
return n if n <= 1
( fibonacci(n - 1) + fibonacci(n - 2) )
end
puts fibonacci(10) # => 55
The code is quite concise, isnt it? Lets examine this code.
Try using recursion to solve Highest Common Factor (HCF) problem with Euclidean
algorithm, a much more efficient way. The algorithm:
hcf(a, 0) = a
hcf(a, b) = hcf(b, a mod b)
Hints
Math Mod
The remainder of a division.
10 % 4 # => 2
http://en.wikipedia.org/wiki/Greatest_common_divisor#Using_Euclid.27s_algorithm
Classic Puzzles 151
We earn interest on the money deposited in the bank. The interest we receive
for the first year will be added to the principal (and then calculate the interest
for the next year), this is called Compound Interest. Write a program to calculate
how much money you will receive back at the given rate after certain years.
Purpose
Recursion
Review reading user input and printing formatted output
Analyse
Calculating compound interest, maybe less obvious than Fibonacci sequence, can also be
solved with recursion. The essence of the compound interest is that the principal (money we
deposit in the bank) plus the interest of the year becomes the new principal for the next year.
Hints
# ...
the_total = compound_interest(amount, rate, years)
Recursion
Classic Puzzles 152
A farmer wants to cross a river and take with him a wolf, a goat, and a cabbage.
There is a boat that can fit himself plus either the wolf, the goat, or the cabbage.
If the wolf and the goat are alone on one shore, the wolf will eat the goat. If the
goat and the cabbage are alone on the shore, the goat will eat the cabbage.
How can the farmer bring the wolf, the goat, and the cabbage across the river?
Step 0
[] <= [:farmer, :wolf, :sheep, :cabbage]
Step 1 [:farmer, :sheep] forward
[:farmer, :sheep] <= [:wolf, :cabbage]
Step 2 [:farmer] backward
[:sheep] <= [:farmer, :wolf, :cabbage]
Step 3 [:farmer, :wolf] forward
[:farmer, :wolf, :sheep] <= [:cabbage]
Step 4 [:farmer, :sheep] backward
[:wolf] <= [:farmer, :sheep, :cabbage]
Step 5 [:farmer, :cabbage] forward
[:farmer, :wolf, :cabbage] <= [:sheep]
Step 6 [:farmer] backward
[:wolf, :cabbage] <= [:farmer, :sheep]
Step 7 [:farmer, :sheep] forward
[:farmer, :wolf, :sheep, :cabbage] <= []
Done!
Purpose
Backtracking algorithm
Analyse
Remember your first attempts on this famous puzzle, maybe like this:
make_a_move {
move the item with the farmer, if item is farmer just move him
End cause
All three items and the farmer crossed the river.
Constraints
1. Farmer does not cross river empty handed as there is no point of doing that. He can
come back empty handed though.
2. The item to be moved has to be on the same side with the farmer
3. Safety check
sheep and cabbage cannot be on the same side without the farmer
wolf and sheep cannot be on the same side without the farmer
4. The move has not been done before
Classic Puzzles 155
Hints
Data Design
For a complex program, it is important to design the data structure at first.
Besides simple data types (such as Integer and String), we have used the following composite
types:
Array
Hash
Class
I used instance variables @xxx here. As this is a quite complex program, several methods will
be used. By using instance variables, I can use them in any methods without worrying about
the scopes.
Once the data is defined, it is not hard to write the following methods:
def is_all_crossed_river?
# check for @item_positions
end
is safe to cross?
Classic Puzzles 156
def is_safe?
# check @item_positions, both sides!
end
def is_item_with_farmer?(item)
# check @item_positions
end
def move(item)
# change value for the item and the farmer in @item_positions
# toggle @direction
end
def undo_move(item)
# revert value for the item and the farmer in @item_positions
# toggle @direction
end
The above statement stores the step number and the items position in a hash. dup stands for
duplicate; this creates a copy of the object so that the original object wont be affected by
changes.
For every move, we add it to the @moving_log. When reaching the solution (all items crossed),
we print out the moving log.
There is also another use of the moving log: check whether a move has already been done?
Classic Puzzles 157
def has_done_before?
# check the moving log whether have seen current item positions before?
end
Backtracking
This is the core part of the program: the recursive method. Its purpose: make a move if satisfies
the constraints.
def cross
if is_all_crossed_river?
# print moving log
# exit
end
@items.each do |item|
# ignore if not satifsying a move, eg. not going with the farmer
# move item
end
end
Classic Puzzles 158
Solve this cryptic equation, each letter stands for a unique digit (0-9). There are no leading
zeros.
UK
USA
+ USSR
--------
AGING
Purpose
Analyse
The brute-force approach (we used in Google Labs Aptitude Test) will work for this puzzle,
but wont be elegant. We could use backtracking to solve this puzzle.
We have 8 letters here.
U K S A R G I N
For each letter, possible values are 0 - 9. Then our backtracking algorithm can be
This might sound complex, but it is how our brain works for a similar but much more simpler
puzzles (like 2 or 3 letters). Computers dont mind complex calculations and memorizing all
the steps as long as there are algorithm-turned-into-instructions to follow on. Here is the
pseudocode for the above backtracking algorithm:
Pseudocode
function find_out(letter_index)
As you can imagine, this will generate a huge number of combinations to check against the
equation. It is going to be slow.
Based on the equation, we can reason out
A = 1, U = 9
Because USSR plus 3-digit number (and another 2-digit) can only make 1????, i.e. A
= 1 and U = 9.
G=0
Based on A = 1 and U = 9, G must be 0 as U(9) carry 1 to AG (10).
Classic Puzzles 160
We could go further, but I think this is good enough. Lets leave the rest to the computer to
figure out.
End cause
The combination satisfy UK + USA + USSR = AGING
Constraints
Hints
Data structure
@letters is defined as an array, so that we can start trying first letter @letters[0], then
@letters[1], , etc.
@is_digit_used is used to ensure that a digit can only be assigned to one letter.
Check answer
This is to verify whether a possible combination satisfies the equation.
def check_ans()
uk = decode_letters_to_number('UK', letter_to_digit_lookup)
# ...
# print out the solution if uk + usa + ussr == aging
end
Classic Puzzles 162
Here are some more fun puzzles that are great for programming exercises.
Tower of Hanoi
The objective of the game is to move all the disks onto a different pole.
(source: wikipedia)
Knights tour
A knights tour is a sequence of moves of a knight on a chessboard such that the knight visits
every square exactly once.
(source: wikipedia)
http://en.wikipedia.org/wiki/Tower_of_Hanoi
http://en.wikipedia.org/wiki/Knight%27s_tour
Classic Puzzles 163
Place eight chess queens on an 8x8 chessboard so that no queens is attacking any of the
others, that is, no two queens share the same row, column and diagonal.
(source: wikipedia)
http://en.wikipedia.org/wiki/Eight_queens_puzzle
11. Web Test Automation
Web Test Automation, or automated functional testing for web applications via the Graphical
User Interface (GUI), is the use of automated test scripts to drive test executions to verify
that the web application meets requirements. To put it simply, during the execution of an
automated test for a website, you see mouse and keyboard actions such as clicking a button
and typing text in a text box in a browser, without human intervention. Those actions were
driven by test scripts.
To effectively develop automated test scripts, mastering programming is a must. Ruby is
regarded as the best language for writing test scripts. In this chapter, we will use Ruby script
to test web applications.
http://www.forbes.com/sites/jacquelynsmith/2012/03/23/the-happiest-jobs-in-america
164
Web Test Automation 165
Big demand
Now a large percentage of new software development are web applications. There
are also considerable legacy desktop applications will be migrated to web-based.
Comparing to traditional desktop client development, web applications (or as a result
of modern software development practices) require far more frequent releases. As a
result, a lot more testing effort is needed. Furthermore, the emerging requirement of
supporting all leading browsers: IE, Chrome and Firefox also means demanding more
testing resources.
Relatively stable skill set
Some IT professionals left this industry because the technologies change too quickly,
this is particularly true for programmers. I have been working in IT over 18 years,
seeing many technologies come and go. However, the skill set for testing web
applications is relatively stable. While there are advancements in web technologies,
the fundamental and core ones such as HTML, CSS and JavaScripts remain largely the
same.
Transferable skill
The knowledge and experience you gained at company A is directly applicable to
company B, as long as you are testing web applications.
Few possess the skills
We know that there is no degree in software testing, and I never heard of any
universities offering courses in automated testing (This puzzles me. We all know that
software tester is an essential role in software projects). As a result, very few people
possess test automation knowledge.
Web Test Automation 166
A good news for you, the knowledge you gained from doing this books exercises plus
some extra effort (see below) will put you in a position well ahead of your peers, if you
are interested in getting the happiest job in USA.
Watir (Web Application Testing in Ruby) is a free and open source library for automated
testing web applications in Internet Explorer. As its name suggests, test scripts are actually
Ruby scripts.
Watir, or watir-classic to be precisely, works only on Internet Explorer (up to IE11) on
Windows.
Install Watir
RubyGems is the default package manager for Ruby. Most of Ruby libraries are released in a
format called gem. Here is a command to install Watir gem.
Alternatively, You may download RubyShell, a pre-packaged Ruby for Windows installer,
which contains all common gems required for software testing.
https://testwisely.com/testwise/downloads
Web Test Automation 167
require 'watir'
browser = Watir::Browser.new
browser.goto "http://travel.agileway.net"
browser.text_field(:name, "username").set("agileway")
browser.text_field(:name, "password").set("testwise")
browser.button(:value, "Sign in").click
browser.text_field(:name, "passengerFirstName").set("Bob")
browser.text_field(:name, "passengerLastName").set("Tester")
browser.button(:value,"Next").click
Selenium WebDriver (also known as Selenium 2) is the most popular testing framework for
web and mobile applications. There is clearly trend that more and more software companies,
such as Facebook, desire Selenium skills from programmers and software test engineers.
Selenium supports all major browsers and tests can be written in many programming
languages and run on Windows, Linux and Macintosh platforms.
require 'selenium-webdriver'
browser = Selenium::WebDriver.for(:firefox)
browser.navigate.to("http://travel.agileway.net")
browser.find_element(:id, "username").send_keys("agileway")
browser.find_element(:id, "password").send_keys("testwise")
browser.find_element(:xpath,"//input[@value='Sign in']").click
Selenium::WebDriver::Support::Select.new(browser.find_element(:name, "toPort\
")).select_by(:text, "Sydney")
Selenium::WebDriver::Support::Select.new(browser.find_element(:id, "departDa\
y")).select_by(:text, "04")
Selenium::WebDriver::Support::Select.new(browser.find_element(:id, "departMo\
nth")).select_by(:text, "March 2012")
browser.find_element(:xpath,"//input[@value='Continue']").click
browser.find_element(:name, "passengerFirstName").send_keys("Wise")
browser.find_element(:name, "passengerLastName").send_keys("Tester")
browser.find_element(:xpath,"//input[@value='Next']").click
}
browser.find_element(:name, "card_number").send_keys("4000000000000000")
Selenium::WebDriver::Support::Select.new(browser.find_element(:name, "expiry\
_year")).select_by(:text, "2013")
browser.find_element(:xpath,"//input[@value='Pay now']").click
browser = Selenium::WebDriver.for(:chrome)
...
Like Chrome, Selenium requires extra software to drive IE. It is called Internet Explorer
Driver Server, you can download it from the Selenium site.
https://sites.google.com/a/chromium.org/chromedriver/downloads
http://www.seleniumhq.org/download/
Web Test Automation 170
browser = Selenium::WebDriver.for(:ie)
...
Web test drivers such as Watir and Selenium WebDriver drive browsers. However, to make
effective use of them for testing, we need put them in a test framework which defines
test structures and provides assertions (performing checks in test scripts after driving the
application to a certain point).
In the context of web testing, typical checks are:
RSpec
RSpec is a popular test framework in Ruby. Standard gem installation (gem install rspec)
or using RubyShell. The below is a complete test script in RSpec.
require 'selenium-webdriver'
require 'rspec'
before(:all) do
@browser = $browser = Selenium::WebDriver.for(:firefox)
@browser.navigate.to("http://travel.agileway.net")
end
after(:all) do
@browser.quit
end
@browser.find_element(:id, "username").send_keys("agileway")
@browser.find_element(:id, "password").send_keys("testwise")
@browser.find_element(:xpath,"//input[@value='Sign in']").click
# check whether signed in OK?
expect(@browser.page_source).to include("Signed in!")
Selenium::WebDriver::Support::Select.new(@browser.find_element(:name, "f\
romPort")).select_by(:text, "New York")
Selenium::WebDriver::Support::Select.new(@browser.find_element(:name, "t\
oPort")).select_by(:text, "Sydney")
Selenium::WebDriver::Support::Select.new(@browser.find_element(:id, "dep\
artDay")).select_by(:text, "04")
Selenium::WebDriver::Support::Select.new(@browser.find_element(:id, "dep\
artMonth")).select_by(:text, "March 2012")
@browser.find_element(:xpath,"//input[@value='Continue']").click
@browser.find_element(:name, "passengerFirstName").send_keys("Wise")
@browser.find_element(:name, "passengerLastName").send_keys("Tester")
@browser.find_element(:xpath,"//input[@value='Next']").click
sleep 10
# check receipt info
expect(@browser.page_source).to include("2012-03-04 <b>New York</b> to\
<b>Sydney</b>")
end
Web Test Automation 172
end
Besides the test statements driving the application, it contains two checks:
# ...
expect(@browser.page_source).to include("Signed in!")
# ...
expect(@browser.page_source).to include("2012-03-04 <b>New York</b> to <b>\
Sydney</b>")
Obviously, here we have only touched the very basic of test automation for web applications.
The messages I am trying to convey in the chapter:
You can use Ruby to do some real work, or get a promising career.
You understand test scripts now, and probably can write some of your own.
If you are interested in test automation, please check the resources section for more.
12. Beyond This Book
If you look back the time when you started the first exercise, you probably realize that you
have learned a lot. While this book stops here, your learning wont. You have just laid a good
foundation to achieve more with computer, with the power of programming.
We have covered the fundamentals of Ruby. To know more cool features of Ruby, such as
Blocks, I would highly recommend the book: Programming Ruby 1.9 & 2.0: The Pragmatic
Programmers Guide. It is commonly known as The PickAxe because of the pickaxe
illustration on the cover. Ruby programming language was created by Yukihiro Matsumoto
(Matz) in Japan, The PickAxe has helped Ruby to spread outside Japan.
Programming is just like any other skills, more practices will make it better. As computers
are part of our life, if you pay attention, you will find a lot areas that you can apply your
programming skills to. For example, some exercises in this book are from my daughters Math
homework.
Even you cannot think of something now, try redo some exercises by adding variations,
especially those ones that you had troubles with for the first time.
If you have completed all the exercises, you will have enough programming skills required
for a good automated tester (2012s the happiest job in USA according to this Forbes report,
the second in both 2013 and 2014). As a matter of fact, one of the reasons of writing this book
https://pragprog.com/book/ruby4/programming-ruby-1-9-2-0
http://www.infoq.com/interviews/david-black
http://www.forbes.com/sites/jacquelynsmith/2012/03/23/the-happiest-jobs-in-america
173
Beyond This Book 174
is to respond the question I received from the readers of my other book Practical Web Test
Automation: Can you recommend a book on Ruby?
Ruby, in my opinion, is the most suitable script language for writing automated tests. The
over 10 million downloads of Selenium-WebDriver (the most popular web testing library,
used in Facebook and Google) Ruby gem supports my view.
Many people heard of Ruby the first time from the term Ruby on Rails, a popular web
application framework written in Ruby. Many large web sites running Ruby on Rails include
Hulu, Shopify, Yammer, Scribd and GitHub. Besides large web sites, Ruby on Rails is very
popular in startups.
The word Web in web programming means you also need master web technologies, such
as
HTML
JavaScripts
Cascading Style Sheets (CSS)
Book: Agile Web Development with Rails 4 - the definite guide to Ruby on Rails
development.
ScreenCasts: RailsCasts - great Rails tutorial screencast by Ryan Bates.
Online Course: Codecademys Make a Rails App course
If you dont have a web project in mind, I suggest implementing a library system (extended
from ex9-4) in Rails. In the ex9-4, this over-simplified library system has three features:
http://www.codecademy.com/
https://pragprog.com/book/rails4/agile-web-development-with-rails-4
http://railscasts.com/
http://www.codecademy.com/en/learn/make-a-rails-app/
Beyond This Book 175
Of course, they need to be rewritten in Rails (not hard to do). Then you can add more features:
It is common that beginners plan big and rush to coding, end up too complex to handle. The
proper way is to start simple and enhance the system gradually.
Also it is a good idea to write some automated tests for your web application. You might be
surprised that how useful they are.
Comparing to other main-stream programming languages such as Java and C#, Ruby lacks
behind on Graphical User Interface (GUI) support, maybe due to the majority use of Ruby
Beyond This Book 176
are either web application development (Rails) or scripting (automated testing). Having said
that, it is possible to write desktop applications in Ruby. For example, I developed Testwise,
a testing IDE, in Ruby.
Besides web applications, the other main focus of software development is on iOS or Android
apps. RubyMotion is a commercial product (you need to pay after free trial) that lets you
develop cross-platform native apps for iOS, Android and OS X in Ruby.
http://www.rubymotion.com/
https://leanpub.com/learn-swift-programming-by-examples
Appendix 1 Ruby in Nutshell
A quick summary of core Ruby syntax in code examples.
Print out
Variable assignment
177
Appendix 1 Ruby in Nutshell 178
## read an integer
print "Enter a number"
num = gets.chomp.to_i
Conditional
if
if score < 0
score = 0 # no negative score
end
unless
if true
puts "OK"
else
puts "Never reach here"
end
a = 10
b = 20
if a > b
puts "a is bigger"
elsif a < b
puts "b is bigger"
else
puts "a,b the same"
end
score = 70
Looping
1. while loop
Appendix 1 Ruby in Nutshell 180
count = 0
while count < 10
count = count + 1
print count, " "
end
# 1 2 3 4 5 6 7 8 9
2. for loop
for i in 1..9
print i, " "
end
# 1 2 3 4 5 6 7 8 9
3. until loop
count = 1
until count >= 5
print count, " "
count +=1
end
# 1 2 3 4
4. times loop
number = 2
3.times do
number = number * number
end
# 3 calculations: 2 * 2, 4 * 4, 16 * 16
puts number # => 256
Ignore this remaining statements in the current loop, start the next loop.
Appendix 1 Ruby in Nutshell 181
(1..5).each do |num|
puts "Checking #{num} ..."
next if num % 2 != 0 # Skip odd numbers
puts "#{num} is an even number."
end
Output:
Checking 1 ...
Checking 2 ...
2 is an even number.
Checking 3 ...
Checking 4 ...
4 is an even number.
Checking 5 ...
Infinite loop
while true
# the code within will be running again and again
# you shall really add an exit condition
end
Array
Appendix 1 Ruby in Nutshell 182
Iterate an array
Output:
Appendix 1 Ruby in Nutshell 183
Square of 20 is 400
Square of 10 is 100
Square of 30 is 900
Hash
us_states = {"CA" => "California", "NY" => "New York", "TX" => "Texas"}
us_states["FL"] = "Florida"
us_states.size # => 4
us_states.delete("NY")
puts us_states.inspect #=> {"CA"=>"California","TX"=>"Texas","FL"=>"Florida"}
Iterate a Hash
Output:
California's abbreviation is CA
Texas's abbreviation is TX
Florida's abbreviation is FL
Method
def square(num)
num * num # => equal to return num * num
end
exponent(5) # => 25
exponent(5, 3) # => 125
Scope
def add(a, b)
c = a + b # local scope of function
@c = c # set instance variable's value
puts "inside c = #{c}"
end
Output:
inside c = 3
outside c = 100
instance variable @c = 3
Nested Loop
Appendix 1 Ruby in Nutshell 185
# times table
for i in 1..9 do
for j in 1..9 do
puts "#{i}x#{j} = #{i*j}"
end
end
Class
class Bird
def fly
puts "I am flying"
end
end
seagull = Seagull.new
seagull.fly # => "I am flying"
parrot = Parrot.new
parrot.fly # => "I am flying"
parrot.speak # => "if someone teaches me"
ostrich = Ostrich.new
Appendix 1 Ruby in Nutshell 186
module Logging
def log(message)
puts "[#{Time.now}] [#{self.class.name}] #{message}"
end
end
class A
include Logging
end
class B
include Logging
end
class C
end
a = A.new
b = B.new
c = C.new
File I/O
Read file
Appendix 1 Ruby in Nutshell 187
Write to file
Date
*
**
***
****
*****
******
*******
********
*********
**********
Solution
10.times do |count|
# count starts with 0 then 1, 2, 3 ...
puts "*" * (count + 1)
end
Rubys times do can optionally pass the looping index to the code within the block, the
looping index starts with 0.
10.times do |looping_index|
# ...
end
Write a program to print out *s as a right side of diamond shape (still a triangle).
188
Appendix 2 Solutions 189
*
**
***
****
*****
******
*******
********
*******
******
*****
****
***
**
*
Solution
15.times do |row|
# row starting with 0
if row < 8
star_count = row + 1
else
star_count = (15 - row)
end
puts '*' * star_count
end
*
***
*****
*******
*****
***
*
Solution
15.times do |row|
if row < 8
star_count = row * 2 + 1
space_count = 8 - row
else
star_count = (15 - row) * 2 - 1
space_count = row - 6
end
puts ' ' * space_count + '*' * star_count
end
Ask the user for the size of diamond (by the number for total rows) and then print out a *
diamond.
Solution
Appendix 2 Solutions 191
puts will start a new line, so I used print to print out the static text for asking users
input.
introduce variable middle_row to improve code readability. In fact, Courtney got
failure initially because did not update the if row < ... condition correctly.
move print space * space_count out of if statements (duplication)
Chapter 3
A simple calculator adds two integer numbers (up to 99) from user inputs, and print out the
sum.
Appendix 2 Solutions 192
Solution
Write a program to ask a user for 10 single digit addition questions, give feedback on the
users response and finally print out the score.
1 + 1 = (enter 2)
Correct.
2 + 7 = (enter 8)
Wrong!
...
6 + 3 =
Solution
Appendix 2 Solutions 193
count = 0
10.times do
num1 = rand(10)
num2 = rand(10)
print "#{num1} + #{num2} ="
answer = num1 + num2
input = gets.chomp.to_i
if answer == input
puts "Correct!"
count += 1
else
puts "Wrong!"
end
end
puts "Your score is #{count}/10"
Courtney forgot the You score: part, which she added later.
Write a program to ask a user for 10 single digit subtraction questions, give feedback on the
users response and finally print out the score.
9 - 1 = (enter 8)
Correct.
7 - 2 = (enter 8)
Wrong!
...
Your score: 8/10
Solution
Appendix 2 Solutions 194
count = 0
10.times do
num1 = rand(10)
num2 = rand(10)
if num1 > num2
print "#{num1} - #{num2} = "
answer = num1 - num2
else
print "#{num2} - #{num1} = "
answer = num2 - num1
end
input = gets.chomp.to_i
if answer == input
puts "Correct!"
count = count += 1
else
puts "Wrong!"
end
end
puts "Your score is #{count}/10"
The computer has a secret number (0 to 9), the program asks the user to enter a guess and give
feedback such as too big or too small. The program ends when a correct answer is entered.
Solution
Appendix 2 Solutions 195
puts "I have a secret number (0-9) Can you guess it?"
count = 0
the_secret_number = rand(10)
while input = gets.chomp.to_i
count += 1
if input > the_secret_number
puts "TOO BIG"
elsif input < the_secret_number
puts "too small"
else
puts "CORRECT"
break
end
end
puts "The number is : #{the_secret_number}. and you guessed #{count} times!!"
Chapter 4
Ask a list of names and then output them in alphabetical order. The program ends when the
user enters 0.
Kids in order:
Angela, Anna, Courtney, Dominic, Ella, Emma, Toby
Solution
Appendix 2 Solutions 196
array = []
input = nil
puts "Enter child names in class: (0 to finish)"
until input == "0"
input = gets.chomp
if input != "0"
array << input
end
end
puts "Kids in order:"
puts array.sort.join(", ")
Ask user to enter a number between 1 to 26, the program tells the character at the alphabetical
position. The program ends when the user enters 0.
I know the alphabet very well, enter the alphabetical order number (integer)\
and I will tell you the corresponding letter, 0 to quit:
1 (user enter)
is 'A'
5 (user enter)
is 'E'
0
Bye!
Solution
Appendix 2 Solutions 197
array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N\
", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
puts "I know the alphabet very well, enter the alphabetical order number (in\
teger) and I will tell you the corresponding letter, 0 to quit:"
while true
input = gets.chomp.to_i
if input == 0
break
end
n = input - 1
puts array[n]
end
The above use while infinite loop, you may also use until loop as below:
Ask the user to enter a set of student scores (non-negative) integers and then calculate the
average. The program ends when the user enters -1.
Difficulty: Easy
Average score : 80
Appendix 2 Solutions 198
Solution
array = []
count = 0
puts "Enter scores: "
while true
input = gets.chomp.to_i
break if input == -1
Solution
Appendix 2 Solutions 199
alpha_value_lookups = {}
%w(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z).each_with_index do |\
ch, idx|
alpha_value_lookups[ch] = idx + 1
end
print "Enter word in capitals: "
input = gets.chomp
the_value = 0
input.split("").each do |character|
the_value += alpha_value_lookups[character]
end
I constructed the hash look-up from an alphabetical array. There are other ways too. Here I
will show you three things
Courtney used the array to sum the value, which is not necessary.
Chapter 5
Julie, an Australian, is going to USA, where different Fahrenheit temperature scales are used.
Here is the Fahrenheit to Celsius formula:
Can you write a program for Julie to convert Fahrenheit to Celsius? The result is round to 2
digits after the decimal point.
Appendix 2 Solutions 200
Solution
Write a program to calculate how much tax a person needs to pay based on his/her annual
salary?
Solution
Appendix 2 Solutions 201
case income
when 0..18200
puts "No tax"
when 18201..37000
tax = (income - 18200) * 0.19
when 37001..80000
tax = (income - 37000) * 0.325 + 3572
when 37001..80000
tax = (income - 80000) * 0.37 + 17547
else
tax = (income - 180000) * 0.45 + 54547
end
Emily wrote a book review, but she is not sure whether it meets the required word count.
Assuming Emilys article is saved in a string, Can you write a program to count the number
of words?
Appendix 2 Solutions 202
(assuming the text is "'Practical Web Test Automation' book is great. The en\
d." and set in the code)
The text has 9 words.
Solution
Write a program to generate a Lotto number. Lotto numbers are between 1 and 49, 6 numbers
are a group.
Your winning lotto numbers are [23, 34, 12, 6, 7, 49], good luck!
Solution
count = 0
array = []
until count == 6
lotto_number = rand(49)
lotto_number += 1
if array.include?(lotto_number)
# puts "ALREADY HAS #{lotto_number}"
next
else
array << lotto_number
count += 1
end
end
puts "Your winning lotto numbers are #{array}, good luck!"
Solution
array = [10, 8, 7, 3, 4, 5, 9, 1, 2, 6]
array.each_with_index do |num, idx|
# puts "num = #{num}"
# puts "idx = #{idx}"
Chapter 6
Solution
check = nil
array = []
print "Enter a number: "
input = gets.chomp.to_i
(1..input).each do |x|
check = input % x
if check == 0
array << x
end
end
puts "The divisors of #{input}: #{array.join(", ")}"
Write a program to ask the user to enter two non-negative integers and then find the highest
common factor (HCF).
Solution
Appendix 2 Solutions 205
divisors_list_1 = []
divisors_list_2 = []
puts "Enter first number: "
num1 = gets.chomp.to_i
(1..num1).each do |x|
check = num1 % x
if check == 0
divisors_list_1 << x
end
end
puts "Enter second number: "
num2 = gets.chomp.to_i
(1..num2).each do |x|
check = num2 % x
if check == 0
divisors_list_2 << x
end
end
d1sorted = divisors_list_1.sort.reverse
d1sorted.each do |elem|
# puts "elem = #{elem}"
if divisors_list_2.include?(elem)
puts "The HCF is #{elem}"
break
end
end
Least (also sometimes called Lowest) common multiple is the smallest (non-zero) number
that is a multiple of two or more examples. For example, 12 is the LCM of 6 and 4, as 12 = 6
x 2 and 12 = 3 x 4. 24 is a common multiple of 4 and 6, but is not the lowest.
Appendix 2 Solutions 206
Solution
Solution
Appendix 2 Solutions 207
array = []
(2..20).each do |num|
# check one number is a prime number or not
flag = true
(2..num - 1).each do |x| # trying to check each possible divisor
if num % x == 0
flag = false # mark this has divisor
break # no point to check more - composite, moves on to next
end
end
if flag == true # the number has no divisors
array << num # add to prime number list
end
end
puts "Prime numbers (up to 20) are : #{array.join(', ')}"
Start with a pair of rabbits (one male and one female) born on January 1. Assume that all
months are of equal length and that :
1. rabbits begin to produce young two months after their own birth;
2. after reaching the age of two months, each pair produces a mixed pair, (one male, one
female), and then another mixed pair each month thereafter; and
3. no rabbit dies.
How many pairs will there be in the end of each month of first year?
Solution
Appendix 2 Solutions 208
array = [1, 1]
num1 = 1
num2 = 1
10.times do
next_number = num1 + num2
array << next_number
num1 = num2
num2 = next_number
end
puts "The number of rabbit pairs are: #{array.join(', ')}"
Some natural numbers can be written as a sum of consecutive natural numbers. For example,
10 = 1 + 2 + 3 + 4. Some can be written in more than a way. For example, 9 = 2 + 3 + 4 and 9 =
4 + 5. Now can you write a program to output all possible ways for a given natural number.
Enter a number: 9
9 = 2 + 3 + 4
9 = 4 + 5
Solution
Chapter 7
Write a program to ask the user to enter two non-negative integers and then find the highest
common factor (HCF) of them. The program must define a method to return divisors of a
number.
Solution
def get_divisors(num)
array = []
(1..num).each do |x|
check = num % x
if check == 0
array << x
end
end
return array
end
d1sorted = divisors_list_1.sort.reverse
d1sorted.each do |elem|
if divisors_list_2.include?(elem)
puts "The HCF is #{elem}"
Appendix 2 Solutions 210
break
end
end
Write a program to generate a Lotto number. Lotto numbers are between 1 and 49, 6 numbers
are a group. This time, define a method that returns a valid lotto number.
Your winning lotto numbers are [23, 34, 12, 6, 7, 49], good luck!
Solution
def get_next_valid_lotto_number(existing_lotto_numbers)
new_lotto_number = rand(49)
while existing_lotto_numbers.include?(new_lotto_number)
new_lotto_number = rand(49)
end
return new_lotto_number
end
lotto_numbers = []
6.times do
new_valid_number = get_next_valid_lotto_number(lotto_numbers)
lotto_numbers << new_valid_number
end
Write a program to get the lowest number that is dividable by 1, 2, 3, 4, , 14 and 15.
Solution
Appendix 2 Solutions 211
m = 1
(2..15).each do |w|
m = lcm(m, w)
# puts "The LCM for up to #{w} is #{m}"
end
puts "The lowest number that is dividable by 1 to 15 is: #{m}"
Chapter 8
Write a program that reads the contents of a text file containing scores of a class (0-100, one
each line) and calculates the average score.
The content of the text file looks like this:
84
78
...
87
Solution
array = []
file_content.split("\n").each do |line|
array << line.to_i
end
Write a program that reads the contents of a file and counts the number of words and lines
in that file.
Solution
Appendix 2 Solutions 213
first_command_line_argument = ARGV[0]
file_content = File.read(input_file_full_path)
word_count = file_content.split.size
line_count = file_content.split("\n").size
Jessica is planning to invite her friends to her 12-year-old birthday party. Instead of
writing individual invitations, she wants to print them out from computer. Instead creating
documents one by one, she wants to generate a letter document (a text file) using the template
below:
Dear {{first_name}},
Jessica.
Her friends: Pokkle, Angela, Tonpa, Toby, Biscuit, Mito, Kate, Renee, Chloe, Kelly and Melody.
Write a program to help Jessica to generate multiple invitation cards in text files such as
pokkle_invitation.txt and angela_invitation.txt.
Solution
Appendix 2 Solutions 214
Jessica."
guest_list.each do |name|
named_invitation = invitation.gsub("{{first_name}}", name)
puts named_invitation
File.open("C:\\Users\\you\\rubycode\\#{name.downcase}_invitation.txt", "w"\
).write(named_invitation)
end
to
Appendix 2 Solutions 215
so that the files are always shown in alphabetical order and without spaces. (the directory
with the above sample files can be found at sources/files/book_dir)
Solution
require 'fileutils'
directory = File.expand_path( File.join(File.dirname(__FILE__), "files", "bo\
ok_dir"))
Dir.foreach(directory) do |item|
next if item == '.' or item == '..'
# puts item
if item =~ /chapter\s(\d+)(.*)/
sequence = $1
new_sequence = $1.rjust(2, "0")
new_filename = "chapter_" + new_sequence + $2
# puts new_filename
FileUtils.mv(File.join(directory, item), File.join(directory, new_filena\
me))
end
end
Write a currency conversion program that converts Australian Dollars to Japanese Yen. Use
a live currency change rate service (requires Internet Connection) to ensure the accuracy.
Appendix 2 Solutions 216
Solution
require 'net/http'
require 'uri'
require 'csv'
yahoo_exchange_rate_live_url = "http://download.finance.yahoo.com/d/quotes.c\
sv?s=AUDJPY=X&f=sl1d1t1ba&e=.csv"
begin
csv_data = Net::HTTP.get(URI.parse(yahoo_exchange_rate_live_url))
# puts csv_data
rescue => e
puts "Unable to connect to Yahoo Finance, Error: '#{e}'"
exit(-1)
end
Jessica had a good birthday party and received gifts from friends. She now is going to send
each of them a thank you email. Instead of sending one by one, she wrote a program to send
emails of each of her friends by running a single command.
Her thank you email looks like this:
Appendix 2 Solutions 217
Dear Angela,
Thank you for coming to my 12th party, I really like the gift you gave me: C\
at Statue, thank you very much!
Jessica
And this is a list of people (with email) and gifts she received.
Solution
test_mode = true
guest_list = [
{:first_name => "Pokkle", :email => "[email protected]", :gift => "Bow and\
Arrows"},
{:first_name => "Angela", :email => "[email protected]", :gift => "Cat St\
atue"},
# ...
]
Appendix 2 Solutions 218
Thank you for coming to my 12th party, I really like the gift you gave me: {\
{gift}}, thank you very much!
Jessica"
require 'mail'
options = {
:enable_starttls_auto => true,
:address => 'smtp.gmail.com',
:port => 587,
:authentication => 'plain',
:user_name => '[email protected]',
:password => 'secret'
}
Mail.defaults do
delivery_method :smtp, options
end
guest_list.each do |entry|
content = thank_you_template.gsub("{{first_name}}", entry[:first_name]).gs\
ub("{{gift}}", entry[:gift])
puts content
Mail.deliver do
from my_email
to test_mode ? "me+#{entry[:first_name]}@gmail.com" : entry[:email]
subject 'Thank you for your gift'
body content
end
end
Appendix 2 Solutions 219
Chapter 9
9.1 Calculator
Solution
class Calculator
def add(a, b)
return a + b
end
def minus(a, b)
return a - b
end
end
calc = Calculator.new
puts calc.add(2, 3)
puts calc.minus(17, calc.add(2, 3) )
A school consists of teachers and students, and students are grouped by grades. Write a
program to calculate
a teachers age
the average age of teachers
the average age of Grade 10 student
Appendix 2 Solutions 220
students = []
students << Student.new("John Sully", "1999-10-03", 10) # 10 is grade
students << Student.new("Michael Page", "1999-05-07", 11)
students << Student.new("Anna Boyle", "1998-12-03", 10)
students << Student.new("Dominic Chan", "1999-09-10", 10)
Output:
Solution
require 'date'
class Person
attr_accessor :name, :birth_date, :gender
def age
if self.birth_date
tmp_year = Date.today.year - self.birth_date.year
tmp_month = Date.today.month - self.birth_date.month
tmp_day = Date.today.day - self.birth_date.day
return tmp_year - 1 if (tmp_month < 0) || (tmp_month == 0 && tmp_day\
< 0)
return tmp_year
else
Appendix 2 Solutions 221
nil
end
end
end
end
end
students = []
students << Student.new("John Sully", "1999-10-23", 10)
students << Student.new("Michael Page", "1999-10-23", 11)
students << Student.new("Anna Boyle", "1998-12-03", 10)
students << Student.new("Dominic Chan", "1999-10-23", 10)
The sales tax in Australia is called Goods and services tax (GST) of 10%, which is applied
to most products and services. For example, medical services such as physiotherapy is GST-
free, however GST applies to products sold at clinics. All business must show GST-inclusive
prices. For example, for a $10 meal, $9.09 is net amount and $0.91 sales tax.
Complete the program below to calculate the net amount and sales tax of product and
services.
# ... statements to print out product and services' net amount and sale tax
Output:
Solution
module GSTCalc
GST_RATE = 10.0
def net_amount
if @sales_tax_applicable
(@amount / (100.0 + GST_RATE) * 100.0).round(2)
else
return @amount
end
end
def gst
Appendix 2 Solutions 223
if @sales_tax_applicable
tax = (@amount - self.net_amount).round(2)
else
return 0.0
end
end
alias tax gst
end
class ServiceItem
include GSTCalc
end
class Goods
include GSTCalc
Implement a simplified library system. Initially, the librarian can import book records into
the system via a CSV file. Members of the library can borrow and return books.
TITLE, AUTHOR
The 4-Hour Workweek, Timothy Ferriss
How to Win Friends and Influence People, Dale Carnegie
Solution
class Library
@@books = []
@@members = []
@@rentals = []
def self.import_books(csv_file)
require 'csv'
CSV.foreach(csv_file) do |row|
next if row[0] == 'TITLE'
a_book = Book.new
a_book.title = row[0]
a_book.author = row[1]
a_book.status = "available"
@@books << a_book
end
end
def self.find_by_title(book_title)
the_book = @@books.select{|x| x.title == book_title }.first
if the_book.nil?
puts "Book #{book_title} not found"
end
return the_book
end
def self.book_count
@@books.size
end
def self.rental_history
@@rentals
end
def self.return(book)
the_rental = @@rentals.select{|x| x.is_active && x.book == book}.first
book.status = "available"
the_rental.finish
end
end
class Book
attr_accessor :title, :author, :status
end
class Member
attr_accessor :name, :member_id
end
class Rental
attr_accessor :member, :book
attr_accessor :is_active
def finish
@is_active = false
end
end
Each step a zombie makes, there will be exchange of fire (the sunflower shoots seeds to the
zombie; the zombie throws stones to the sunflower), both the flower and the zombie receive
a certain degree of damage to their health. If the attacking zombies health down to 0%, the
zombie dies and then comes another until no more. If the sunflowers health reaches 0%,
game over.
Game rules:
Appendix 2 Solutions 228
F(100) ___ ___ ___ ___ ___ ___ ___ ___ ___ Z87
F( 98) ___ ___ ___ ___ ___ ___ ___ ___ Z38 ___
F( 95) ___ ___ ___ ___ ___ ___ ___ Z00 ___ ___
While the above show 3 lines of printed out text, in the simulation, there shall be only one
(see hints). The line text is re-printed again and again to achieve a Motion effect.
if the sunflower wins,
F( 11) ___ ___ ___ ___ ___ ___ ___ Z00 ___ ___
F( 0) ___ ___ ___ ___ ___ ___ Z18 ___ ___ ___
Solution
Appendix 2 Solutions 229
ZOMBIES_COUNT = 15
DISTANCE = 10
class Sunflower
attr_accessor :health
def initialize
@health = 100
end
def exchange_fire(zombie)
if zombie.in_touch_distance?
@health -= 5
else
@health -= (rand(2) + 1)
end
if @health <= 0
@health = 0
end
end
end
class Zombie
@@live_count = 0
attr_accessor :health, :step
attr_accessor :moving_speed
Appendix 2 Solutions 230
def initialize
@@live_count += 1
@health = 100
@step = 0
@moving_speed = rand(10) >= 8 ? 2 : 1 # 80% are jumping zombies
end
def self.remaining_lives
@@live_count
end
def die
@health = 0
@@live_count -= 1
end
def is_dead?
@health <= 0
end
def in_touch_distance?
@step == DISTANCE
end
def move_forward
@step += @moving_speed
if @step >= DISTANCE
@step = DISTANCE
end
end
end
print "\nY(100) ___ ___ ___ ___ ___ ___ ___ ___ ___ ___"
sunflower = Sunflower.new
zombies = []
ZOMBIES_COUNT.times do
Appendix 2 Solutions 231
active_zombie = nil
if active_zombie.nil? || active_zombie.is_dead?
active_zombie = zombies.shift unless zombies.empty?
end
sunflower.exchange_fire(active_zombie)
flower_health_str = sunflower.health.to_s.rjust(3, " ")
print "\r"
print "F(#{flower_health_str}) "
if sunflower.health > 0
puts "\n\nYou Win! Sunflower survived attacks from #{ZOMBIES_COUNT} zombie\
s"
else
puts "\n\nGame Over! #{Zombie.remaining_lives} zombies left"
end
Appendix 2 Solutions 232
Chapter 10
Solve this cryptic equation, every letter represent a distinct number between 0 - 9. No leading
zeros are allowed. (This was a Google Interview question).
WWWDOT
- GOOGLE
--------
DOTCOM
Solution
start_time = Time.now
(1..9).each do |w|
(1..9).each do |d|
next if d==w
(0..9).each do |o|
next if o==d || o==w
(1..9).each do |t|
next if t==o || t==d || t==w
(0..9).each do |g|
next if g==t || g==o || g==d || g==w
(0..9).each do |l|
next if l==g || l==t || l==o || l==d || l==w
(0..9).each do |e|
next if e==l || e==g || e==t || e==o || e==d || e==w
(0..9).each do |c|
Appendix 2 Solutions 233
(0..9).each do |m|
end
end
end
end
end
end
end
end
end
def hcf(a, b)
if a == 0
return b
elsif b == 0
return a
else
return hcf(b, a%b)
end
end
We earn interest on the money deposited in the bank. The interest we receive for the first
year will be added to the principal (and then calculate the interest for the next year), this is
called Compound Interest. Write a program to calculate how much money you will receive
back at the given rate after certain years.
Solution
A farmer wants to cross a river and take with him a wolf, a goat, and a cabbage. There is
a boat that can fit himself plus either the wolf, the goat, or the cabbage. If the wolf and the
goat are alone on one shore, the wolf will eat the goat. If the goat and the cabbage are alone
on the shore, the goat will eat the cabbage.
How can the farmer bring the wolf, the goat, and the cabbage across the river?
Step 0
[] <= [:farmer, :wolf, :sheep, :cabbage]
Step 1 [:farmer, :sheep] forward
[:farmer, :sheep] <= [:wolf, :cabbage]
Step 2 [:farmer] backward
[:sheep] <= [:farmer, :wolf, :cabbage]
Step 3 [:farmer, :wolf] forward
[:farmer, :wolf, :sheep] <= [:cabbage]
Step 4 [:farmer, :sheep] backward
[:wolf] <= [:farmer, :sheep, :cabbage]
Step 5 [:farmer, :cabbage] forward
[:farmer, :wolf, :cabbage] <= [:sheep]
Step 6 [:farmer] backward
[:wolf, :cabbage] <= [:farmer, :sheep]
Step 7 [:farmer, :sheep] forward
[:farmer, :wolf, :sheep, :cabbage] <= []
Done!
Solution
Appendix 2 Solutions 236
def is_all_crossed_river?
@item_positions.values.all? {|x| x.to_s == "crossed"}
end
def undo_move(item)
if @item_positions[item] == :crossed
@item_positions[:farmer] = @item_positions[item] = :not_crossed
else
@item_positions[:farmer] = @item_positions[item] = :crossed
end
@direction = @direction == :forward ? :backward : :forward
end
def is_safe?
# OK if farmer is there
end
return true
end
def has_done_before?
@moving_log.values.any?{|x|
x[:farmer] == @item_positions[:farmer] &&
x[:sheep] == @item_positions[:sheep] &&
x[:wolf] == @item_positions[:wolf] &&
x[:cabbage] == @item_positions[:cabbage]
}
end
def is_item_with_farmer?(item)
@item_positions[item] == @item_positions[:farmer]
end
def print_moving_log
@moving_log.keys.sort.each do |key|
action_str = "Step #{key}"
item_statuses = @moving_log[key]
items_not_crossed = item_statuses.collect{ |x, y| x if y.to_s == "not_cr\
ossed" }.compact
items_crossed = item_statuses.collect{ |x, y| x if y.to_s == "crossed" }\
.compact
if key > 0
prev_statuses = @moving_log[key -1]
prev_not_crossed = prev_statuses.collect{ |x, y| x if y.to_s == "not_c\
rossed" }.compact
prev_crossed = prev_statuses.collect{ |x, y| x if y.to_s == "crossed" \
}.compact
Appendix 2 Solutions 238
if diff_not_crossed.empty?
action_str += " #{diff_crossed} forward"
else
action_str += " #{diff_not_crossed} backward"
end
end
puts action_str
end
def cross
if is_all_crossed_river?
print_moving_log
puts "Done!"
return # exit out of recursion
end
@items.each do |item|
move(item);
@step += 1
else
undo_move(item); # "not safe, revert"
# puts @item_positions.inspect
end
end
end
cross()
Solve this cryptic equation, each letter stands for a unique digit (0-9) there are no leading
zeros.
UK
USA
+ USSR
--------
AGING
Solution
Appendix 2 Solutions 240
def decode_letters_to_number(a, d)
d.each do |k, v|
a = a.gsub(k,v) #if k && v
end
a.to_i
end
def check_ans()
a = decode_letters_to_number('UK', @letter_to_digits)
b = decode_letters_to_number('USA', @letter_to_digits)
c = decode_letters_to_number('USSR', @letter_to_digits)
d = decode_letters_to_number('AGING', @letter_to_digits)
# puts "[DEBUG] Checking #{a} + #{b} + #{c} = #{d}"
if a + b + c == d
puts "#{a} + #{b} + #{c} = #{d}" # got it
puts @letter_to_digits
end
end
def find_out(idx = 0)
if idx == @letters.length # got 8 letter filled
check_ans()
return
end
(0..9).each do |i|
# assign 0 - 9 to each letter
next if i == 0 and (@letters[idx] =='U' || @letters[idx] =='A' ) # no le\
ading 0
next if @letters[idx] == 'A' && i != 1
next if @letters[idx] == 'U' && i != 9
next if @letters[idx] == 'G' && i != 0
Appendix 2 Solutions 241
unless @is_digit_used[i]
@is_digit_used[i] = true # the number is used
@letter_to_digits[@letters[idx]] = i.to_s
find_out(idx + 1)
@is_digit_used[i] = false # clear
end
end
end
start_time = Time.now
find_out # start
puts "Time to solve: #{Time.now - start_time}"
Chapter 11
require 'watir'
browser = Watir::Browser.new
browser.goto "http://travel.agileway.net"
browser.text_field(:name, "username").set("agileway")
browser.text_field(:name, "password").set("testwise")
browser.button(:value, "Sign in").click
browser.text_field(:name, "passengerFirstName").set("Bob")
browser.text_field(:name, "passengerLastName").set("Tester")
browser.button(:value,"Next").click
Appendix 2 Solutions 242
require 'selenium-webdriver'
browser = Selenium::WebDriver.for(:firefox)
browser.navigate.to("http://travel.agileway.net")
browser.find_element(:id, "username").send_keys("agileway")
browser.find_element(:id, "password").send_keys("testwise")
browser.find_element(:xpath,"//input[@value='Sign in']").click
Selenium::WebDriver::Support::Select.new(browser.find_element(:name, "toPort\
")).select_by(:text, "Sydney")
Selenium::WebDriver::Support::Select.new(browser.find_element(:id, "departDa\
y")).select_by(:text, "04")
Selenium::WebDriver::Support::Select.new(browser.find_element(:id, "departMo\
nth")).select_by(:text, "March 2012")
browser.find_element(:xpath,"//input[@value='Continue']").click
browser.find_element(:name, "passengerFirstName").send_keys("Wise")
browser.find_element(:name, "passengerLastName").send_keys("Tester")
browser.find_element(:xpath,"//input[@value='Next']").click
Appendix 2 Solutions 243
require 'selenium-webdriver'
require 'rspec'
before(:all) do
@browser = $browser = Selenium::WebDriver.for(:firefox)
@browser.navigate.to("http://travel.agileway.net")
end
after(:all) do
@browser.quit
end
Selenium::WebDriver::Support::Select.new(@browser.find_element(:name, "f\
romPort")).select_by(:text, "New York")
Selenium::WebDriver::Support::Select.new(@browser.find_element(:name, "t\
Appendix 2 Solutions 244
oPort")).select_by(:text, "Sydney")
Selenium::WebDriver::Support::Select.new(@browser.find_element(:id, "dep\
artDay")).select_by(:text, "04")
Selenium::WebDriver::Support::Select.new(@browser.find_element(:id, "dep\
artMonth")).select_by(:text, "March 2012")
@browser.find_element(:xpath,"//input[@value='Continue']").click
@browser.find_element(:name, "passengerFirstName").send_keys("Wise")
@browser.find_element(:name, "passengerLastName").send_keys("Tester")
@browser.find_element(:xpath,"//input[@value='Next']").click
sleep 10
# check
expect(@browser.page_source).to include("2012-03-04 <b>New York</b> to\
<b>Sydney</b>")
end
end
Resources
Code Editor
Ruby Language
Ruby Tutorials
245
Resources 246
More Exercises
Ruby Quiz
A programming challenge site for Ruby programmers, contains over 150+ quizzes. The
quizzes are generally quite hard.
Test Automation
Others
Agile Web Development with Rails 4 by Sam Ruby, Dave Thomas, David Heine-
meier Hansson
Learn to develop web applications with popular Ruby on Rails.
Learn Swift Programming by Examples by Zhimin Zhan
Leverage similar exercises in this book to learn a hot new programming language Swift
to develop your own iOS apps.
http://rubyquiz.com/
https://leanpub.com/practical-web-test-automation
https://leanpub.com/selenium-recipes-in-ruby
https://leanpub.com/watir-recipes
http://www.amazon.com/Agile-Development-Rails-Facets-Ruby/dp/1937785564
https://leanpub.com/learn-swift-programming-by-examples