Lab 9: Arrays and Iterators
Objectives
- Practice the basics of array access and manipulation
- Use arrays to hold an indeterminate amount of data
- Explain the semantics of the array
collect,select,reject, andinjectiterators - Use iterators and blocks to examine array contents
- Use iterators and blocks as an alternative to loops
Introduction
Arrays are invaluable for when you need to easily gather, inspect, or manipulate multiple pieces of related data. The Ruby syntax for using arrays is borrowed from and found in many other popular modern programming languages, and deserves some attention and practice.
Is rand really Random?
We’ve used the rand method a few times now to help us generate random values in our programs. Generating random numbers is something we often need to do, and it’s fairly important to get the job done properly (i.e., that the numbers we get are really random!)
To figure out if the numbers generated by rand really are random, we can enlist the help of arrays again. For this exercise, you’ll use rand to simulate rolls of an N-sided dice, and an array to store the results of those rolls. After the simulation, your program should print out the number of times each outcome occurs, along with the corresponding percentage of the total number of rolls.
The following is a sample session with a completed program:
Enter the number of sides for your dice: 6 Enter the number of rolls to simulate: 10 1 rolled 2 times (20.0%) 2 rolled 1 times (10.0%) 3 rolled 0 times (0.0%) 4 rolled 5 times (50.0%) 5 rolled 2 times (20.0%) 6 rolled 0 times (0.0%)
Exercise 1
Implement the program described above (allowing user input to specify both the number of faces of the dice and the number of rolls) and save it in the file dice_stats.rb.
How can you use your program to prove to yourself that the random number generator is sufficiently random? How many rolls do you need for a 6 sided dice to get the percentages to get close? Are the results the same each time (for the same input)?
Scaled Histograms
Remember the program we wrote together in class to print out bar graphs corresponding to numbers input by the user? For this first exercise, you’ll write a somewhat improved version of the program.
The first improvement will a cosmetic one: your program should allow the user to enter the character to use in drawing the bar graphs (the program we wrote in class used the # character).
The second improvement is a bit more useful: your program should scale the values of the bar graph so that the entire bar graph fits into a width specified by the user. This can be done by determining the maximum value entered by the user and then using it to either scale values “up” or “down” to the specified width. A little bit of arithmetic wizardry is called for here!
The following are sample sessions with a completed program:
Enter a character to use for the graph: * Enter the width (in characters) of the graph: 20 Enter a value in the graph (-1 to end): 5 Enter a value in the graph (-1 to end): 2 Enter a value in the graph (-1 to end): 10 Enter a value in the graph (-1 to end): 1 Enter a value in the graph (-1 to end): -1 Here's your graph: ********** **** ******************** **
Enter a character to use for the graph: @ Enter the width (in characters) of the graph: 20 Enter a value in the graph (-1 to end): 100 Enter a value in the graph (-1 to end): 90 Enter a value in the graph (-1 to end): 50 Enter a value in the graph (-1 to end): 18 Enter a value in the graph (-1 to end): 65 Enter a value in the graph (-1 to end): -1 Here's your graph: @@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@@@@@@ @@@ @@@@@@@@@@@@@
Exercise 2
Implement the scaled histogram program, described above, and save it in a file named scaled_bars.rb. Note that your program should use the each iterator (and not a while loop) to iterate over array values.
Fun with Array Iterators
While the each iterator is great for when we need to simply go through each element of an array in turn, we frequently need to do more interesting things with arrays. That’s where the rhyming collect, select, reject, and inject iterators might come in handy. Just as methods return values (as we recently discussed in class), blocks can also produce values to be used by an iterator.
Take the following code, for example:
arr = [1, 2, 3, 4, 5] arr_times_two = arr.collect {|x| x*2 } puts arr_times_two # The above code prints the following: # 2 # 4 # 6 # 8 # 10
In particular, examine the block {|x| x*2}. The block does nothing more than multiply the variable x by 2 — this value (being the last value in the block) becomes the return value of the block. The collect iterator runs the block with each value in the arr array and collects those values in a new array, which is assigned to arr_times_two.
Now go back and read that last paragraph a few more times.
Moving on. select and reject run the block with each value in the array and use the value of the block to determine whether or not to keep the value. select will return a new array containing all the elements for which the block does not return false. reject returns a new array with those elements for which the block does return false. The following sample code illustrates:
arr = [1, 2, 3, 4, 5] arr_even = arr.select {|x| x%2 == 0 } # arr_even now contains [2, 4] arr_odd = arr.reject {|x| x%2 == 0 } # arr_odd now contains [1, 3, 5]
Finally, we have inject, possibly the most interesting and confusing of the lot. inject passes a block two parameters. One parameter corresponds to a different value from the array each time the block is called, as before, but the other parameter corresponds to the return value from the previous time the block was called. Weird, huh?
The first time the block is called there is no “previous time”, so inject takes an argument that specifies the parameter value to use on the first block invocation. If an argument isn’t given to inject, it uses the first and second values from the array as arguments to the block for the first time. In the end, inject returns the value of the block the last time it is called.
inject is very useful for when you need to reduce the contents of an array to a single value.
Take a look at the following examples of using inject:
nums = [10, 20, 30, 40, 50] strs = ["one", "two", "three", "four"] nums_sum = nums.inject {|sum, x| sum + x } # nums_sum now contains 150 puts nums_sum nums_prod = nums.inject(1) {|prod, x| prod * x} puts nums_prod # nums_prod now contains 12000000 strs_longest = strs.inject do |longest, str| if longest.length > str.length longest else str end end # strs_longest contains "three" strs_sum_lengths = strs.inject(0) do |sum_lengths, str| sum_lengths = sum_lengths + str.length end # strs_sum_lengths contains 15
Didn’t I say blocks and iterators were powerful?
Exercise 3
Go back and rewrite your solution for exercise 2 so that you make use of the inject and collect operators. You should use inject to determine the maximum value entered by the user, and collect to create an array of the bars (corresponding to values entered by the user) before subsequently printing them out. Save your solution in a file named scaled_bars2.rb.
Leave a comment
You must be logged in to post a comment.