Ruby Array

 

 Ruby Array

What is Ruby Array

A collection of objects or values with a defined, regular order. In Ruby, you can represent ordered collections of objects using arrays.

Here’s a basic Ruby array:

x = [1, 2, 3, 4]

 

This array has four elements. Each element is an integer and is separated by commas from its neighboring elements. Square brackets are used to denote an array literal. Elements can be accessed by their index (their position within the array). To access a particular element, an array (or a variable containing an array) is followed by the index contained within square brackets. This is called an element reference. For example:

  • x = [1, 2, 3, 4]
  • puts x[2]
  • 3

 

Creating and Initializing Arrays

Initializing Arrays

Problem How do you create an array in Ruby?

Solution

In Ruby, arrays are sequentially an integer-indexed collection of objects. The starting index is 0 (like C or Java), however, a negative index is considered from the end (in reverse), with –1 referring to the last element. Arrays can hold strings, integers, hashes, and so on (including other arrays). Ruby arrays can grow automatically as needed.

 

There are many ways to create an array in Ruby. One option may be more suitable than the others, based on the situation. An array can be created using literals, which in this case is a list of 0 or more objects within square brackets, or by explicitly instantiating an Array object.

 

Many valid ways of creating/initializing an array, are shown next.

a1 = []

puts a1.length

A new array, a1, is created. The length function, called on an array, indicates the number of elements that the array has at that point. In this case, the code would print 0, because it is an empty array.

For initializing the array with a number of elements, as it is created, you could use the following.


a1 = [1,2,5]

...

a2 = Array.new

This creates an empty array.

...

a3 = Array.new(20)

puts a3.length

An array of size 20 is created (the length is 20), with all elements initialized to nil.

...

a4 = Array.new(4,"a")


This creates an array of size 4 and initializes all elements to "a", as you can see.

irb(main):007:0> a4 = Array.new(4,"a")

=> ["a", "a", "a", "a"]

...

irb(main):001:0> a6 = Array.[](1,2,3,4,5)

=> [1, 2, 3, 4, 5]

...

irb(main):002:0> a7 = Array[1,2,3,4,5]

=> [1, 2, 3, 4, 5]

...

a8 = Array(0..9)

Now you can use a range to initialize an array.

irb(main):003:0> a8 = Array(0..9)

=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

...

For a Ruby array, it is legal to hold multiple types of objects at the same time. Thus, the following is perfectly legal in Ruby.

a1 = [1,"cat",2,"dog",3]

 

Accessing Array Elements

Accessing Array

Problem You want to access the elements in an array.

Solution

Arrays are organized linearly with numeric indices. In Ruby (as in many other languages), the index of the first element is 0, the second element is 1, and so on. The element at index j (which is actually j+1-the element) is accessed as <array-name>[j]. That is, for the following array a1, the first element (whose value is 1) can be accessed as a1[0], the second element ("cat") can be accessed as a1[1], and so on.


a1 = [1,"cat",2,"dog",3]

irb(main):002:0> a1[0]

=> 1

irb(main):003:0> a1[1]

=> "cat"

Note that trying to access an index that is out of bounds for the array does not result in an error, but returns nil.


irb(main):004:0> a1[6]

=> nil

...

Unlike many other computer languages, Ruby allows negative array indices. They denote elements from the last position, backward; the last position index being –1.

 

Hence, in the preceding array, a1[-1] refers to 3 (the last element) and a1[-2] refers to "dog" (the last element).


a1 = [1,"cat",2,"dog",3]

puts "last element : " + a1[-1].to_s

puts "element before last : " + a1[-2].to_s

It prints like this:

last element : 3

element before last : dog

(Note that for puts, the non-string elements had to be converted with to_s).

 

It is possible to access part of the array in an array[start, length] style index range or an array[Range] style index range. Here is an example.


a1 = [1,"cat",2,"dog",3]

print a1[1,2]

puts

print a1[1..3]

This prints as follows.

["cat", 2]

["cat", 2, "dog"]

 

Inserting an Element at a Certain Position

Certain Position

Problem You want to insert elements into an array at a certain position.

Solution

It can be done by assigning the new value to the element, by referring to the element with its index. It is possible to assign values to multiple elements at the same time (using the same principle of access, such as range and so forth, as already discussed).

The following code illustrates various assignments on array elements.


a1 = [1,"cat",2,"dog",3]

a1[5] = 'tiger' # added at the end

# now array is [1, "cat", 2, "dog", 3, "tiger"] print a1

puts

a1[3] = 'wolf' #1 element gets replaced

# now array is [1, "cat", 2, "wolf", 3, "tiger"] print a1

puts

a1[2,2] = [4,'bat'] # 2 elements gets replaced by 2 new ones

# now array is [1, "cat", 4, "bat", 3, "tiger"]

print a1

puts

a1[2,2] = 'possam' # 2 elements gets replaced by 1 new element, array shrinks

# now array is [1, "cat", "possam", 3, "tiger"] print a1

puts

a1[2,1] = [5,'lynx'] # one element gets replaced by 2 , array grows

# now array is [1, "cat", 5, "lynx", 3, "tiger"] print a1

puts

a1[-4..-3] = [2,'dog'] # replace using range

# now array is [1, "cat", 2, "dog", 3, "tiger"] print a1

...

Working with Multidimensional Arrays

Multidimensional Arrays

Problem You want to work with data that requires a multidimensional array.

 

Solution

Multidimensional arrays are somewhat counterintuitive in Ruby. They should simply be declared as an array of arrays (or an array of an array of arrays, and so on), and the dimensions are not consistent. (An array may have one element as single and another element as an array).

 

Here is an illustrative example.

a = [1,[2,3]]

As you can see, this is not something that you would expect in Java. But it is valid.

b = [[1,2,3],[4,5,6]]

This is more organized. Note that in this case, the way to access an element is very similar to many other languages.

b[1][0] => 4

However weird it might seem, Ruby multidimensional arrays can be worked upon interestingly. The transpose and flatten functions are notable.

irb(main):005:0> a.flatten

=> [1, 2, 3]

irb(main):006:0> b.transpose

=> [[1, 4], [2, 5], [3, 6]]

Note that flatten has an in-place version, flatten!, which changes the original array rather than returning a new modified copy. 

 

Working with Arrays

Working with Arrays

Problem You want to use the full set of array operations, such as getting information about an array, comparing arrays, and carrying out set operations.

 

Solution

The Array class has plenty of methods (some inherited) to facilitate working with arrays. You might already have seen the length function. A similar function is a size, which returns the (current) size of the array.

The following prints a 5.


a = [2,3,5,4,1]

print a.size

empty?

empty? checks whether an array is empty or otherwise.

irb(main):001:0> a = []

=> []

irb(main):002:0> b = [1,2]

=> [1, 2]

irb(main):003:0> a.empty?

=> true

irb(main):004:0> b.empty?

=> false

fill

fill can fill an array partially or fully (based on how it is used). It has many forms. Some forms are shown in following example.

irb(main):001:0> a = Array.new(5)

=> [nil, nil, nil, nil, nil]

irb(main):002:0> a.fill('x')

=> ["x", "x", "x", "x", "x"]

irb(main):003:0> a.fill('y',2,2)

=> ["x", "x", "y", "y", "x"]

irb(main):004:0> a.fill('z',1..2)

=> ["x", "z", "z", "y", "x"]

 

Add, Subtract, Compare, and Contrast

Contrast

Linear (a.k.a. one-dimensional) arrays can be worked upon in Ruby with some functions, which may have a feel of arithmetic fluidity.


+

The + concatenates two arrays.

irb(main):001:0> [1,2,3] + [4,5]

=> [1, 2, 3, 4, 5]

concat

The concat function can also be used to similar effect as +.

-


The - function

irb(main):002:0> [1,2,3,4,5] - [2,3]

=> [1, 4, 5]

 

The behavior of - is somewhat like a set operation. It doesn’t affect non-existing elements in the first array and removes repeated elements when required.


irb(main):003:0> [1,2,2,4,5,5] - [2,3] => [1, 4, 5, 5]

*

The * function has repetitive action.

irb(main):004:0> [3,4] * 3

=> [3, 4, 3, 4, 3, 4]

<<

The << function appends an element or array at the end of the left array.

irb(main):006:0> [1,2] << 'a'

=> [1, 2, "a"]

irb(main):007:0> [2,3] << 'a' << 'b' << [4,5] => [2, 3, "a", "b", [4, 5]]

==

The == function checks the equality of two arrays (alternatively, the eql? method can be used).

irb(main):008:0> [1,2] == [1, 2]

=> true

irb(main):009:0> [1,2] == [2,3]

=> false

<=>

The <=> function is the comparison operator. Returns an integer (–1, 0, or 1 based on whether the first array is less than, equal to, or greater than the second array).


irb(main):010:0> ['a','a','b'] <=> ['a','b','c'] => -1

irb(main):011:0> [4,5,6] <=> [2,3]

=> 1

irb(main):012:0> ['a','a','b'] <=> ['a','b'] => -1

 

Set Operations

Set Operations

An array, as a collection, is not a set in the mathematical sense. In a more general sense (i.e., in the English-language term), it can be thought of as a set of things (of course, structured in a certain manner). From that point of view, you can think of a set operation between two arrays as a set operation between two sets of things.

 

At a conceptual level, set operations on arrays in general work on the set of elements of one array with those of another array. If there is a common element in both arrays, for instance (whether it occurs once, or multiple times, in either array), this is returned as part of the output of intersection operation of those two arrays. Some of these operations are described next.

| (or union)

irb(main):013:0> ['a','a','b'] | ['b','c','c','d'] => ["a", "b", "c", "d"]

Note that duplicate elements are eliminated.

& (or intersection)

irb(main):014:0> ['a','a','b','b','c'] & ['b','c','c','d'] => ["b", "c"]

 

uniq

As a side note, you can use the union operation with an empty array to get an array with a unique set of elements.

irb(main):016:0> ['a','a','b','b','c'] | [] => ["a", "b", "c"]

But a cleaner way to do that is using the uniq function.

irb(main):017:0> ['a','a','b','b','c'].uniq => ["a", "b", "c"]

 

In Place Operations

Place Operations

This is a common feature of Ruby (not just restricted to arrays). A function that has a ! at the end usually denotes an in place operation (meaning it changes the original array, as opposed to returning a new modified copy, keeping the original intact). For instance, uniq has a variation, which is uniq!, and that works in place (i.e., on the original array).


irb(main):001:0> a = ['a','a','b','b','c'] => ["a", "a", "b", "b", "c"] irb(main):002:0> b = a.uniq => ["a", "b", "c"]

irb(main):003:0> print a

["a", "a", "b", "b", "c"]=> nil

irb(main):004:0> c = a.uniq!

=> ["a", "b", "c"]

irb(main):005:0> print a

["a", "b", "c"]=> nil

 

Note that similar to this, if a method name ends in ? (e.g., eql?), it usually checks the trueness of something and returns a Boolean value. There are other utilities, notably sort and reverse, which also have in place versions.

 

sort (and sort!)

sort and sort! are used to sort the elements of an array. It is very handy for a lot of scripting tasks.

irb(main):006:0> [1,3,4,2,6,3,9,5,4].sort => [1, 2, 3, 3, 4, 4, 5, 6, 9]

 

Note that sort has another form, which uses the block feature of Ruby

 

reverse (and reverse!)

reverse! does the same thing but on the original array itself.

irb(main):007:0> [1, 2, 3, 3, 4, 4, 5, 6, 9].reverse => [9, 6, 5, 4, 4, 3, 3, 2, 1]

 

Further Access and Manipulation

Access and Manipulation

A few more useful functions of Array API are explained next.

include?

 

As you can see, they include? the method ends in a ?, which checks whether a certain element is contained in an array (and returns a boolean value). It is like the contains function in some languages.


irb(main):008:0> ['a','b','c'].include?('d')

=> false

irb(main):009:0> ['a','b','c'].include?('c')

=> true

 

index

index

You can get the index of a particular element within an array, using the index method (given the element value). It is a somewhat counterintuitive approach to lookup an array, but it can be very useful sometimes.


irb(main):010:0> ['a','b','b','c','d'].index('c')

=> 3

irb(main):011:0> ['a','b','b','c','d'].index('b')

=> 1

 

Note that for repetitive elements, it returns the index of the first occurrence. It returns nil if the element does not exist in the array.


rindex

rindex a function similar to index, but it returns the rightmost index (in a repetitive element) or nil if it does not exist.

irb(main):001:0> ['a','b','b','c','d'].rindex('b')

=> 2

irb(main):002:0> ['a','b','b','c','d'].rindex('e')

=> nil

values_at

 

You can access the elements of an array using <array>[start-index,length] or <array>[range] construct. But both of these require that the elements be returned to a contiguous position. If you need to return an array (a subarray of the original) with multiple elements but not contiguous, you can use the values_at function.

irb(main):001:0> ['a','b','d','f','j','l','m'].values_at(0,2,5) => ["a", "d", "l"]

 

fetch

fetch

fetch is for accessing an element of an array. However, it can be more useful than a normal access mechanism when the index may be out of bounds and that should not cause an exception in the flow of execution. It has a few forms (two of them are discussed here).

 

A fetch without argument returns the element at that index or results in an error for out-of-bound indexes.

irb(main):008:0> a1 = [1,"cat",2,"dog",3] => [1, "cat", 2, "dog", 3] irb(main):009:0> a1.fetch(0) => 1

irb(main):010:0> a1.fetch(6)

IndexError: index 6 outside of array bounds: -5...5 from (irb):10:in `fetch'

from (irb):10

However, a fetch with an argument returns the argument as an alternate value in index out of bounds cases.

irb(main):011:0> a1.fetch(6,'Not found')

=> "Not found"

 

insert

insert

The insert function allows insertion at a certain index position (pushing later elements to higher index positions to create space). It is possible to insert multiple elements at once. It is also possible to use a negative index (–1 is the last item, –2 is the item before that, and so on).


irb(main):002:0> a = ['a','b','c','d'] => ["a", "b", "c", "d"] irb(main):003:0> a.insert(2,5) => ["a", "b", 5, "c", "d"]

irb(main):004:0> a.insert(-2,6) => ["a", "b", 5, "c", 6, "d"] irb(main):005:0> a.insert(4, 'e','f') => ["a", "b", 5, "c", "e", "f", 6, "d"]

Note that it affects the original array.

 

delete

delete deletes all occurrences of a specified item from the array. It returns nil if the item is not found.


irb(main):007:0> a = ['a','b','b','c','d'] => ["a", "b", "b", "c", "d"] irb(main):008:0> a.delete('b') => "b"

irb(main):009:0> print a

["a", "c", "d"]=> nil

irb(main):010:0> a.delete('z')

=> nil

 

In the return of the print a command in irb (shown earlier), note that first the value of the array at that point is printed. Then, the return value (of print function) is printed after the =>. Since the print function returns nil (it prints to the console, but returns nil), that part turns up as => nil.

 

The delete function has another form that can return something other than nil and that uses the block feature.

irb(main):011:0> a.delete('z') { 'Value not in array' } => "Value not in array"

 

delete_at

delete_at is used to delete elements at a particular index. For an out of range index, it returns nil.


irb(main):001:0> a = ['a','b','c','d','e'] => ["a", "b", "c", "d", "e"] irb(main):002:0> a.delete_at(2) => "c"

irb(main):003:0> print a

["a", "b", "d", "e"]=> nil

 

join

join

join returns a string, which is made by joining all the elements (using a specific separator between elements, if one is given). The default separator is $, which is usually nil.


irb(main):005:0> ['n','o','t','e'].join => "note"

irb(main):006:0> ['abra','ca','dabra'].join('-')

=> "abra-ca-dabra"

 

compact

compact removes nil elements from an array and collapses it (if there was any nil element to begin with).

irb(main):007:0> ['a',nil,nil,'b',nil,'c'].compact => ["a", "b", "c"]

 

clear

clear removes all elements from the array.


irb(main):008:0> a = ['x','y','z']

=> ["x", "y", "z"]

irb(main):009:0> a.clear

=> []

 

Creating Hashes

Creating Hashes

Problem How is a hash created in Ruby?

Solution

Hashes (also known as associative arrays or maps) are a collection of key-value pairs. The keys are not necessarily numeric or sequential; however, they should be unique across the hash. The values are retrieved through the corresponding keys.

 

A hash is structured/organized like this:

["cat" => "feline", "wolf" => "lupine", "bear" => "ursine"]

And instead of retrieving an element in this way, for example—'element at index 0', it is retrieved like this: 'element whose key is “wolf”'.

A key can be any Ruby object (even an array); however, a string key is quite common.

A hash can be created in many ways. The following are some examples.

h1 = Hash.new

This creates an empty hash:

h2 = Hash['a' => 100, 'b' => 200]

 

This creates a hash and initializes it with two key-value pairs. At this point, if h2 is printed, it would look like this:


{"a"=>100, "b"=>200}

And the elements can be accessed as h['a'] or h['b'].

irb(main):003:0> h2['a']

=> 100

irb(main):004:0> h2['b']

=> 200

The following works.

h = Hash["a" => 100, "b" => 200]

And so does the following.

h = { "a" => 100, "b" => 200 }

A hash can be created with a default value (as shown in the following).

h = Hash.new ('unknown')

 

The significance of the default value is that, if it is accessed with a key, which is non-existent for the hash, then the default value will be returned. (For a hash, where no default value is available, it returns nil in such situations).

 

Adding New Elements to a Hash

Adding New Elements

Problem

How do you add new elements to an existing hash?

Solution

After a new hash is created with a default value, let’s say as follows… h = Hash.new('unknown'). The addition of new entries can be done by assigning a value to a new key position, as shown here.


h['AUS'] = 'Canberra'

h['UK'] = 'London'

h['JP'] = 'Tokyo'

At this point, the hash’s length or size is 3.

irb(main):005:0> h.length

=> 3

irb(main):006:0> h.size

=> 3

 

However, if a key, which is non-existent, is accessed, it returns the default value.


irb(main):007:0> h['USA']

=> "unknown"

The default values can be accessed by the default method at any point.

irb#1(main):017:0> h.default

=> "unknown"

And they are set with the default= method.

irb#1(main):018:0> h.default='ABCD'

=> "ABCD"

irb#1(main):019:0> h['x']

=> "ABCD"

But more importantly, the set of keys and the values of a hash can be accessed using the keys and values methods, respectively.


irb#1(main):014:0> h.keys

=> ["AUS", "UK", "JP"]

irb#1(main):015:0> h.values

=> ["Canberra", "London", "Tokyo"]

The set of keys for a hash, in particular, is important for iterating through its elements.

 

Working with Hashes

Working with Hashes

Problem

You want to use the full set of hash operations, such as getting information about a hash, inverting a hash, and accessing data in a hash.

Solution

The Hash class in Ruby offers a rich set of functions. Some of these have already been discussed. In fact, the access operator [] (e.g., h['a']) and the assignment operator []= (e.g., h['a'] = 1) are themselves methods. More methods are discussed next.

 

clear

clear clears a hash (removes all its elements). Note that it works in place (i.e., on the original hash object).


irb(main):001:0> h = { 1 => 'a', 2 => 'b' }

=> {1=>"a", 2=>"b"}

irb(main):002:0> h.clear

=> {}

irb(main):003:0> print h

{}=> nil

empty?

empty? checks whether the hash is empty or not. It returns a Boolean.

irb(main):004:0> h.empty?

=> true

has_key?

has_key? checks whether the given key exists in the hash. It returns a Boolean. The same function is called with other names, such as – key?, include?, member?.

irb(main):005:0> h = { 1 => 'a', 2 => 'b' }

=> {1=>"a", 2=>"b"}

irb(main):006:0> h.has_key?(1)

=> true

irb(main):007:0> h.has_key?(3)

=> false

This is a very useful function to work with a hash.

has_value?

has_value? is the counterpart of the has_key? function for checking the existence of a given value in the hash. It also has a synonym: value?.

irb(main):008:0> h.has_value?('b')

=> true

irb(main):009:0> h.has_value?('c')

=> false

key

key is used to get the key of a given value. It returns nil if the value is not present in the hash.

irb(main):001:0> { 1 => 'a', 2 => 'b' }.key('b')

=> 2

irb(main):002:0> { 1 => 'a', 2 => 'b' }.key('c')

=> nil

 

fetch

argument

A fetch has a similar connotation to the function with the same name in the array. It accesses an element of a hash. This can be more useful than a normal access mechanism when there is a possibility that the key, for which the element is being attempted to be retrieved, may not be present in the hash and that should not cause an exception.

 

A fetch without an argument returns the element for that key or results in an error if the key is not present. But a fetch with a second argument returns the value specified in that argument when the key is not present.


irb(main):001:0> { 1 => 'a', 2 => 'b' }.fetch(2,'invalid key') => "b"

irb(main):002:0> { 1 => 'a', 2 => 'b' }.fetch(3,'invalid key') => "invalid key"

values_at

Somewhat similar to its namesake in array, values_at can retrieve values for multiple keys in one shot.

irb(main):003:0> h = {1 => 'a',2 => 'b',3 => 'c',4 => 'd'} => {1=>"a", 2=>"b", 3=>"c", 4=>"d"} irb(main):004:0> h.values_at(1,4)

=> ["a", "d"]

irb(main):005:0> h.values_at(2,5)

=> ["b", nil]

 

Note that it returns nil for non-existent keys. For a hash with a default value, it returns the default value in those places.


irb(main):006:0> h.default = 'x'

=> "x"

irb(main):007:0> h.values_at(2,5,8)

=> ["b", "x", "x"]

 

delete

The delete function deletes the value in the given key and returns the value (or returns nil if a key is not present). This function has more than one form.


irb(main):008:0> {1 => 'a', 2 => 'b'}.delete(1)

=> "a"

irb(main):009:0> {1 => 'a', 2 => 'b'}.delete(3)

=> nil

 

invert

invert

invert returns a new hash, which is an inversion of the original hash (in the sense that the values of the original hash are made keys to this hash, and the corresponding keys of the original hash are made corresponding values). This can be very useful sometimes.


irb(main):010:0> {1 => 'a', 2 => 'b'}.invert => {"a"=>1, "b"=>2}

to_a

to_a converts the hash into a two-dimensional array, where each internal array is a conversion of the key-value pairs of the hash.

irb(main):013:0> {1 => 'a', 2 => 'b'}.to_a => [[1, "a"], [2, "b"]]

==

== compares two hashes for equality. It returns a Boolean.

irb(main):001:0> {1 => 'a', 2 => 'b'} == {1 => 'a', 3 => 'c'} => false

irb(main):002:0> {1 => 'a', 2 => 'b'} == {1 => 'a', 2 => 'b'} => true

irb(main):003:0> {1 => 'a', 2 => 'b'} == {2 => 'b', 1 => 'a'} => true

 

merge (and merge!)

merge

Called on one hash, with another hash object as an argument, the merge function returns a new hash (merge! is the in-place version) that merges the elements of the second hash to the first hash. Any common key gets the value of the second hash. (This function also has another form involving the block feature).


irb(main):007:0> h1 = {1 => 'a', 2 => 'b'} => {1=>"a", 2=>"b"}

irb(main):008:0> h2 = {1 => 'd', 3 => 'c'}

=> {1=>"d", 3=>"c"}

irb(main):009:0> h1.merge(h2)

=> {1=>"d", 2=>"b", 3=>"c"}

 

Creating a Collection of Unique Objects

Creating a Collection

Problem

You want to create a collection of unique objects.

Solution

A set is a common type of collection. A Ruby set (which intuitively points to a collection of objects), follows a somewhat mathematical (set theory) convention:

• Each object in a set may occur only once.

• There is no specific ordering or indexing in a set.

Sets can be very useful for many algorithms, where something needs to be represented as a collection of unique objects.

 

In Ruby, in order to use a set, you need to include the corresponding module (actually require works at the file level, so it includes the set.rb file). The following code shows how to create a set and add elements to it, as well as initializing a set with multiple elements at creation.


#include the corresponding module

require 'set'

#create an empty set

s1 = Set.new

#add elements to the set

s1.add(1)

s1.add('a')

#create and initialise a set

s2 = Set.new [1,2,'c'] #use at least one space between new and [

s3 = [1,2,'d'].to_set

 

Note that a set can contain multiple types of elements. (Note also that for the second form—that is the definition for s2, there has to be at least one space between new and the opening square bracket; otherwise, it will result in an error).

Possibly the easiest way (in terms of typing), however, is to use the Set[] construct directly.


irb(main):004:0> require 'set'

=> true

irb(main):005:0> Set[1,2]

=> #<Set: {1, 2}>

Note that even in irb, you need to require it once for the session.

 

There is no question of retrieving an individual element of a set (unlike an array or a hash), because individual elements do not have an identity as such within the organization of the set. However, set operations (in a mathematical sense) can be performed on the set, with other sets, and there are ways to determine whether a particular element exists in the set or not (without any such operation, defining a set would be meaningless anyway).

 

Inspecting a Set

Problem

You want to see what is in a set.

 

Solution

A good way to inspect the current contents of a set is to use the p function, as shown next.


irb(main):001:0> require 'set'

=> true

irb(main):002:0> s = Set.new [1,2,'c'] => #<Set: {1, 2, "c"}> irb(main):003:0> p s #<Set: {1, 2, "c"}>

=> #<Set: {1, 2, "c"}>

irb(main):004:0> print s

#<Set:0x007fd019976b20>=> nil

Note that p (a bit like print or puts but not quite), prints the value to be inspected rather than invoking to_s on the object (as puts or print does). Hence, if to_s is not defined/overridden in the class satisfactorily, then it may print the object-id, and so forth (see the output of print s in the preceding case).

 

Working with Sets

Working with Sets

Problem

You want to use the full set of set operations, such as getting information about a hash, inverting a hash, and accessing data in a hash.

Solution

The Set API provides a rich set of functions to manipulate a single set, perform set operations on two sets, and so on. Some of these functions are discussed next.

 

Checking and Changing


Let’s start with the set [1,2].

irb(main):002:0> s = Set[1,2]

=> #<Set: {1, 2}>

length (or size)

length or size provides the size of the set (in terms of the number of elements).

irb(main):003:0> s.length

=> 2

irb(main):004:0> s.size

=> 2

empty?

empty? checks if the set is empty. It returns a Boolean.

irb(main):005:0> s.empty?

=> false

include?

include? checks if the given item exists in the set. It returns a Boolean.

irb(main):006:0> s.include?(1)

=> true

clear

clear removes all elements from the set.

irb(main):007:0> s.clear

=> #<Set: {}>

<< (or add)

<< adds an element in the set.

irb(main):009:0> s << 'a'

=> #<Set: {"a"}>

 

merge

merge can be used to add multiple elements at the same time.


irb(main):011:0> s.merge(['b','c','d','e','f'])

=> #<Set: {"a", "b", "c", "d", "e", "f"}>

 

delete

delete

delete is used to delete one item.


irb(main):013:0> s.delete('a')

=> #<Set: {"b", "c", "d", "e", "f"}>

Note that it returns the remaining set.

 

subtract

subtract deletes multiple items at the same time.


irb(main):014:0> s.subtract(['c','d'])

=> #<Set: {"b", "e", "f"}>

Note that change is done to the original set (see the following).

irb(main):015:0> p s

#<Set: {"b", "e", "f"}>

=> #<Set: {"b", "e", "f"}>

 

Also note that for partial existence in the delete list (i.e., the argument to subtract contains some elements that do not exist in the first set), only the elements that exist in the original set will be deleted.


irb(main):016:0> s.subtract(['e','g'])

=> #<Set: {"b", "f"}>

==

The == function checks the equality of two sets.

irb(main):018:0> s2 = Set[2,3]

=> #<Set: {2, 3}>

irb(main):019:0> s3 = Set[3,2]

=> #<Set: {3, 2}>

irb(main):020:0> s2 == s3

=> true

Note that the order of the elements does not matter.

Set Operations

The Set API provides many functions.

+ (or | or union)

The + or | functions return a set that is the union of two sets.

irb(main):022:0> s1 = Set[1,2,3,4]

=> #<Set: {1, 2, 3, 4}>

irb(main):023:0> s2 = Set[3,4,5,6]

=> #<Set: {3, 4, 5, 6}>

irb(main):024:0> s1 + s2

=> #<Set: {1, 2, 3, 4, 5, 6}>

& (or intersection)

The & function returns the intersection of two sets.

(Assume the preceding two sets, s1 and s2, are in scope.)

irb(main):025:0> s1 & s2

=> #<Set: {3, 4}>

intersect?

The intersect? function checks whether two sets intersect (i.e., if there is any common element at all). It returns a Boolean.

irb(main):026:0> s1.intersect?(s2)

=> true

 

disjoint?

disjoint

The disjoint? the function checks whether two sets are disjoint. Two sets are disjoint if they have no elements in common. (It is essentially the exact opposite of intersecting?).


irb(main):027:0> s1.disjoint?(s2)

=> false

- (or difference)

The – function shows the differences between two sets. It returns a set contain any element that is in the first set but not in the second.

irb(main):028:0> s1 - s2

=> #<Set: {1, 2}>

^

The ^ provides a set that contains elements from both sets, but not the common elements.

irb(main):029:0> s1 ^ s2

=> #<Set: {5, 6, 1, 2}>

Subset and superset

If s1 and s2 are sets that are defined as follows

irb(main):030:0> s1 = Set[1,2,3]

=> #<Set: {1, 2, 3}>

irb(main):031:0> s2 = Set[1,2]

=> #<Set: {1, 2}>

the verification of whether s1 is a superset of s2 is done by using the >= function.

irb(main):032:0> s1 >= s2

=> true

The same effect can be achieved with the superset? function.

irb(main):034:0> s1.superset?(s2)

=> true

Note that any set is a subset of itself, and hence s1 >= s1 is true.

irb(main):033:0> s1 >= s1

=> true

 

However, s1 is a proper superset of s1 in this case (a proper superset of a set should be a superset of the set, but should have at least one more element than the corresponding subset), but s1 cannot be a proper superset of itself. To check whether a set (s1) is a proper superset of another set (s2) is done with the > function, as shown next.


irb(main):035:0> s1 > s2

=> true

irb(main):036:0> s1 > s1

=> false

 

There is a corresponding proper_superset? function to check the same.

There are also corresponding functions—such as subset?, proper_subset?, <, and <= —that check the inverse relationship.


irb(main):038:0> s1 <= s1

=> true

irb(main):039:0> s2 <= s1

=> true

irb(main):040:0> s2.proper_subset?(s1)

=> true

irb(main):041:0> s2 < s1

=> true

Flattening and Conversion

A set of sets can be flattened by using the flatten function. (The in place counterpart is flatten?).

irb(main):050:0> s = Set[Set[1,2], Set[3,4], Set[2,3], Set[4,5]]

=> #<Set: {#<Set: {1, 2}>, #<Set: {3, 4}>, #<Set: {2, 3}>, #<Set: {4, 5}>}>

irb(main):051:0> s.flatten!

=> #<Set: {1, 2, 3, 4, 5}>

 

A set can be converted to an array using the to_a function.

irb(main):055:0> Set['a','c','b','e','d'].to_a => ["a", "c", "b", "e", "d"]

Collections will be brought up again in context of iterators. But before beginning on blocks and iterators, you may wish to try some exercises.

 

Exercise .1

Given an array of letters and a word as input, write a program to find out whether the word can be built from the letters contained in the array. Any letter can be used up to as many times as it occurs in the array (i.e., if the word needs three letter a’s, then the array should have at least three letter a’s).

 

Using the program (/) function, show that for the array ['y','z','b','e','a','u','t'] and the word beauty returns true, but ['r','o','u','g','h'] and tough returns false.

 

Exercise 2

Suppose there are two text files that report train timing (in a 24-hour format). The first report provides arrival times to a station (on a particular day), and the second report provides departure times. The file contents are as follows.

arrtime.txt

43UP 8:35

54DN 10:32

32UP 11:52

 

(Solution) Excercise 1


def canformword(arr,word)

arrword = word.chars

arrleft = arr

flag = true

for i in 0...arrword.size

ch = arrword[i]

if !arrleft.include?(ch)

flag = false

break

else

ind = arrleft.index(ch)

arrleft.delete_at(ind)

end

end

if flag

puts 'can form word'

else

puts 'can not form word'

end

end

canformword(['y','z','b','e','a','u','t'], 'beauty')

canformword(['r','o','u','g','h'], 'tough')

(Solution) Excercise 4.2

def timeinmin(tm)

a = tm.split(':')

a[0].to_i * 60 + a[1].to_i

end

def gethashfromfile(filename)

thefile = File.open(filename,'r')

h = Hash.new

while (line = thefile.gets)

x = line.chomp.split(/\s+/)

h[x[0]] = timeinmin(x[1])

end

thefile.close

h

end

h1 = gethashfromfile('arrtime.txt')

h2 = gethashfromfile('deptime.txt')

k1 = h1.keys

k2 = h2.keys

kcommon = k1 & k2

knotinboth = (k1 - kcommon) | (k2 - kcommon)

arr = kcommon.to_a

for i in 0...arr.size

stay = h2[arr[i]] - h1[arr[i]]

if stay < 0

puts arr[i] + ': data issue'

else

puts arr[i] + ': stay ' + stay.to_s + ' minutes'

end

end

arr2 = knotinboth.to_a

for i in 0...arr2.size

puts arr2[i] + ': data issue'

end

10DN 13:56

45DN 14:20

deptime.txt

54DN 11:14

45DN 14:28

43UP 8:30

10DN 13:59

35UP 11:52

The data is space separated. The first column is the train number (train id) and the second column is the time. The data is not ordered by train id.

 

Note that the data may have an anomaly, such as the arrival may be later than the departure, and also one train id may be found in one file, but not another. The exercise is to programmatically find the amount of time (in minutes) that each train stays in the station (when possible), and to flag the trains that have data anomalies.

 

Blocks and Iterators

Blocks and Iterators

Intuitively, a block of code is a set of statements that are grouped together. For instance, statements within a function (function body) or the block of code to be executed inside the while loop. However, Ruby has a special block feature that (while still being a group of one or more statements usually enclosed in flower brackets) has very interesting usages. It is especially useful in the context of iterator methods for collections.

 

Perhaps it is best to explain by example to a Ruby newbie.

First of all, a block (and this refers to the block feature, not just any general block of code) can be any chunk of code bounded by do-end keyword pairs or { }. The following are both valid blocks.


do

puts "Hello"

puts "world"

end

and

{ puts 'hello world' }

And while they have a somewhat anonymous function feel about them, these code bodies, by themselves, will not run.

Try the following, however; it will work.

3.times do

puts 'Hello'

end

And it prints this:

Hello

Hello

Hello

There is a convention (but not a syntactic rule) that do-end is preferred in multiline code over {}. (Note that henceforth in the book, the {} variation is usually used rather than the do-end variety).

 

What happened here?

The number 3 is a Fixnum object on which the times method has been called. The block (beginning with do and ending with end) has been passed as an argument to the times method. The code block argument has been executed that many times. times here is an iterator method that takes a block as an argument and executes it repeatedly (the number of repetitions depends on the context).

 

Associating Blocks with Functions

Associating Blocks

Problem

Suppose you have a rather long function in which you are to repeatedly perform a set of actions on a variable (or variables) as it changes its state through the course of the function. Think of putting debug messages, which prints the variable name, its current value, and also some kind of marker that indicates the relative position of this message within the function. For example, printing messages like this:

 

X is now 3 before the iteration

X is now 5 inside the if statement

It would be nice if you could pass the value of x and the position marker string

(e.g., “before the iteration” or “inside the if statement”) and that subfunction, called from

the right places print those messages nicely for you.

 

Using a separate function for that purpose seems a bit heavy-handed. Besides, your project may have policies against creating debug functions for the deliverable code. How do you create such a subfunction without seeming like creating a function?

 

Note This is just one of the scenarios. There may be other situations where such a subfunction (sort of) may be useful for a purpose very different from debugging.

 

Solution

One very handy answer for such scenarios is associating the function with a block and calling the subfunction action (This is not the official term. I am using it here for illustrative purposes. The official term is block.) wherever required within the original function (even at multiple places), using the keyword yield.

Such a function can be defined as follows.


def check1

puts "beginning"

yield

puts "end"

end

104

 

This yield signifies a call to a block (executing a code chunk of the block) that is associated with this function at that point in the function.

When you call the function, you have to pass the block in such a way that the beginning of the block (either the keyword do, or the {) should start on the same line as the function name (any extra arguments should come before the block).

 

So the following code is valid.


def check1

puts "beginning"

yield

puts "end"

end

puts 'outside the function'

check1 do

puts 'ok'

end

It produces the following.

outside the function

beginning

ok

end

The last part could have been written like this:

check1 {

puts 'ok'

}

Or like this:

check1 do puts 'ok' end

Or like this:

check1 { puts 'ok' }

And it would still work well.

But the following won’t work.

check1

do

puts 'ok'

end

 

Note that the function could have been defined with a signature involving a reference to a block, like this:

 

def check1(&block)

def check

It should work in the same way as the original function when properly invoked. This is because, even for the original function, the block was working as an implicit argument. A method doesn’t need to specify the block in its signature in order to receive a block parameter.

 

Note that if you wish to explicitly define the &block argument, it should come at the end (after other arguments, if any) in the signature.

The following is an example of a function that takes an argument and also uses a block.


def check2(name)

puts "processing #{name}"

yield

puts "end"

end

puts 'outside the function'

check2 ('abcd') {

puts "Hello"

}

When run, it should produce the following.

outside the function

processing abcd

Hello

end

In this sense, a block can be thought of as simply a chunk of code and yield allows you to inject that code at some place in a method.

 

Adding Arguments to a Block

Adding Arguments

Problem

You want to pass arguments to a block to make your code extra efficient.

Solution

Blocks can have their own arguments. (It can be used very effectively to write small and succinct code, which nevertheless can accomplish a great deal). The following is an example.


def check3(id)

puts "processing empid #{id}"

yield 'Nadia'

puts "end"

end

puts 'outside the function'

check3 (2) do |str|

puts "Hello #{str}"

end

When run, it should produce the following.

outside the function

processing empid 2

Hello Nadia

end

 

Note that id is an argument to the function, but str is an argument to the block; the arguments are separate, and it is possible to have a block with argument and a function that does not take an explicit argument). I mention explicit argument because the block itself is an implicit argument to the function.

 

Note that the construct is { | arg1, arg2, ...| <code body of block> }.


do-end can be used in place of {}, and the block can span multiple lines.

An example of a multi-argument block is as follows.

def multipl

yield 3,4

end

multipl { |a,b| puts a * b }

It should produce this:

12

The preceding code can be written slightly differently to use a return value.


def multipl

value = yield 3,4

puts "value is " + value.to_s

end

multipl { |a,b| a * b }

And it will produce this:

value is 12

 

This is a demonstration of how a block can return a value (the return value from the last statement executed), which may be used in the associated function.

 

Initializing and Finalizing Code

Finalizing Code

Problem

How do you initialize variables for the whole program or execute initializing/finalizing code in Ruby?

Solution

You might have seen the use of these already in an earlier discussion (in the context of language elements). But since this is related to a block feature, it is elaborated here in a little more detail. Every Ruby source file can declare blocks of code to be run as the file is being loaded (the BEGIN blocks) and after the program has finished executing (the END blocks). They are in the following form.


BEGIN {

begin code

}

END {

end code

}

 

A program may include multiple BEGIN and END blocks. BEGIN blocks are executed in the order they are encountered. END blocks are executed in reverse order.

 

As an example, the following code


BEGIN { x = 'a' ; puts x }

BEGIN { y = 'b' ; puts y }

puts 'general code'

END { a = 'x'; puts a }

END { b = 'y'; puts b }

produces this:

a

b

general code

y

x

This was a demonstration of the order of execution. However, the real use case of such blocks are less dramatic (and possibly more useful).

Imagine that you are working with a lot of CSV files. Very likely, you will get down to splitting strings using a comma as the separator in a lot of places in your code. In such a case, it may save you much hassle (and typing) if you set a default separator for a split in the BEGIN block for the entire code, as follows.

BEGIN { $; = ',' }

 

And then, use the split function on strings, without mentioning the separator explicitly.

line.split #instead of line.split(',')

 

Iterating over Data

Iterating over Data

Problem

You need to perform operations on each item in a collection (e.g., each item in an array needs to be multiplied by 2). This may be done by using a for loop and accepting each element of the array, one by one, doing the operation, and possibly putting the result back into the array again (or putting it in another array for the result—and this array needs to be created first).

 

As far as coding goes, it would be easier if there was a simpler method or program construct where you just mention (a) which array to work on and (b) which operation to perform on the elements of the array.

 

There are other operations that require the collection in its entirety for the operation, but individual elements still participate; for example, sorting the elements of an array based on their values (where individual elements may need comparison with one another in some form). Again, a more traditional solution would require a bit of coding (and the associated debugging, as required). Since sorting is a fairly common operation, it would be nice if a construct existed whereby you specify the array and the operation (sort in this case) and things are done for you.

 

Ruby provides a lot of iterators that address this scenario perfectly.

Solution

Iterators are essentially methods that execute a block of code multiple times. They are usually used with collections, to perform some function, taking each element of the collection as an argument in turn.

 

Some iterators can also work with ranges. Ranges can be considered a sequence. For instance, the range 0..9 includes the numbers 0,1,2,3,4,5,6,7,8,9. An iterator can iterate through these numbers, in turn, and perform some action/check using each of them as an argument.

Some iterators are discussed next.

each

 

Each is an iterator that works with a range, as well as collections like arrays, hashes, or sets. 'each' (like any other iterator) takes a block as a parameter. The block itself takes a parameter and performs the action specified in the code body of the block, following that parameter.

 

The block parameter gets the value of each of the elements of the collection, in turn. (That is how iterators are designed: the block of the iterator gets passed the collection elements, in turn). In a range, instead of collection elements, it is the numbers (or other things, if it is a non-number range) in the sequence that gets passed in turn.

It’s time for an example.

 

The following code is supposed to take the numbers 1,2,3,4 and 5 in turn and print the square of each of the numbers (followed by a new line).


(1..5).each {|i| puts i * i}

And so it does.

1

4

9

16

25

It works very similarly for a set.

require 'set'

Set['a','b','c'].each{|x| puts x}

That produces this:

a

b

c

A set has a 'reverse_each' iterator that traverses elements in reverse occurrence order. This is an example of its usage:


Set[1,2].reverse_each{ |i| puts i * 2}

It should produce the following and also return the set.

4

2

For an array, however, it is a little trickier. It has the each iterator to traverse thorough the elements of the array.

[3,2,5].each {|i| puts i * 2}

And that produces the following.

6

4

10

110

 

But it also has a few other variations. One is 'each_index' (not applicable to sets or hashes), which is used to traverse through the indexes.

 

The following code

[3,2,5].each_index {|i| print i, ","}

produces this:

0,1,2,

(Note that here print is used with two arguments of different types.)

Another is 'reverse_each', which traverses the elements in the opposite order. Hence, the following


[3,2,5].reverse_each {|i| puts i * 2}

should produce this:

10

4

6

 

For a hash, it gets even better (in the sense that it has more variations of each). There is 'each', 'each_pair' (a synonym for 'each'), 'each_key', 'each_value', and 'reverse_ each'. Note the following run in irb (for the hash { 'a' => 100, 'b' => 200 }).


irb(main):001:0> h = { 'a' => 100, 'b' => 200 } => {"a"=>100, "b"=>200}

irb(main):002:0> h.each {|key, value| puts "key #{key} has value #{value}" }

key a has value 100

key b has value 200

=> {"a"=>100, "b"=>200}

irb(main):003:0> h.each_key {|key| puts key }

a

b

=> {"a"=>100, "b"=>200}

irb(main):004:0> h.each_value {|value| puts value }

100

200

=> {"a"=>100, "b"=>200}

'each' here takes two arguments, which gets the key and value for each hash pair. The name of the arguments does not matter. (You could use |k, v| for instances). The first argument gets the key and the second argument gets the value.

 

It is simpler for 'each_key' and 'each_value'. They work with only one argument.

'reverse_each' works in occurrence order.

{1 => 'a', 2=> 'b'}.reverse_each{|k,v| puts k * 2}

That produces the following.

4

2

step

This is an iterator, which is particularly applicable to range, and not to collections.

 

As you have seen, each (for range) takes one item from the sequence in turn. For an integer sequence like 0..9, it would take, 0,1,2, and so on. However, there may be a case where we do not need each item, but alternate ones. The step can be useful in such situations.

 

The following code


(0..9).step(2){|i| puts "even number : #{i}"}

produces this:

even number : 0

even number : 2

even number : 4

even number : 6

even number : 8

Step could also be more than 2. Note that it can work with non-numeric ranges also.

('a'..'e').step(2){|i| puts "letter : #{i}"}

That should produce the following.

letter : a

letter : c

letter : e

select and reject

 

These are also two well-known iterators. 'reject' is the exact opposite of 'select'. 'select' returns a new collection (which may not be of the same type as the original one)—with elements or items—that satisfies the condition given in the block code. 'reject' returns one—with elements or items—that does not satisfy the condition. Both have in place versions ('select!' and 'reject'), but the in-place versions are not available for the range.

 

It really is quite intuitive, when you see them in action. In a range, it returns an array. The following code


digits = 0..9

ret = digits.select {|i| i < 5 }

puts digits

runs as follows.

irb(main):001:0> digits = 0..9

=> 0..9

irb(main):002:0> ret = digits.select {|i| i < 5 }

=> [0, 1, 2, 3, 4]

irb(main):003:0> puts digits

0..9

=> nil

 

It has selected items that are less than 5, as expected. Note that the original range is intact.

'reject' in this case does just the opposite (i.e., rejects items that are less than 5).

irb(main):002:0> ret = digits.reject {|i| i < 5 } => [5, 6, 7, 8, 9]

 

For Arrays

For Arrays

For an array (and set and hash), the in place versions are also available.

The following code run demonstrates the 'select' and 'select!' applied to an array.


irb(main):001:0> a = [1,2,3,4,5]

=> [1, 2, 3, 4, 5]

irb(main):002:0> b = a.select { |num| num.even? }

=> [2, 4]

irb(main):003:0> print a

[1, 2, 3, 4, 5]=> nil

irb(main):004:0> c = a.select! { |num| num.odd? }

=> [1, 3, 5]

irb(main):005:0> print a

[1, 3, 5]=> nil

Eventually, 'reject' has the opposite effect of 'select'.

irb(main):006:0> [1,2,3,4,5].reject { |i| i.even? } => [1, 3, 5]

 

For Hashes


Note that here the block takes two arguments (although both are not always used).

irb(main):001:0> h = { 'a' => 100, 'b' => 200, 'c' => 300 } => {"a"=>100, "b"=>200, "c"=>300}

irb(main):002:0> h.select {|k,v| k > 'a'} => {"b"=>200, "c"=>300}

irb(main):003:0> h.select {|k,v| v < 200}

=> {"a"=>100}

irb(main):004:0> print h

{"a"=>100, "b"=>200, "c"=>300}=> nil irb(main):005:0> h.select! {|k,v| k > 'a'} => {"b"=>200, "c"=>300} irb(main):006:0> print h

{"b"=>200, "c"=>300}=> nil

irb(main):007:0> h.reject! {|k,v| v < 300}

=> {"c"=>300}

irb(main):008:0> print h

{"c"=>300}=> nil

For Sets

Note that for sets, the non-in-place versions return an array, not a set.

iirb(main):001:0> require 'set'

=> true

irb(main):002:0> s1 = Set[1,2,3,4]

=> #<Set: {1, 2, 3, 4}>

irb(main):003:0> s2 = s1.select { |i| i.even? }

=> [2, 4]

irb(main):004:0> p s1

#<Set: {1, 2, 3, 4}>

=> #<Set: {1, 2, 3, 4}>

irb(main):005:0> s3 = s1.reject { |i| i.odd? } => [2, 4]

irb(main):006:0> s4 = s1.reject! { |i| i.even? }

=> #<Set: {1, 3}>

irb(main):007:0> p s1

#<Set: {1, 3}>

=> #<Set: {1, 3}>

irb(main):008:0> print s3

[2, 4]=> nil

 

map or collect

'map' and 'collect' are also useful iterators. There are also in place ('map!' or 'collect!') versions. The non-in-place versions return an array, even when applied to a hash or a set. The hash versions take two arguments. The in-place version does not apply to a hash. They apply a certain function (the code body) to each of the elements in turn and return an array, which is a collection of the results.

 

For Arrays

 Arrays


irb(main):001:0> a = [3,4,5]

=> [3, 4, 5]

irb(main):002:0> a.map {|i| i + 2}

=> [5, 6, 7]

irb(main):003:0> a.collect {|i| i + 2}

=> [5, 6, 7]

irb(main):004:0> print a

[3, 4, 5]=> nil

irb(main):005:0> a.map! {|i| i + 2}

=> [5, 6, 7]

irb(main):006:0> print a

[5, 6, 7]=> nil

 

For Sets

Note that the non-in-place version returns an array.


irb(main):001:0> require 'set'

=> true

irb(main):002:0> s1 = Set[1,2]

=> #<Set: {1, 2}>

irb(main):003:0> s2 = s1.map {|i| i * 2}

=> [2, 4]

irb(main):004:0> p s1

#<Set: {1, 2}>

=> #<Set: {1, 2}>

irb(main):005:0> s3 = s1.map! {|i| i * 2}

=> #<Set: {2, 4}>

irb(main):006:0> p s1

#<Set: {2, 4}>

=> #<Set: {2, 4}>

 

For Hashes

Hashes

It takes two arguments, as usual. (Note that it does not have an in-place version).


irb(main):001:0> h = {'a' => 1, 'b' => 2}

=> {"a"=>1, "b"=>2}

irb(main):002:0> h.map {|k,v| v + 3}

=> [4, 5]

irb(main):003:0> h.collect {|k,v| v + 3}

=> [4, 5]

irb(main):004:0> print h

{"a"=>1, "b"=>2}=> nil

delete_if and keep_if

 

These are also very useful. As the name suggests, delete_if deletes the elements that satisfy the condition in the given code block (keep_if keeps them). Both of them work in place (even though they don’t have an '!' at the end).

 

For Arrays


irb(main):001:0> a = [3,4,5,8,9]

=> [3, 4, 5, 8, 9]

irb(main):002:0> a.keep_if {|i| i.even?}

=> [4, 8]

irb(main):003:0> print a

[4, 8]=> nil

irb(main):004:0> a.delete_if {|i| i.even?} => []

irb(main):005:0> print a

[]=> nil

For Sets

irb(main):001:0> require 'set'

=> true

irb(main):002:0> s1 = Set[3,4,5,6,7] => #<Set: {3, 4, 5, 6, 7}> irb(main):003:0> s2 = Set[3,4,5,6,7] => #<Set: {3, 4, 5, 6, 7}> irb(main):004:0> s1.delete_if{|i| i.even?} => #<Set: {3, 5, 7}>

irb(main):005:0> s2.keep_if{|i| i.even?}

=> #<Set: {4, 6}>

irb(main):006:0> p s1

#<Set: {3, 5, 7}>

=> #<Set: {3, 5, 7}>

irb(main):007:0> p s2

#<Set: {4, 6}>

=> #<Set: {4, 6}>

 

For Hashes


irb(main):001:0> h = { 'a' => 100, 'b' => 200, 'c' => 300 } => {"a"=>100, "b"=>200, "c"=>300}

irb(main):002:0> h.delete_if {|k, v| k > 'b' } => {"a"=>100, "b"=>200}

irb(main):003:0> h.keep_if {|k, v| v > 100 }

=> {"b"=>200}

irb(main):004:0> print h

{"b"=>200}=> nil

 

sort

The sort function has an iterator form (i.e., using block). In the block, a sort order can be specified (which may be other than the default sort order). If no block is specified, the default sort order is followed.


irb(main):010:0> Set[4,3,5].sort {|a,b| b<=>a} => [5, 4, 3]

irb(main):011:0> { 1 => 'a', 3 => 'c', 2 => 'b'}.sort {|a,b| b<=>a} => [[3, "c"], [2, "b"], [1, "a"]]

irb(main):012:0> [2,6,4,5].sort {|a,b| b<=>a}

=> [6, 5, 4, 2]

irb(main):013:0> [2,6,4,5].sort

=> [2, 4, 5, 6]

irb(main):014:0> Set[4,3,5].sort

=> [3, 4, 5]

irb(main):015:0> { 1 => 'a', 3 => 'c', 2 => 'b'}.sort => [[1, "a"], [2, "b"], [3, "c"]]

 

Note that upon sort, set returns an array and hash returns an array of arrays (the inner arrays being key-value pairs).

This concludes our current discussion on blocks and iterators. Thus far, quite a few topics have been covered, which (collectively) can be used to tackle some serious programming tasks. You may wish to try your hand at the exercises offered next.

 

Exercise 1

You are given a hash in which the key is a student’s name and the value is the student’s total marks in an exam. Suppose anyone receiving more than 599 (i.e., 600 or more) is placed in the first division. Write a program to print the name and the marks of each student, in a nicely formatted manner, and include ‘First Division’ in the result if he/she achieved first division.

e.g given {"Abani Sen" => 650, "Dora Pride" => 573}

It should print something like this:

Abani Sen: Marks obtained 650 : First Division

Dora Pridle: Marks obtained 573

Use at least one iterator in the solution.

 

Exercise 2

You are given the following hash.


h = {

"Abani Sen" => 650,

"Dora Pridle" => 573,

"Sana Chowdhury" => 824,

"Pritish Panda" => 732

}

Print the name and marks, sorted by marks, with highest marks at the top.

Exercise 3

For entry into engineering or medicine, when the score is calculated, some of the credit is taken from the marks in the exam discussed earlier. These are the rules:


• up to 500 marks: no credit toward entrance

• 501 to 600: 10 credits

• 601 to 700: 20 credits

• 701 to 800: 40 credits

• 801 onward: 70 credits

 

Write a program to determine and print the credits each of the students received 

(Solution) Exercise 1


h = {

"Abani Sen" => 650,

"Dora Pridle" => 573,

"Sana Chowdhury" => 824,

"Pritish Panda" => 732

}

h.each { |k, v|

str = ''

str = ' : First Division' if v > 599

puts "#{k} : Marks obtained #{v}#{str}"

}

(Solution) Exercise 2


h = { "Abani Sen" => 650, "Dora Pridle" => 573, "Sana Chowdhury" => 824, "Pritish Panda" => 732 }
​ arr = h.invert.sort{|a,b| b<=>a} arr.each { |x| str = '' str = ' : First Division' if x[0] > 599 puts "#{x[1]} : Marks obtained #{x[0]}#{str}" } (Solution) Excercise 5.3 h = 
​{ "Abani Sen" => 650, "Dora Pridle" => 573, "Sana Chowdhury" => 824, "Pritish Panda" => 732 }
​ h.map {|k,v| case v when 0..500 puts "#{k} : no credit" when 501..600 puts "#{k} : credit : 10" when 601..700 puts "#{k} : credit : 20" when 701..800 puts "#{k} : 
​credit : 40" else puts "#{k} : credit : 70" end