1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.functor.example;
18  
19  import static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertTrue;
21  import static org.junit.Assert.fail;
22  
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.Random;
27  
28  import org.apache.commons.functor.BinaryFunction;
29  import org.apache.commons.functor.UnaryFunction;
30  import org.apache.commons.functor.core.Constant;
31  import org.apache.commons.functor.core.collection.IsEmpty;
32  import org.apache.commons.functor.core.comparator.IsGreaterThanOrEqual;
33  import org.apache.commons.functor.core.comparator.IsLessThan;
34  import org.apache.commons.functor.core.composite.ConditionalUnaryFunction;
35  import org.apache.commons.functor.generator.FilteredGenerator;
36  import org.apache.commons.functor.generator.IteratorToGeneratorAdapter;
37  import org.junit.Test;
38  
39  /*
40   * ----------------------------------------------------------------------------
41   * INTRODUCTION:
42   * ----------------------------------------------------------------------------
43   */
44  
45  /*
46   * Here's an example of the Quicksort sorting algorithm, implemented using
47   * Commons Functor.
48   *
49   * See http://commons.apache.org/sandbox/functor/examples.html
50   * for additional examples.
51   */
52  
53  /**
54   * An example of implementing the quicksort sorting algorithm
55   * using commons-functor.
56   * <p>
57   * See the extensive in line comments for details.
58   *
59   * @version $Revision: 1171255 $ $Date: 2011-09-15 22:27:39 +0200 (Thu, 15 Sep 2011) $
60   * @author Rodney Waldhoff
61   */
62  @SuppressWarnings("unchecked")
63  public class QuicksortExample {
64  
65  /*
66   * ----------------------------------------------------------------------------
67   * UNIT TESTS:
68   * ----------------------------------------------------------------------------
69   */
70  
71  /*
72   * In "test first" style, let's start with the some functional descriptions
73   * of what'd we'd like our Quicksort to do, expressed as unit tests.
74   *
75   * In our tests, we'll use a "quicksort" method which takes a List and
76   * returns a (new) sorted List.  We'll define that method a bit later.
77   *
78   *
79   * First, let's get some trivial cases out of the way.
80   *
81   * Sorting an empty List should produce an empty list:
82   */
83  
84      @Test
85      public void testSortEmpty() {
86          List empty = Collections.EMPTY_LIST;
87          List result = quicksort(empty);
88          assertTrue("Sorting an empty list should produce an empty list.", result.isEmpty());
89      }
90  
91  /*
92   * Similarly, sorting a List composed of a single element
93   * should produce an equivalent list:
94   */
95  
96      @Test
97      public void testSortSingleElementList() {
98          List list = new ArrayList();
99          list.add("element");
100 
101         List sorted = quicksort(list);
102 
103         assertTrue(
104             "The quicksort() method should return a distinct list.",
105             list != sorted);
106 
107         assertEquals(
108             "Sorting a single-element list should produce an equivalent list",
109             list,
110             sorted);
111     }
112 
113 /*
114  * Finally, sorting a List composed of multiple copies
115  * of a single value should produce an equivalent list:
116  */
117 
118     @Test
119     public void testSortSingleValueList() {
120         List list = new ArrayList();
121         for (int i = 0; i < 10; i++) {
122             list.add("element");
123         }
124         List sorted = quicksort(list);
125 
126         assertTrue(
127             "The quicksort() method should return a distinct list.",
128             list != sorted);
129 
130         assertEquals(list, sorted);
131     }
132 
133 /*
134  * So far so good.
135  *
136  * Next, let's take some slightly more complicated cases.
137  *
138  * Sorting an already sorted list:
139  */
140     @Test
141     public void testSortSorted() {
142         List list = new ArrayList();
143         for (int i = 0; i < 10; i++) {
144             list.add(new Integer(i));
145         }
146 
147         List sorted = quicksort(list);
148 
149         assertTrue(
150             "The quicksort() method should return a distinct list.",
151             list != sorted);
152 
153         assertEquals(
154             "Sorting an already sorted list should produce an equivalent list",
155             list,
156             sorted);
157     }
158 
159 /*
160  * Sorting a reverse-order list (finally, a test case that requires something
161  * more than an identity function):
162  */
163     @Test
164     public void testSortReversed() {
165         List expected = new ArrayList();
166         List tosort = new ArrayList();
167         for (int i = 0; i < 10; i++) {
168             /*
169              * The "expected" list contains the integers in order.
170              */
171             expected.add(new Integer(i));
172             /*
173              * The "tosort" list contains the integers in reverse order.
174              */
175             tosort.add(new Integer(9 - i));
176         }
177 
178         assertEquals(expected, quicksort(tosort));
179     }
180 
181 /*
182  * Just for fun, let's add some randomness to the tests, first by shuffling:
183  */
184     @Test
185     public void testSortShuffled() {
186         List expected = new ArrayList();
187         for (int i = 0; i < 10; i++) {
188             expected.add(new Integer(i));
189         }
190         List tosort = new ArrayList(expected);
191         Collections.shuffle(tosort);
192 
193         assertEquals(expected, quicksort(tosort));
194     }
195 
196 /*
197  * and then using random values:
198  */
199     @Test
200     public void testSortRandom() {
201         Random random = new Random();
202         /*
203          * populate a list with random integers
204          */
205         List tosort = new ArrayList();
206         for (int i = 0; i < 10; i++) {
207             tosort.add(new Integer(random.nextInt(10)));
208         }
209         /*
210          * and use java.util.Collections.sort to
211          * give us a sorted version to compare to
212          */
213         List expected = new ArrayList(tosort);
214         Collections.sort(expected);
215 
216         assertEquals(expected, quicksort(tosort));
217     }
218 
219 /*
220  * Finally, while this quicksort implementation is intended to
221  * illustrate the use of Commons Functor, not for performance,
222  * let's output some timings just to demonstrate that the
223  * performance is adequate.
224  */
225 
226     private static final int SIZE = 1000;
227     private static final int COUNT = 100;
228 
229     @Test
230     public void testTimings() {
231         /*
232          * We'll need the total elapsed time:
233          */
234         long elapsed = 0L;
235 
236         /*
237          * and a source for random integers:
238          */
239         Random random = new Random();
240 
241         /*
242          * Repeat this COUNT times:
243          */
244         for (int i = 0; i < COUNT; i++) {
245             /*
246              * Create a List of size SIZE, and
247              * populate it with random integers:
248              */
249             List tosort = new ArrayList(SIZE);
250             for (int j = 0; j < SIZE; j++) {
251                 tosort.add(new Integer(random.nextInt(SIZE)));
252             }
253 
254             /*
255              * Start the timer.
256              */
257             long start = System.currentTimeMillis();
258 
259             /*
260              * Sort the list.
261              * (We'll ignore the returned value here.)
262              */
263             quicksort(tosort);
264 
265             /*
266              * Stop the timer.
267              */
268             long stop = System.currentTimeMillis();
269 
270             /*
271              * Add the elapsed time to our total.
272              */
273             elapsed += stop - start;
274         }
275 
276         /*
277          * Whew, that was a lot of processing.  Now figure out
278          * how long it took on average (per list)
279          * and print a simply summary:
280          */
281 
282         /*
283         double avgmillis = ((double) elapsed) / ((double) COUNT);
284 
285         System.out.println();
286         System.out.println(
287             "Quicksort Example: Sorted "
288                 + COUNT
289                 + " lists of "
290                 + SIZE
291                 + " elements in "
292                 + elapsed
293                 + " millis ("
294                 + avgmillis
295                 + " millis, or "
296                 + (avgmillis / 1000D)
297                 + " seconds on average).");
298         System.out.println();
299         */
300     }
301 
302 
303 /*
304  * THE QUICKSORT ALGORITHM:
305  * ----------------------------------------------------------------------------
306  */
307 
308 /*
309  * Our quicksort method will invoke a UnaryFunction named
310  * quicksort:
311  */
312 
313     public List quicksort(List list) {
314         return (List)(quicksort.evaluate(list));
315     }
316 
317 /*
318  * The quicksort sorting algorithm can be summarized as follows:
319  *
320  * Given a list of elements to be sorted:
321  *
322  * A) If the list is empty, consider it already sorted.
323  *
324  * B) If the list is non-empty, we can sort it by first splitting it into
325  *   three lists:
326  *     1) one list containing only the first element in the list (the "head")
327  *     2) one (possibly empty) list containing every element in the remaining
328  *        list that is less than the head
329  *     3) one (possibly empty) list containing every element in the remaining
330  *        list that is greater than or equal to the head
331  *    applying the quicksort algorithm recursively to the second and third lists,
332  *    and joining the results back together as (2) + (1) + (3).
333  */
334 
335 /*
336  * Using a functional style (or at least a semi-functional style), it's easy
337  * to transalate this description directly into code:
338  */
339 
340     private UnaryFunction quicksort = new ConditionalUnaryFunction(
341         /* if the list is empty... */
342         IsEmpty.instance(),
343         /* ...then return an empty list... */
344         new Constant(Collections.EMPTY_LIST),
345         /* ...else, apply the following function... */
346         new ListFunction() {
347             public Object evaluate(List list) {
348                 /* Create a list to contain the results. */
349                 List result = new ArrayList(list.size());
350                 /*
351                  * Recursively apply quicksort the the elements in the
352                  * tail less than the head, adding the result to the
353                  * sorted list we're generating.
354                  */
355                 result.addAll(
356                     (List) quicksort.evaluate(
357                         lesserTail.evaluate(
358                             head.evaluate(list),
359                             tail.evaluate(list))));
360                 /*
361                  * Add the head to the sorted list we're generating.
362                  */
363                 result.add(head.evaluate(list));
364                 /*
365                  * Recursively apply quicksort the the elements in the
366                  * tail greater than the head, adding the result to the
367                  * sorted list we're generating.
368                  */
369                 result.addAll(
370                     (List) quicksort.evaluate(
371                         greaterTail.evaluate(
372                             head.evaluate(list),
373                             tail.evaluate(list))));
374                 /*
375                  * And return the generated list.
376                  */
377                 return result;
378             }
379     });
380 
381 /*
382  * Now let's look at the building blocks we need to flesh out that
383  * function.
384  *
385  * First, let's save ourselves some casting and error handling by
386  * definining some functor sub-types.
387  *
388  * Let ListFunction be a UnaryFunction that operates on Lists:
389  */
390 
391     public abstract class ListFunction implements UnaryFunction {
392         public abstract Object evaluate(List list);
393 
394         public Object evaluate(Object obj) {
395             if (obj instanceof List) {
396                 return evaluate((List) obj);
397             } else if (null == obj) {
398                 throw new NullPointerException("The argument must not be null.");
399             } else {
400                 throw new ClassCastException(
401                     "The argument must be a List, found " +
402                     obj.getClass().getName());
403             }
404         }
405     }
406 
407 /*
408  * Let ObjectListFunction be a BinaryFunction that operates on
409  * an Object, List pair:
410  */
411 
412     public abstract class ObjectListFunction implements BinaryFunction {
413         public abstract Object evaluate(Object head, List tail);
414 
415         public Object evaluate(Object left, Object right) {
416             if (left != null && right instanceof List) {
417                 return evaluate(left, (List) right);
418             } else if (null == left) {
419                 throw new NullPointerException("The left argument must not be null.");
420             } else if (null == right) {
421                 throw new NullPointerException("The right argument must not be null.");
422             } else {
423                 throw new ClassCastException(
424                     "The right argument must be a List, found " +
425                     right.getClass().getName());
426             }
427         }
428     }
429 
430 /*
431  * Now for the implementations.
432  *
433  * Given a List, we need to be able to break it into its head:
434  */
435 
436     private UnaryFunction head = new ListFunction() {
437         public Object evaluate(List list) {
438             return list.get(0);
439         }
440     };
441 
442 /*
443  * and its tail:
444  */
445     private UnaryFunction tail = new ListFunction() {
446         public Object evaluate(List list) {
447             return list.size() < 2 ?
448                 Collections.EMPTY_LIST :
449                 list.subList(1, list.size());
450         }
451     };
452 
453 /*
454  * Given a List in head/tail form, we should be able to find
455  * the list of elements in the tail less than the head.
456  * We can simply apply the select algorithm here, using
457  * a predicate identifying elements less than the head.
458  */
459     private BinaryFunction lesserTail = new ObjectListFunction() {
460         public Object evaluate(Object head, List tail) {
461             return new FilteredGenerator(
462                     IteratorToGeneratorAdapter.adapt(tail.iterator()),
463                 IsLessThan.instance((Comparable) head)).toCollection();
464         }
465     };
466 
467 /*
468  * We must also be able to find the List of elements in
469  * the tail greater than (or equal to) the head. This
470  * is similar to the lesserTail approach.
471  */
472     private BinaryFunction greaterTail = new ObjectListFunction() {
473         public Object evaluate(Object head, List tail) {
474             return new FilteredGenerator(
475                     IteratorToGeneratorAdapter.adapt(tail.iterator()),
476                 IsGreaterThanOrEqual.instance((Comparable) head)).toCollection();
477         }
478     };
479 
480 /*
481  * Note that each of these smaller functors is readily testable
482  * in isolation:
483  */
484 
485     public void testHeadFunction() {
486         List list = new ArrayList();
487         try {
488             head.evaluate(list);
489             fail("Expected IndexOutOfBoundsException when evaluating head of an empty list");
490         } catch(IndexOutOfBoundsException e) {
491             // expected
492         }
493 
494         list.add("First");
495         assertEquals("First",head.evaluate(list));
496 
497         list.add("Second");
498         assertEquals("First",head.evaluate(list));
499 
500     }
501 
502     public void testTailFunction() {
503         List list = new ArrayList();
504         {
505             List result = (List)(tail.evaluate(list));
506             assertTrue("Tail of an empty list is empty.",result.isEmpty());
507         }
508         list.add("First");
509         {
510             List result = (List)(tail.evaluate(list));
511             assertTrue("Tail of a one element list is empty.",result.isEmpty());
512         }
513         list.add("Second");
514         {
515             List result = (List)(tail.evaluate(list));
516             assertEquals("Tail of a two element list has one element.",1,result.size());
517             assertEquals("Second",result.get(0));
518         }
519         list.add("Third");
520         {
521             List result = (List)(tail.evaluate(list));
522             assertEquals("Tail of a three element list has two elements.",2,result.size());
523             assertEquals("Second",result.get(0));
524             assertEquals("Third",result.get(1));
525         }
526     }
527 
528     public void testLesserTail() {
529         List list = new ArrayList();
530         for (int i=0;i<10;i++) {
531             list.add(new Integer(i));
532         }
533         for (int i=0;i<10;i++) {
534             Integer val = new Integer(i);
535             List lesser = (List) lesserTail.evaluate(val,list);
536             assertEquals(i,lesser.size());
537             for (int j=0;j<i;j++) {
538                 assertEquals(new Integer(j),lesser.get(j));
539             }
540         }
541     }
542 
543     public void testGreaterTail() {
544         List list = new ArrayList();
545         for (int i=0;i<10;i++) {
546             list.add(new Integer(i));
547         }
548         for (int i=0;i<10;i++) {
549             Integer val = new Integer(i);
550             List greater = (List) greaterTail.evaluate(val,list);
551             assertEquals(10-i,greater.size());
552             for (int j=i;j<10;j++) {
553                 assertEquals(new Integer(j),greater.get(j-i));
554             }
555         }
556     }
557 }