Below, we have to modify the [] method in SongList, so that it can accept a string parameter and return to the song that is heading. It is easy to achieve: We have an array of objects containing a lot of Song objects, and we only need to loop over the entire array, and find the one of the matches.
Class Songlist
DEF [] (key)
IF key.kind_of? (Integer)
Return @songs [key]
Else
For i in 0 ... @ Songs.Length
Return @songs [i] if key == @songs [i] .name
end
end
Return NIL
end
end
This is already working, and it seems very conforming to convention: use a for loop to traverse an array.
Is there a more natural way?
Of course, we can use Array's Find method.
Class Songlist
DEF [] (key)
IF key.kind_of? (Integer)
Result = @songs [key]
Else
Result = @ Songs.Find {| Asong | Key == asong.name}
end
Return RESULT
end
end
We can use the IF modifier to make the code shorter.
Class Songlist
DEF [] (key)
Return @songs [key] if key.kind_of? (Integer)
Return @ Songs.Find {| Asong | Asong.name == Key}
end
end
The method Find is an iterator, which repeatedly performs a given block. Blocks and iterators are more interesting in Ruby. We will further discuss these features later.
Implement iterators
A Ruby iterator is a simple method that can receive code blocks. The first look, the block in Ruby is the same as C, Java, Perl, but actually there is different.
First, the block next to the method call in the source code, and writes the last parameter of this method to the same row. Second, this block will not be implemented immediately, and Ruby will first remember the context of this block (local variable, current object, etc.), then enter the method, which is also the place where Magic begins.
In the method, this block will call execution with Yield, just like this block is the method itself, every time the Yield is executed in the method, this block will be called. When this block is executed, the control will be handed over to the statement behind Yield (Yield is from a language: CLU). Let's take a small example.
Def Threeetimes
Yield
Yield
Yield
end
Threeetimes {puts "hello"}
Products:
Hello
Hello
Hello
This block (defined by two braces) assigns a method threetime, in this method, Yield executes 3 times, each executing it will call a given block, ie print a welcome statement. What makes blocks become interesting that you can deliver parameters to blocks and get results from blocks. In the following example, we will get less than a specified value of the Fibonacci number.
DEF Fibupto (max)
I1, I2 = 1, 1 # Parallel Assignment
While I1 <= MAXYIELD I1
I1, I2 = I2, I1 I2
end
end
FIBUPTO (1000) {| f | print f, "}
Products:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
In this example, Yield receives a parameter, which will pass to the specified block when executed. In the definition of the block, the parameters are enclosed in both vertical lines and placed in the forefront. In this example, f is used to receive parameters passed by Yield, so this block can print this sequence. A block can accept any parameters. What will happen if a block is parameter and the number of parameters passed in Yield? Coincidentally, this is the same as the principle that we talked in parallel assignment (if a block only receives a parameter, the parameters provided by Yield are more than 1, then these parameters will be converted to an array.) Pass The parameters given to a block may be existing local variables. If this is the case, the new value of this partial variable (if it is modified in block), it will be retained after the block exits, which may have some side effects, but This has a performance test.
A block can also return a result to the method of calling it. The value of the last expression in this block will return to the method, and the Find method in Array is working. (Find is defined in enumerable, being inserted into class array)
Class Array Def Find for I in 0 ... size value = self [i] return value if yield (value) end return nil end end [1, 3, 5, 7, 9] .find {| v | v * v > 30} »7
This array is transmitted to the specified block, and this method returns the currently corresponding element value if this block returns True. If the value is not met, the NIL is returned. This method shows the benefits of the iterator, the Array class only makes yourself, access array elements, and application code is only focused on special needs.
Other common iterators in the collection object in Ruby, which are each and collect. Each can be considered the simplest iterator, which will call each element of the collection.
[1, 3, 5] .each {| i | PUTS I}
Products:
1
3
5
The other is Collect, which is similar to EACH, which passes the elements in the collection to a block, and then returns a new array containing the result of the result in the block.
["H", "a", "l"]. Collect {| x | x.succ} »[" i "," b "," m "]
Comparison of Ruby and C , Java
This is worth spend some time to compare the iterator in Ruby, C , Java. In Ruby, iterator is a simple way, whenever it produces a new value, will call the Yield method. Using iterators only need to pass a block to this iterator, do not need to create a secondary class like C , Java to process the status of the iterator. From this point and some other features, Ruby is a transparent language. When you write a program, you only need to pay attention to the functionality that can be implemented without having to write scaffolding to support language. The iterator is not only used in a collection structure such as an array and hash, but also returns the sequence value in the Fibonacci example above, and the input and output classes in the Ruby also use iterator, which implements iterator interfaces, each time Returns the next line of I / O stream. f = file.open ("testfile")
F. Each Do | LINE |
Print Line
end
f.close
Products:
This is line one
This is line two
This is line three
And so on ...
Let's take a look at another iterator. SmallTalk also supports iterators. If you calculate the sum of elements in an array with a SmallTalk language, you can:
Sumofvalues "SmallTalk Method"
^ Self Values
INJECT: 0
INTO: [: Sum: Element | SUM Element Value]
INJECT works this: When the first specified block is executed, the SUM is set to the parameters of the Inject (this example is 0), and ELEMENT is set to the first element of the array. When Block is executed in the future, the value of the SUM is set to the value returned after the last Block execution. In this way, SUM can record the value of the total number of last INJECTs is the value returned after the Block last executed.
Ruby does not have an Inject method, but we can easily write one, in this example we join the Array class.
Class Array Def Inject (N) Each {| Value | N = Yield (n, value)} n end def SUM INJECT (0) {| n, value | n value} end def Product inject (1) {| n, Value | n * value} End end [1, 2, 3, 4, 5] .sum »15 [1, 2, 3, 4, 5] .product» 120
Although Block is often used in an iterator, there are other useful uses.
Use blocks in transaction processing
Block can also be used as a code that must be run under certain transaction control. For example, you often open a file, do some processing, and then make sure that the file will be closed at the end. Although we can implement conventional methods, we can let file objects are responsible for turning off it. A simple example is as follows (ignored error processing, etc.):
Class file
DEF file.openandProcess (* args)
f = file.open (* args)
Yield f
f.close ()
end
end
File.openandProcess ("Testfile", "R") DO | AFILE |
Print While Afile.GetSend
Products:
This is line one
This is line two
This is line three
And so on ...
This small example expounds several technical points. Method OpenandProcess is a class method that can be used independently of any File object separately, ie unsenable instances of the class. We use * Args in the parameter list of this method, which means that all parameters during the call will be passed to this method as an array. This parameter is passed to the File.Open method.
Once this file is opened, OpenandProcess will call Yield and pass the open file object to this block. This file will be turned off when Block returns. In this case, the task of closing the file is transformed from the user using the file object to the file itself.
Finally, this example uses DO iND to define a block, which defines a block with two parenthesis, which is only a priority difference, which will be discussed later.
Let the files manage their own life cycle, and Ruby's own File class provides such support. If a block is specified when the file.open is called, this block will be executed using the file object as a parameter, and then this file will be turned off by the file object after the block ends. This is very interesting, that is, the file.open method has two versions, one accepts Block, one does not accept Block. This method only returns an open file object when you do not specify the block calling this method. Method KERNEL :: Block_given? Provides the possibility of implementing this feature, if the call is called, this method will return TRUE so you can also implement yourself Open method:
Class file
DEF file.myopen (* args)
AFILE = file.new (* args)
# I i'S a block, pass in the file and close
# THE FILE WHEN IT RETURNS
IF block_given?
Yield Afile
Afile.close
AFILE = NIL
end
Return Afile
end
end
This article is taken from: http://www.ruby-cn.org/book/programmingruby/tut_containers.html