A1: (a) and (b)
In Ruby, all objects have a logical value for use in conditional statements.
The objects false
and nil
are treated as
logically false, all other objects are treated as logically true.
A2: (c) and (e)
The complete list of reserved words as of Ruby 2.1 is listed below:
A3: (c)
Ruby variable names must begin with a lowercase letter or underscore, and may contain only letters, numbers, and underscore characters.
Variable names must not conflict with keywords (e.g. you cannot have a variable called class
), but unambiguous names that contain reserved words are acceptable (i.e. both classy
and _class
are valid Ruby variable names)
A4: (a) and (b)
Single quoted string literals are simple, and are meant to represent raw sequences of characters.
Double quoted string literals are more complex, but offer extra features such as string interpolation (#{...}
), where entire Ruby expressions can be evaluated and inserted into a string.
As a shortcut, #$
is usable for inserting the contents of a global variable into a string. (Similarly, #@
can be used with instance variables). This shortcut variant is less commonly used than the more general #{...}
form.
A5: (c)
A leading zero in an integer literal indicates 'octal-mode' in Ruby, i.e. a number in base 8 format. However, all print functions in Ruby will output numeric values in base 10 by default.
Should you need to output numbers in something other than base 10, there are many different functions in Ruby for formatted numeric output (e.g. String#%
, Numeric#to_s(base)
, Kernel#sprintf
)
A6: (b)
The ternary operator (cond ? expr1 : expr2
) is a compact form of if/else
which will return expr1
if cond
is true, otherwise will return expr2
. It is most suitable for short statements that easily fit on a single line.
A7: (a)
Ruby case
statements will select the first branch to match its when
condition.
Because Ruby's two-dot range literal is an inclusive range, the end value is included as part of the range.
So although both 1..120
and 120..170
include 120
, the when 1..120
branch is matched because it appears first in the case statement.
A8: (d)
Although local variables from the surrounding scope are accessible within blocks, block parameters themselves are always block-local variables. This means that when a block parameter has the same name as a local variable from the surrounding scope, within the block any references will refer to the block-local variable. This prevents accidental modification of variables from the outside scope due to naming collisions.
Defining block parameters with the same name as a local variable from the surrounding scope is considered an antipattern and may be a sign of an accidental programming error. To catch this problem, run ruby
with the -w
flag, and you will see warnings like warning: shadowing outer local variable - item
wherever this problem occurs.
A9: (c)
The Integer#times
method yields values starting at zero, up to one less than the specified integer.
Although block variables with the same name of local variables from the surrounding scope are shadowed (see A8
), other local variables are accessible and can be modified. This is because Ruby blocks are closures.
A10: (c), (d)
The encoding magic comment must appear as the first line of the file, unless a UNIX shebang line is present (in that case, the encoding line would be placed on the second line).
Both coding: ...
and encoding: ...
may be used to set the source file's encoding, and they work identically.
Setting the encoding for a source file affects only the contents of that file. In other words, it applies to things like string literals in the file, but does not automatically set encoding for things like reading from and to other files.
If no encoding comment is present in a file, the default encoding is set to UTF-8.
A11: (c)
String#encoding
returns an Encoding
object which also provides some other helpful methods (e.g. Encoding#ascii_compatible?
)
A12: (b)
When a coding:
comment is omitted, Ruby will use UTF-8 by default for its source encoding.
A13: (b)
In an if/elsif/else
conditional statement, the first matching if
or elsif
branch will be executed. If none match, then the else
branch will be run.
A14. (c)
When none of the if
and elsif
clauses of a conditional statement match, the else
branch is run.
A15: (d)
Nearly every programming language has some sort of if/elsif/else
structure, but they vary in the name they choose for elsif
. This can be a source of confusion if you're coming to Ruby from another language, and the only solution is to memorize the specific term used in each language.
A16: (a) and (c)
Character classes ([...]
) match any single character from within the brackets.
Alternatives (...|...
) are used to match any one of many possible subexpressions.
The \A
anchor matches the beginning of a string, and the \z
anchor matches the end of a string.
Note that the reason that (b) is not a correct answer is because its subexpressions are \ARuby
and ruby\z
, allowing matches for things like Ruby123
A17: (b)
The expression /\A[a-z][a-z]*\z/
could be described in words as "A string which begins with a lowercase letter, followed by zero or more additional lowercase letters and nothing else."
Here are some additional notes for understanding the other patterns in this question:
- The
*
quantifier matches a subexpression zero or more times. - The
.
quantifier matches any character except a newline. - The
^
inverts a character class, causing it to match anything except the named chars.
(see A16
for a recap of other features that were already covered in that question)
A18: (d)
Constants can be redefined, but because this is usually a bad practice, a warning is displayed.
Because Ruby also uses constants for referencing module and class names, the constant redefinition warning can also help catch accidental naming collisions.
A19. (b)
No warning is shown because the constant is not being redefined; instead the object it references is being modified.
By convention, objects referenced by constants are usually treated as immutable. But there are certain rare cases where that convention would not apply.
A20: (b) and (e)
Some notes on Ruby variable naming rules:
- Global variables start with
$
. - Class variables start with
@@
. - Instance variables start with
@
. - Local variables must begin with a lowercase letter or an underscore.
- The remaining characters in any variable type are limited to letters, numbers, and underscores.
A21: (c)
In this example, both the x
and y
variables reference the same array object.
Because Array#reject!
modifies its receiver, this means that it modifies the single array that is referenced by both variables.
A22: (c)
Some notes on Array
operations:
shift
removes the first element of an array and returns its value.pop
removes the last element of an array and returns its valuepush
adds the specified element to the end of an array.
A23: (b)
The logical ||
and &&
operators short-circuit, only executing the right side
of the expression if necessary.
The special |
and &
operators provided on Ruby's boolean objects do not short circuit, so the right side of the expression is always evaluated.
Note that all Ruby objects support the ||
and &&
operators, but not all objects implement |
and &
.
A24: (c)
Although the or
operator short circuits and the n = true
expression is never executed, the n
local variable is still statically declared. Therefore, the variable is present but its value is nil
.
A25: (a) and (d)
This question illustrates two different ways of indexing a sub-array.
One approach is to use two integers, i.e. x[1,3]
-- This means "get a subarray of length 3
starting at index 1
.
Another approach is to use a range, which generates a subarray based the index values within that range.
The simple form of using a range is something like x[1..3]
which would give you a subarray starting at index 1
and ending at index 3
.
But Ruby also allows negative indexing, which define indexes relative to the end of an array rather than the beginning.
Thus, x[-4..-2]
is referring to the subarray starting from the 4th to the last position in the array, and continuing to the second-to-last position.
To clarify, here is a list of the index values for each position in the array from this question:
x [ 9, 7, 5, 3, 1]
i 0 1 2 3 4
-i -5 -4 -3 -2 -1
A26: (d)
Array#join
returns a string that is created by converting each element
in an array to a string and then combining them together with the
specified separator.
A27: (a)
String#to_i
attempts to parse an integer from a string starting from its first character, and continuing until the end of a valid number in a particular base. If a string does not begin with a valid number, 0
is returned.
By default, numbers are assumed to be in base 10, but other bases (from 2
to 36
) can be specified via a parameter.
Note that while "42A7".to_i
returns 42
because A
is not a valid part of a base 10 number, "42A7".to_i(16)
would extract the hexadecimal value 0x42A7
, which when converted to decimal would be equal to 17063
.
A28. (b)
has_key?
, include?
, key?
, member?
are all aliases for a single method which returns true
if the given key is present in the hash, and returns false
otherwise.
The contain?
method is not defined by Hash
.
A29: (a) and (e)
Some notes on array processing methods:
- In addition to
reject!
there is alsoreject
, which returns a new array rather than modifying the original. - Because there isn't a non-destructive form of
delete_if
, there is nodelete_if!
method. By convention Ruby only uses!
at the end of the method when there are two features that work similarly but with one being more dangerous than the other. - The
Array#slice
method works similarly toArray#[]
, and is used for retrieving a specific value or subarray by index rather than filtering based on a condition. It does not accept a block.
A30: (c)
The |
operator is equivalent to a set union. It returns a new array that is built by joining two arrays together, eliminating any duplicates while preserving order.
A31: (d)
The &
operator is equivalent to a set intersection. It returns a new array that is made up of elements found in both arrays it operates on, while preserving order and eliminating duplicates.
A32: (b)
In a begin/rescue/end
block... the first matched rescue
statement will be executed.
Because SomeOtherError
is a subclass of SomeError
, it matches the rescue SomeError
statement, and so that branch is what gets run.
In a real application, it is usually a good practice to attempt to match more specific errors before the more general errors that they inherit from (e.g. rescue StandardError
would usually come last).
A33: (c)
Dividing by zero raises a ZeroDivisionError
exception.
That exception is rescued, and a message is printed out. Then exit(1)
tells Ruby to exit with an error code.
But because the begin...end
expression has an ensure
section, it is run before the interpreter exits.
The ensure
clause is useful because it can be used to do cleanup even when some code raises an exception or tells Ruby to exit. It is often used for things like closing file handles, database connections, etc.
A34: (e)
By default, all classes inherit from the Object
class, whether or not they are explicit subclasses of some other class.
To create class hierarchies that do not inherit from Object
, it is possible to explictly inherit from BasicObject
instead, which has very few features built into it. But the use cases for doing so are uncommon.
A35: (c)
Class definitions can be re-opened and updated at any time, including Ruby core classes like Object
.
Because all Ruby core objects (except BasicObject
) inherit from the Object
class, adding new methods to Object
will make them available on all objects.
A36: (c)
Whenever the new
method is called on a class, a new instance of that class is allocated and then the initialize
method is called on that instance. This allows some setup code to be run as soon as the object is instantiated.
A37: (d)
The new
method (defined by Class
) is used to create new object instances.
A38: (a)
The super
keyword invokes a method with the same name higher up the ancestry chain.
In this particular example, calling Bar.new
causes Bar#initialize
to run, which sets @var = "banana"
. But then immediately after that, super
is called, causing Foo#initialize
to run. That method sets @var = "apple"
, which explains the final result.
A39: (c)
Array#delete
removes all elements from an array that are equal to the specified value.
For deleting a value at a particular index, see documentation for Array#delete_at
.
For deleting values based on a condition, see documentation for Array#delete_if
(an alias for reject!
).
A40: (c)
The to_a
method uses the common naming convention for converting an object into an array, and is found throughout Ruby's collection classes.
Some objects also implement to_ary
, which is used for implicit conversions. For example, Array#flatten
will attempt to call to_ary
on the elements within an array if it is present. But these use cases are uncommon.
A41: (b)
The #find
method is defined by the Enumerable
module. It returns the first element of the collection for which the block's result is not false
or nil
.
Note that Enumerable#find
is also aliased as Enumerable#detect
.
A42: (a) and (c)
The sort_by
method maps the elements in a collection to a set of values via a block, and then sorts the elements of the collection in ascending order based on those values.
The sort
method (when called without a block) sorts an array in ascending order directly based on the values of its elements. There is also a block form of sort
which allows for element-by-element comparison.
Both sort_by
and sort
rely on the <=>
operator to be defined in order to make comparisons between objects. Ruby's Numeric
classes all implement this operator, but you can also define it for your own objects.
A43: (b)
When called with a block, sort
will attempt to put elements in order based on the block's result.
The block must implement a comparison between two elements, and is expected to return a negative integer when the first element should appear before the second in the sorted array, 0
if the two elements have an equal sort order, and a postive integer when the first element should appear after the second in the sorted array.
Ruby's numeric objects implement <=>
, which provides this behavior automatically:
>> 3 <=> 1
=> 1
>> 3 <=> 3
=> 0
>> 3 <=> 5
=> -1
The <=>
(spaceship operator) can be implemented by any object that has a meaningful sort order.
A44: (b)
The seek
method is used to move to a specific byte offset in an I/O stream.
Offsets are zero-based, so seek(5)
sets the position in the stream to just after the fifth byte.
The gets
method reads from the current position in the stream to the end of a line.
A45: (a)
The "r"
open mode means "read only, starting from the beginning of the file."
This is both the safest default option and the most common use case.
A46: (d) and (e)
The following I/O open modes are supported by Ruby:
"r" Read-only, starts at beginning of file (default mode).
"r+" Read-write, starts at beginning of file.
"w" Write-only, truncates existing file
to zero length or creates a new file for writing.
"w+" Read-write, truncates existing file to zero length
or creates a new file for reading and writing.
"a" Write-only, each write call appends data at end of file.
Creates a new file for writing if file does not exist.
"a+" Read-write, each write call appends data at end of file.
Creates a new file for reading and writing if file does
not exist.
A47: (b) and (c)
Some additional notes:
FileUtils.mv
from the fileutils stdlib can be used to rename a directory.
File.basename
is used for getting the last part of a file name from a path string. (e.g. File.basename("long/path/to/something") #=> "something"
)
A48: (b)
Similar to the syntax for indexing subarrays (Q25), it is possible to index a substring by providing a starting position and length.
A49: (b)
Note that the replacement string does not need to be the same length as the original string. For example:
>> str = "boat"
=> "boat"
>> str[1,2] = "uil"
=> "uil"
>> str
=> "built"
A50: (b)
Ruby's numeric objects define a method called coerce
which attempts to convert objects into the same type for arithmetic operations. This method is not implemented by the String
class, so a TypeError
is raised.
Note that if the order was reversed (i.e. "hi" * 5
), then the result would be "hihihihihi"
. This is because String
does define its own *
operator, which is used when the string appears on the left hand side of the expression.