RSS Feed

Methods on the Blocks

June 8, 2012 by szabcsee

Ruby's Methods on the Blocks

In my next lessons with Lynda I went through a few new methods which are extremely useful. Today we check out .merge, .collect/map, .sort, .inject. These methods are extremely useful to deal with our code blocks and data.

Merge

What merge does can be seen in this example:

 
>> h1 = { "a" => 111, "b" => 222}
=> {"a"=>111, "b"=>222}
>> h2 = {"b" => 333, "c"=> 444}
=> {"b"=>333, "c"=>444}
>> h1.merge(h2)
=> {"a"=>111, "b"=>333, "c"=>444}

We took two hashes h1 and h2 and merge them together. Where the same key existed (“b”), the later hash’s value stayed. Of course we can also set this as you can see in the following example:

>> h1.merge(h2) {|key,old,new| new }
=> {"a"=>111, "b"=>333, "c"=>444}
>> h1.merge(h2) {|key,old,new| old }
=> {"a"=>111, "b"=>222, "c"=>444}
>>

We can tell to Ruby that merge and do this and that when you find a conflict. For example:

h1.merge(h2) do |key, old, new|
   if old < new
       old
   else
       new
   end
end

It tells Ruby, if you find a conflict, check and if the old one is smaller than the new one, keep the old one, otherwise keep the new one. So if there is a conflict pick the one with the lower value.
But we can write this much more simple.

h1.merge(h2) {|k,o,n| o < n ? o : n }

This is just a shorthand, does exactly what the one above.

Collect

Let’s take a look at the collect method. Actually it’s synonymous with map.

 
>> array = [1,2,3,4,5,6]
=> [1, 2, 3, 4, 5, 6]
>> array.collect {|i| i + 1}
=> [2, 3, 4, 5, 6, 7]
>> ['apple','banana','orange'].map {|fruit| fruit.capitalize}
=> ["Apple", "Banana", "Orange"]
>> ['apple','banana','orange'].map {|fruit| fruit.reverse}
=> ["elppa", "ananab", "egnaro"]

So what collect does it takes each element of an array, does to it what we tell and then puts it in a new array. But here is something important:

['apple','banana','orange'].map {|fruit| fruit.capitalize if fruit == 'orange'}
=> [nil, nil, "Orange"]
>>

You can see that before with the reverse collect (or map) did it to each element and returned. But in case of the fruit == orange we can see that it returned an array with 2 nils and the orange. So we need to define that we do need some value to be returned even-though our condition is not fulfilled.

['apple','banana','orange'].map do |fruit|
        if fruit == 'banana'
         fruit.capitalize
       else
         fruit
       end
end

This gives us what we want: ["apple", "Banana", "orange"] . We need to be explicit when using map/collect.
Because there some important rules with collect/map:

  • the number of items goes in (to be processed) equal to the number of items that comes out
  • whatever we put in (array, range, hash) we will get an array back

Let’s take a look as we can do some crazy things with hashes.

 
>> hash = {"a" => 111, "b" => 222, "c" => 333}
=> {"a"=>111, "b"=>222, "c"=>333}
>> hash.map {|k,v| k}
=> ["a", "b", "c"]
>> hash.map {|k,v| v * 20}
=> [2220, 4440, 6660]
>> hash.map {|k,v| "#{k}: #{v * 20}"}
=> ["a: 2220", "b: 4440", "c: 6660"]
>>

As you can see first we mapped the keys of the hash and got them back in an array, then we took the values and multiplied by 20 and got back in an array. Finally we mapped the keys and values and put the keys and the multiplied values in a string and got back an array with strings of keys and values.
We can use exclamation mark to modify the variable itself:

foo = "A STRING"
foo.downcase!
puts foo
a string

Sort

Comparison operators return -1 or 0 or 1 depends on something is less then, equal or bigger than something. Now we are really going to put everything in order here :)

>> 1  2
=> -1
>> 2  1
=> 1

We can see the values we get back is -1, 0 and 1 or move left, nill or move right. Let’s see it in practice:

 
>> array = [3,1,5,2,4]
=> [3, 1, 5, 2, 4]
>> array
=> [3, 1, 5, 2, 4]
>> array.sort {|v1,v2| v1  v2 }
=> [1, 2, 3, 4, 5]
>> array
=> [3, 1, 5, 2, 4]
>> array.sort
=> [1, 2, 3, 4, 5]
>> array.sort {|v1,v2| v2  v1 }
=> [5, 4, 3, 2, 1]
>> array.sort.reverse
=> [5, 4, 3, 2, 1]
>> array.reverse
=> [4, 2, 5, 1, 3]
>>

Of course we have a lot of convenient method built in like sort and .sort.reverse but we can fine tune our parameters also to get exactly what we need.
Let’s see how it works with strings.

 
>> fruits = ['banana', 'apple', 'orange', 'pear']
=> ["banana", "apple", "orange", "pear"]
>> fruits.sort
=> ["apple", "banana", "orange", "pear"]
>> fruits.sort {|fruit1,fruit2| fruit1.length  fruit2.length}
=> ["pear", "apple", "orange", "banana"]
>> fruits.sort_by {|fruit| fruit.length}
=> ["pear", "apple", "orange", "banana"]

.sort_by tells the method to get in there and grab the value i should use for the comparison. What about hashes. Well Ruby turns every hash into an array before sorting them. So you can’t talk to them like hashes when sorting.

 
>> hash
=> {"a"=>111, "b"=>222, "c"=>333}
>> hash.to_a
=> [["a", 111], ["b", 222], ["c", 333]]
>> hash.sort {|item1,item2| item1[0]item2[0] }
=> [["a", 111], ["b", 222], ["c", 333]]

Inject

Inject is accumulating values. And we need something where we accumulate our values. Most programmer call this storage memo.

>> (1..10).inject{|memo,n| memo+n}
=> 55
>> array = [*1..10]
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>> sum = array.inject {|memo, n| memo + n}
=> 55
>> sum
=> 55
>> sum = array.inject(100) {|memo, n| memo + n}
=> 155

We can see in the beginning that the process iterates from 1 to 10 and add each element to memo:
memo = 0
memo = memo + 1
memo = memo + 2

memo = memo + 10
and it comes out as 55
Later we say inject(100) which means memo is already 100 in the beginning.
If that would be it, what inject knows, we could use something more simple instead (and probably it would be called .sum) but of course we can get more sophisticated.

 
>> product = array.inject {|memo, n| memo * n}
=> 3628800
>> product = array.inject(2) {|memo, n| memo * n}
=> 7257600
>>

What is important is that when our process returns a nill and we put it in memo, that can break the whole process as at the next step it can not multiply anything with nill. But how to use this on strings?
Let’s see. This short code checks the length of all elements of the array and each time checks if fruits is longer than memo, if not, memo stays in memo if it is then update it with memo. This way we get the longest fruit name.

=> ["banana", "apple", "orange", "pear"]
>> longest_world = fruits.inject do |memo, fruit|
?> if memo.length > fruit.length
>> memo
>> else
?> fruit
>> end
>> end
=> "orange"

We can do all sorts of checking, comparison with .inject. I think this is enough for today.


No Comments »

No comments yet.

Leave a Reply

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

Connect with Facebook

*

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