Return-Path: Received: from fort-point-station.mit.edu by po10.mit.edu (8.9.2/4.7) id RAA11627; Tue, 7 Nov 2000 17:44:08 -0500 (EST) Received: from hermes.java.sun.com (hermes.java.sun.com [204.160.241.85]) by fort-point-station.mit.edu (8.9.2/8.9.2) with ESMTP id RAA20915; Tue, 7 Nov 2000 17:43:42 -0500 (EST) Received: (from nobody@localhost) by hermes.java.sun.com (8.9.3+Sun/8.9.1) id WAA10841; Tue, 7 Nov 2000 22:45:15 GMT Date: Tue, 7 Nov 2000 22:45:15 GMT Message-Id: <200011072245.WAA10841@hermes.java.sun.com> X-Mailing: 295 From: JDCTechTips@sun.com Subject: JDC Tech Tips November 7, 2000 To: JDCMember@sun.com Reply-To: JDCTechTips@sun.com Errors-To: bounced_mail@hermes.java.sun.com Precedence: junk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: Beyond Email 2.2 J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, November 7, 2000. This issue covers: * Using Random Numbers for Testing and Simulation * Collection Utilities These tips were developed using Java(tm) 2 SDK, Standard Edition, v 1.3. You can view this issue of the Tech Tips on the Web at http://developer.java.sun.com/developer/TechTips/2000/tt1107.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING RANDOM NUMBERS FOR TESTING AND SIMULATION The Java(tm) standard library provides a random number class, java.util.Random. This tip examines some aspects of using random numbers in Java programming. The tip starts with some fundamentals of using random numbers, and then presents a longer example at the end. Random numbers, as found in programming languages, are really "pseudo" random numbers. They're not random in the same sense as physical phenomena such as thermal noise or background radiation. However it's interesting to note that truly random number generators, ones that are hardware-based, are starting to appear on the market. Though software-generated random numbers are not really random, it's possible to generate random numbers in such a way that important statistical tests like chi square and serial correlation are satisfied. The Random class uses a random number generator of the form: nextrand = nextrand * a + b; where a and b are carefully chosen constants. As defined by D. H. Lehmer and described by Donald E. Knuth in "The Art of Computer Programming, Volume 2: Seminumerical Algorithms," section 3.2.1, this is a "linear congruential" random number generator. The low-order bits of random numbers generated this way tend not to be random, so internal calculation is done using 48 bits. But a Random method such as Random.nextInt uses only the upper 32 bits of the current 48-bit random value. A sequence of random values that is generated is deterministic. This means that from a given starting point (a "seed"), the sequence of values returned is predictable. When you set up a random number generator, you can say: Random rn = new Random(1234); if you want to specify the seed (here, it's 1234), or: Random rn = new Random(); if you want the generator to be seeded from the current time of day (using System.currentTimeMillis). The first approach produces a predictable sequence, and so this tip uses "Random(0)" in the demo programs below. The first random number program is a simple one that prints out 10 "heads" or "tails" values: import java.util.Random; public class RandDemo1 { public static void main(String args[]) { Random rn = new Random(0); for (int i = 1; i <= 10; i++) { System.out.println(rn.nextBoolean() ? "heads" : "tails"); } } } The nextBoolean method in RandDemo1 is implemented internally by generating a 48-bit random value, and then checking whether the high bit is 1 or 0. The next example is slightly more complex: import java.util.Random; class RandUtils { private static Random rn = new Random(0); private RandUtils() {} // get random number in range, lo <= x <= hi public static int getRange(int lo, int hi) { // error checking if (lo > hi) { throw new IllegalArgumentException("lo > hi"); } // handle special case if (lo == Integer.MIN_VALUE && hi == Integer.MAX_VALUE) { return rn.nextInt(); } else { return rn.nextInt(hi - lo + 1) + lo; } } // return true perc % of the time public static boolean getPerc(int perc) { // error checking if (perc < 0 || perc > 100) { throw new IllegalArgumentException("bad perc"); } return perc >= getRange(1, 100); } } public class RandDemo2 { public static void main(String args[]) { int accum[] = new int[10]; // generate random numbers in a range and tally them for (int i = 1; i <= 10000; i++) { accum[RandUtils.getRange(0, accum.length - 1)]++; } // display results for (int i = 0; i < accum.length; i++) { System.out.println(i + " " + accum[i]); } } } In this example, RandUtils is a utility class that implements a couple of methods: getRange and getPerc. The getRange method returns a random number in a specified range. The method is based on Random.nextInt, which returns a random number between 0 (inclusive) and the specified argument (exclusive). What inclusive and exclusive mean here is that if you call Random.nextInt as follows: Random rn = new Random(); int n = rn.nextInt(10); n will have a value, 0 <= n < 10. In other words, 0 can be one of the returned values, but not 10. The other method is getPerc; it returns true the specified percentage of the time. For example, you can say: if (RandUtils.getPerc(75)) { // block of code } and the block of code will be executed 75% of the time, on average. You'll see a use for this method in the next example. When you run the RandDemo2 program, you should get the following result: 0 990 1 1011 2 952 3 1045 4 998 5 1005 6 1021 7 1009 8 1005 9 964 Note that the tally for each number in the range should be about 1000. The results in this example vary slightly from the expected value. This is normal. If you want to check whether the variation is statistically significant, use a chi square test. If you do, you should find that the results observed here are well within those expected from random fluctuations. The final example is more complicated. Suppose that you're testing some software, and one of the inputs to the software is calendar dates, like this: September 25, 1956 You'd like to generate random dates in this form, with some of the dates being legal, and some illegal (such as "January 32, 1989"). How can you do this? One way is to use random numbers. Here's an example: import java.util.Random; class RandUtils { private static Random rn = new Random(0); private RandUtils() {} // get random number in range, lo <= x <= hi public static int getRange(int lo, int hi) { // error checking if (lo > hi) { throw new IllegalArgumentException("lo > hi"); } // handle special case if (lo == Integer.MIN_VALUE && hi == Integer.MAX_VALUE) { return rn.nextInt(); } else { return rn.nextInt(hi - lo + 1) + lo; } } // return true perc % of the time public static boolean getPerc(int perc) { // error checking if (perc < 0 || perc > 100) { throw new IllegalArgumentException("bad perc"); } return perc >= getRange(1, 100); } } class GenDate { // names of months private static final String months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; // days in month private static final int days_in_month[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // return true if leap year private static boolean isLeapYear(int year) { if (year % 4 != 0) { return false; } if (year % 400 == 0) { return true; } return (year % 100 != 0); } // get the number of days in a given month private static int getDaysInMonth(int month, int year) { int m = days_in_month[month - 1]; if (month == 2 && isLeapYear(year)) { m++; } return m; } // generate a random string private static String getRandString() { switch (RandUtils.getRange(1, 4)) { // empty string case 1: { return ""; } // random integer case 2: { return Integer.toString( RandUtils.getRange(-100000, 100000)); } // random characters case 3: { StringBuffer sb = new StringBuffer(); int n = RandUtils.getRange(1, 10); for (int i = 1; i <= n; i++) { char c = (char)RandUtils.getRange(32, 127); sb.append(c); } return sb.toString(); } // random number of spaces case 4: { StringBuffer sb = new StringBuffer(); int n = RandUtils.getRange(1, 10); for (int i = 1; i <= n; i++) { sb.append(' '); } return sb.toString(); } } // shouldn't get here throw new Error(); } // this class has only static methods, so // can't create instances of the class private GenDate() {} public static String getRandDate() { StringBuffer sb = new StringBuffer(); // generate year, month, day int year = RandUtils.getRange(1500, 2100); int month = RandUtils.getRange(1, 12); int day = RandUtils.getRange(1, getDaysInMonth(month, year)); // 50% of the time, return a valid date if (RandUtils.getPerc(50)) { sb.append(months[month - 1]); sb.append(" "); sb.append(day); sb.append(", "); sb.append(year); } else { // generate a month or random string if (RandUtils.getPerc(75)) { sb.append(months[month - 1]); } else { sb.append(getRandString()); } // generate single space or random string if (RandUtils.getPerc(75)) { sb.append(" "); } else { sb.append(getRandString()); } // generate day of month or random number if (RandUtils.getPerc(75)) { sb.append(day); } else { sb.append(RandUtils.getRange(-100, 100)); } // generate , or random string if (RandUtils.getPerc(75)) { sb.append(", "); } else { sb.append(getRandString()); } // generate year or random string if (RandUtils.getPerc(75)) { sb.append(year); } else { sb.append(getRandString()); } } return sb.toString(); } } public class RandDemo3 { public static void main(String args[]) { for (int i = 1; i <= 15; i++) { System.out.println(GenDate.getRandDate()); } } } The output of the program is: May 21, 1778 June -83, 2006 September 51575 14, M%r September 26, 1614 October 17, 1910 May 16, 1818 August 27, 1646 November 19, 2055 June 12, 1797 June 13, 1585 August 2, 1998 October 17, September 14, 1545 June339628, This technique is quite powerful and useful. Typically you start with a description of what constitutes legal input, and then systematically go through and generate "nearly correct" input, but with some illegal variations thrown in, driven by random numbers. A similar technique can be used for doing simulation. To learn more about using random numbers, see Section 17.3 Random in "The Java Programming Language, Third Edition," by Arnold, Gosling, and Holmes (http://java.sun.com/docs/books/javaprog/thirdedition/); and Chapter 3 in Volume 2 of "The Art of Computer Programming" by Knuth. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - COLLECTION UTILITIES Collection classes like ArrayList and HashMap are used heavily in Java programming. Associated with these classes are what might be called utility methods. These methods add functionality to the collection classes. This tip looks at some of these utilities. The first utility method is one that provides a synchronization wrapper on top of an existing collection. A wrapper is an alternate view of a collection. You can still access and modify the original collection, but the wrapped collection provides some other desirable property. Here's a demonstration of using synchronization wrappers: import java.util.*; public class UtilDemo1 { public static void main(String args[]) { Object obj = new Object(); // create a list List list = new ArrayList(); // put a wrapper on top of it List synclist = Collections.synchronizedList(list); // add some objects to the list long start = System.currentTimeMillis(); for (int i = 1; i <= 1000000; i++) { synclist.add(obj); } long elapsed = System.currentTimeMillis() - start; System.out.println(elapsed); } } By default, collection classes such as ArrayList are not thread-safe (unlike the older Vector class). A thread-safe implementation does more for you, but costs more in return. If you have multiple threads sharing a single collection, then you need to worry about synchronization, that is, you need to be aware of potential problems and deal with them. In the example above, the collection is wrapped, and so, method calls such as add will be synchronized. The synchronization is done by first obtaining a lock on the wrapper (synclist). If you really want to thwart synchronization, you can still access the list directly using "list" instead of "synclist". However, this is probably not a good idea. Another way of tackling the same problem of adding objects to a list looks like this: import java.util.*; public class UtilDemo2 { public static void main(String args[]) { Object obj = new Object(); // create a list List list = new ArrayList(); // add some objects to it long start = System.currentTimeMillis(); synchronized (list) { for (int i = 1; i <= 1000000; i++) { list.add(obj); } } long elapsed = System.currentTimeMillis() - start; System.out.println(elapsed); } } In this example, no synchronization wrapper is used, but the list is locked while objects are added to it. This demo runs about 25% faster than the previous one, but at the expense of keeping the list locked throughout the update operation. The Collection class also has methods that return unmodifiable (as opposed to synchronized) wrappers. One of these methods is Collections.unmodifiableList; you can use it to create a read-only view of a list. The list can still be modified, but not through the wrapper interface. This is especially useful if you want to pass a list to some other function, but you want to prevent the other function from modifying the list. To do this, you simply use a lightweight wrapper to make the list read only. Here's an example that uses Collections.unmodifiableList: import java.util.*; public class UtilDemo3 { public static void main(String args[]) { // create a list and add some items to it List stringlist = new ArrayList(); stringlist.add("alpha"); stringlist.add("beta"); stringlist.add("gamma"); // create an unmodifiable view of the list List rolist = Collections.unmodifiableList(stringlist); // add to the original list (works OK) stringlist.add("delta"); // add through the read-only view (gives an exception) rolist.add("delta"); } } This example program creates a list and adds some items to it. It then creates an unmodifiable view of the list. When you run the program, you'll see that an additional item can be added to the original list. However the program throws an exception when it attempts to add an item to the read-only view. Another kind of operation provided by the utility methods is min/max. Here's an example using min: import java.util.*; public class UtilDemo4 { public static void main(String args[]) { // create a list and add some objects to it List list = new ArrayList(); list.add("alpha"); list.add("Beta"); list.add("gamma"); list.add("Delta"); // compute the minimum element, case sensitive String str = (String)Collections.min(list); System.out.println(str); // compute the minimum element, case insensitive str = (String)Collections.min(list, String.CASE_INSENSITIVE_ORDER); System.out.println(str); } } This program computes the minimum value of a set of strings, using the natural ordering of strings (see java.lang.Comparable). Then it computes the minimum value using an implementation of the java.util.Comparator interface. In this example, a special comparator String.CASE_INSENSITIVE_ORDER is used. A comparator allows you to specify a particular ordering of elements. The output of the program is: Beta alpha You can use the shuffle utility method to randomly shuffle the elements of a list. For example, this program reads a text file and then displays its lines in random order: import java.io.*; import java.util.*; public class UtilDemo5 { public static void main(String args[]) throws IOException { // check command line argument if (args.length != 1) { System.err.println("missing input file"); System.exit(1); } // open file FileReader fr = new FileReader(args[0]); BufferedReader br = new BufferedReader(fr); // read lines from file List list = new ArrayList(); String ln; while ((ln = br.readLine()) != null) { list.add(ln); } br.close(); // shuffle the lines Collections.shuffle(list); // print the result Iterator it = list.iterator(); while (it.hasNext()) { System.out.println((String)it.next()); } } } For input like: 1 2 3 4 5 output might be: 3 2 1 5 4 A program like this one is useful in generating test data. A final example shows how to do binary searching in a list: import java.util.*; public class UtilDemo6 { public static void main(String args[]) { // create list and add elements to it List list = new ArrayList(); list.add("alpha"); list.add("Beta"); list.add("Delta"); list.add("gamma"); // do the search int i = Collections.binarySearch(list, "chi", String.CASE_INSENSITIVE_ORDER); i = -(i + 1); // display the result System.out.println("insertion point = " + i); } } The list is searched (case insensitive) for an occurrence of the string "chi", which is not found. When a key is not found, the return value from binarySearch is -(i + 1), where i is the appropriate insertion point to maintain the list in proper order. When run, the UtilDemo6 program prints: insertion point = 2 In other words, "chi" should be inserted at location 2, just before "Delta". The collection utilities also contain methods for sorting lists, reversing the order of lists, filling, and copying. To learn more about collection utilities, see section 16.8 Wrapped Collections and the Collections Class in "The Java Programming Language, Third Edition" by Arnold, Gosling, and Holmes (http://java.sun.com/docs/books/javaprog/thirdedition/). . . . . . . . . . . . . . . . . . . . . . . . - NOTE Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun Microsystems(tm) purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page (http://developer.java.sun.com/subscription/), uncheck the appropriate checkbox, and click the Update button. - SUBSCRIBE To subscribe to a JDC newsletter mailing list, go to the Subscriptions page (http://developer.java.sun.com/subscription/), choose the newsletters you want to subscribe to, and click Update. - FEEDBACK Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com - ARCHIVES You'll find the JDC Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html - COPYRIGHT Copyright 2000 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html This issue of the JDC Tech Tips is written by Glen McCluskey. JDC Tech Tips November 7, 2000