CSC/ECE 517 Summer 2008/wiki1 6 arraysandhashes
Arrays and Hashes
Arrays and hashes are built into Ruby. Arrays, of course, are built into Java too, but hashes are only available through library classes. Compare Ruby and Java arrays, as well as hashes. Write equivalent code sequences in the two languages that illustrate the convenience of programming these constructs in both languages.
Arrays
While both Java and Ruby provide built-in support for arrays, they differ in the operations that can be performed on them. Arrays in both languages are objects, though this association is implicit in Java arrays, where arrays are container objects and whose direct superclass is type Object. In Ruby, arrays are easily immediately identifiable as objects and they are represented by the Array class.
Comparison of Common Operations in Ruby and Java
In this section, we provide a side-by-side examination of common array operations in Ruby and compare and contrast them with Java.
Creating an Array
One way to create an array in Ruby is to to use the new class method:
planets = Array.new(8)
This creates an empty array named planets. This is similar to the Java initialization:
String[] planets = new String[8];
Arrays can also be initialized to values in both Java and Ruby. For example, we can initialize an arrays of planets by entering the following in Java, noting the use of curly braces in array initialization:
String[] planets = {"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" };
Some people consider Pluto a planet, but is this arguable. In Ruby, we can perform a similar operation, instead using square brackets:
planets = [ "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" ]
The similarities between Java and Ruby with respect to array creation end here. Ruby provides additional convenience operators on array creation that Java lacks.
For example, Ruby provides an additional way to define an array of strings using the %w notation. It assumes that all elements are strings, but can save typing:
planets = %w[ Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune ]
An array in Ruby can also be created by specifying the default value to each element in the array:
planets = Array.new(8, "planets")
Furthermore, since Ruby offers range capabilities, one can initialize an array using ranges:
digits = Array(1..x)
The above Ruby code creates an array of digits with elements from 1 through 100. The equivalent Java code is far more cumbersome and potentially error-prone, as the programmer must micromanage the details of array indexing:
for (int j = 1; j <= x; j++) digits[j - 1] = j;
Accessing Array Elements
Elements in Java and Ruby are accessed in much the same way, though Ruby again offers a few additional methods for accessing elements of arrays. In Java, one could access the element "Venus" of the planets array as:
planets[0];
In Ruby, the operation is much the same:
planets[0]
In addition, Ruby allows one to index using negative indices, so that:
planets[-1]
returns "Neptune". Finally, Ruby allows the first and last methods to easily get the first and last elements of an array.
Ruby provides several methods, which, in combination with the range operator, provide an elegant way to obtain sub-arrays of an array. In Ruby, for example, we can generate a sub array of the first three elements after "Mercury" of the array with:
some_planets = planets[1..3]
Such tasks can also be accomplished in Java to a certain extent, but require the use of the java.util.Arrays package. The equivalent Java code for the simple operation above, for instance, would be:
String[] somePlanets = java.util.Arrays.copyOfRange(planets, 1, 4)
Note that the Java method is a half-open interval.
Both languages allow the programmer to iterate over the array. In Ruby, this can be accomplished with the use of blocks. Let us print out of the plants in uppercase with a simple one-liner:
planets.each { |p| puts p.upcase }
In Java, one can use the for-each loop instead, which has similar, but somewhat more verbose syntax:
for (String p : planets) System.out.println(s.toUpperCase());
Finally, let's look at what happens when we access an element that doesn't exist in the array, such as index 100. Java will throw an ArrayIndexOutOfBoundsException. Ruby, on the other hand, returns nil.
Checking Equality
Ruby provides the == method to compare arrays for equality. Given the following code:
planets_a = ["Mercury", "Venus", "Earth"] planets_b = ["Mercury", "Venus", "Earth"] planets_c = ["Earth", "Neptune", "Pluto"]
It is easy to see that:
planets_a == planets_b => true planets_a == planet_c => false
While Java provides both == and equals(), these operators and methods derive from java.lang.Object and do not provide the intended result. Given the following code:
planetsA = {"Mercury", "Venus", "Earth"}; planetsB = {"Mercury", "Venus", "Earth"}; planetsC = {"Earth", "Neptune", "Pluto"};
the expressions:
planetsA == planetsB; planetsA.equals(planetsB)
both return false. Instead, to get the desired behavior one must again utilize the package java.util.Arrays, in particular, the java.util.Arrays.equals() method:
java.util.Arrays.equals(planetsA, planetsB)
This is just one example of where Ruby follows the principle of least surprise, meaning the language should behave in such a way as to minimize confusion for experienced users.
Inserting, Removing and Changing Elements
Most static languages, including Java, require you to increase the size of an array in order to add elements to it. In Java, one must create a new array of larger size and copy the elements into the newly created array before adding the new element. To compensate for these deficiencies, Java provides classes like ArrayList, but traditional arrays are still subject to this limitation.
Moreover, while Java provides both System.arraycopy, and convenience methods in java.util.Arrays, the resulting Java code is far from transparent. Ruby, on the other hand, makes such operations as insertion and addition trivial out of the box to implement, largely due to its dynamic handling of arrays.
Let us first change some elements in an array, without affecting its size, by changing "Venus", "Earth", and "Mars" with "Larry", "Curly", and "Moe". In Java:
planets[1] = "Larry"; planets[2] = "Curly"; planets[3] = "Moe";
All of these can be changed simultaneously in Ruby with a single statement, again, with the help of the range operator:
planets[1..3] = %w[Larry Curly Moe]
Let us now assume that "Pluto" has been defined properly as a planet. It should now be added to the end of the planets array. In Java, one must use the library call java.util.Arrays.copyOf, or roll their own loop. In either case, the algorithm requires the creation of a new array. We choose to implement the former approach:
planets = java.util.Arrays.copyOf(planets, planets.length + 1); planets[planets.length - 1] = "Pluto";
The same operation in Ruby is incredibly straight-forward:
planets << "Pluto"
Finally, let us remove "Earth" from the planets array (possibly, it has been destroyed as a result of global warming) in Java. The implementation again is tedious and cumbersome as one must pay attention to the array indexing of each method argument. In fact, a significant amount of time was spent establishing the array boundaries:
String[] new_planets = new String[planets.length-1]; System.arraycopy(planets, 0, new_planets, 0, 2); System.arraycopy(planets, 2 + 1, new_planets, 2, new_planets.length - 2);
In Ruby, one can simply say:
planets.delete "Earth"
or, with absolute indices:
planets.delete_at(2)
Array Summary
To be fair, in Java 6, many of the vanilla array deficiencies have been addresses with additional classes and APIs, in particular, the ArrayList, which provides methods such as clear, clone, and add that mimic operations found in Ruby. Even with these additional classes, however, the number of pre-defined methods on arrays available in Java lack sorely compared with Ruby. For example, Java offers no built-in equivalent to the Ruby method uniq. Finally, in methods that are nearly equivalent between Java and Ruby, the Ruby syntax tends to be less verbose and more declarative.
Hashes
In this section, we provide a side-by-side comparison of common hash operations in Ruby and compare and contrast them with Java.
Creating a Hash Table
One way to create a hash table in Ruby is to use the new class method:
planets = Hash.new
This creates an empty hash table named planets. This is similar to the Java initialization:
import java.util.Hashtable
Hashtable<String, String> Planets = new Hashtable<String,String>();
Hash tables can also be initialized with values in both Java and Ruby.
The following associates "Ring" to "Saturn" and "Big" to "Jupiter" in Ruby.
planets["Ring"]= "Saturn" planets["Big"] = "Jupiter"
or
planets = {"Ring"=> "Saturn","Big"=> "Jupiter"}
As above, the following associates "Ring" to "Saturn" and "Big" to "Jupiter", but in Java.
planets.put( "Ring" , "Saturn" ); planets.put( "Big" , "Jupiter" );
Accessing Table Entries
In the previous examples we created a hashtable named planets in Java and in Ruby. The hashtable contains the names of planets which can be accessed with their corresponding key maps. In this section we are going to explore the differences in accessing the data in each of these hash tables.
To retrieve the entries from the planets table in Java we can do the following:
planets.get("Ring"); planets.get("Big");
The following code could be used to accomplish the same task in Ruby:
planets["Ring"] Planets["Big"]
Both sets of code would retrieve Saturn and Jupiter.
Verifying, Removing, and Replacing Entries
To prevent errors, it is common to verify that an entry or a key is in a table before trying to do an operation on it. The following Java code will check the hash table planets to see whether or not they are empty, it will check for the key "Big" and the table entry "Saturn". It will return true if the entry or key is found and false if it is not.
planets.isEmpty(); planets.contains("Big"); planets.containskey("Big"); Planets.containsValue("Saturn");
Ruby has similar functions which return true if found:
planets.empty? planets.has_key?("Big") planets.has_value?("Saturn")
Finding the size of a hashtable is very similar in Java and Ruby. Finding the size of hashtable planets in Java:
planets.size();
And in Ruby:
planets.size
The following Java code will remove the key "Big" and its corresponding entry from the hash table planets:
planets.remove ("Big");
Ruby can accomplish the same task with:
planets.delete("Big");
To replace a key/entry pair in a hash table Java would do the following:
planets.put("Big","Saturn");
This is the same syntax we used to populate an empty table. It returns the previous value or null if no previous value.
In Ruby it would look like:
planets.replace({"Ring" =>"Saturn","Big" =>"Jupiter"}){#=> "Ring"=>"Neptune", "Big"=>"Saturn"}
Moving a Hash Table Into an Array
We will use the planets table to populate an array called new_planets.
In Java:
String[] new_planets = new String[8]; for ( Enumeration e = planets.keys() ; e.hasMoreElements() ; ) { String planet_value = (String) e.nextElement(); new_planets[Integer(e)]=planet_value; }
In Ruby:
Hash Table Summary
The differences in creating and using hashtables in Java as opposed to in Ruby is mostly a matter of cosmetic syntax. Java uses parenthesis where Ruby uses brackets. The Ruby code is a little more readable and the same task in Java can often be accomplished with less code in Ruby. Unlike Java, method calls in Ruby often omit parenthesis. Also return values that would return null in Java return nil in Ruby.
External Links
- Java Platform, Standard Edition 6 API Specification, for see java.util.Arrays and java.util.ArrayList; for hash tables see java.util.Hashtable.
- The Java Language Specification, Chapter 10: Arrays.
- The Java Tutorials, see the section on Arrays.
- RDoc Documentation, on the Array class and Hash class.
- Learning Ruby by Michael Fitzgerald, O'Reilly. Chapters 6 (Arrays) and 7 (Hashes). [Offline Book].
- To Ruby From Java