View Javadoc
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.Function;
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.ConditionalFunction;
35  import org.apache.commons.functor.generator.FilteredGenerator;
36  import org.apache.commons.functor.generator.loop.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: 1541658 $ $Date: 2013-11-13 19:54:05 +0100 (Mi, 13 Nov 2013) $
60   */
61  @SuppressWarnings("unchecked")
62  public class QuicksortExample {
63  
64  /*
65   * ----------------------------------------------------------------------------
66   * UNIT TESTS:
67   * ----------------------------------------------------------------------------
68   */
69  
70  /*
71   * In "test first" style, let's start with the some functional descriptions
72   * of what'd we'd like our Quicksort to do, expressed as unit tests.
73   *
74   * In our tests, we'll use a "quicksort" method which takes a List and
75   * returns a (new) sorted List.  We'll define that method a bit later.
76   *
77   *
78   * First, let's get some trivial cases out of the way.
79   *
80   * Sorting an empty List should produce an empty list:
81   */
82  
83      @Test
84      public void testSortEmpty() {
85          List<Object> empty = Collections.EMPTY_LIST;
86          List<?> result = quicksort(empty);
87          assertTrue("Sorting an empty list should produce an empty list.", result.isEmpty());
88      }
89  
90  /*
91   * Similarly, sorting a List composed of a single element
92   * should produce an equivalent list:
93   */
94  
95      @Test
96      public void testSortSingleElementList() {
97          List<Object> list = new ArrayList<Object>();
98          list.add("element");
99  
100         List<?> sorted = quicksort(list);
101 
102         assertTrue(
103             "The quicksort() method should return a distinct list.",
104             list != sorted);
105 
106         assertEquals(
107             "Sorting a single-element list should produce an equivalent list",
108             list,
109             sorted);
110     }
111 
112 /*
113  * Finally, sorting a List composed of multiple copies
114  * of a single value should produce an equivalent list:
115  */
116 
117     @Test
118     public void testSortSingleValueList() {
119         List<Object> list = new ArrayList<Object>();
120         for (int i = 0; i < 10; i++) {
121             list.add("element");
122         }
123         List<?> sorted = quicksort(list);
124 
125         assertTrue(
126             "The quicksort() method should return a distinct list.",
127             list != sorted);
128 
129         assertEquals(list, sorted);
130     }
131 
132 /*
133  * So far so good.
134  *
135  * Next, let's take some slightly more complicated cases.
136  *
137  * Sorting an already sorted list:
138  */
139     @Test
140     public void testSortSorted() {
141         List<Object> list = new ArrayList<Object>();
142         for (int i = 0; i < 10; i++) {
143             list.add(Integer.valueOf(i));
144         }
145 
146         List<?> sorted = quicksort(list);
147 
148         assertTrue(
149             "The quicksort() method should return a distinct list.",
150             list != sorted);
151 
152         assertEquals(
153             "Sorting an already sorted list should produce an equivalent list",
154             list,
155             sorted);
156     }
157 
158 /*
159  * Sorting a reverse-order list (finally, a test case that requires something
160  * more than an identity function):
161  */
162     @Test
163     public void testSortReversed() {
164         List<Object> expected = new ArrayList<Object>();
165         List<Object> tosort = new ArrayList<Object>();
166         for (int i = 0; i < 10; i++) {
167             /*
168              * The "expected" list contains the integers in order.
169              */
170             expected.add(Integer.valueOf(i));
171             /*
172              * The "tosort" list contains the integers in reverse order.
173              */
174             tosort.add(Integer.valueOf(9 - i));
175         }
176 
177         assertEquals(expected, quicksort(tosort));
178     }
179 
180 /*
181  * Just for fun, let's add some randomness to the tests, first by shuffling:
182  */
183     @Test
184     public void testSortShuffled() {
185         List<Object> expected = new ArrayList<Object>();
186         for (int i = 0; i < 10; i++) {
187             expected.add(Integer.valueOf(i));
188         }
189         List<Object> tosort = new ArrayList<Object>(expected);
190         Collections.shuffle(tosort);
191 
192         assertEquals(expected, quicksort(tosort));
193     }
194 
195 /*
196  * and then using random values:
197  */
198     @Test
199     public void testSortRandom() {
200         Random random = new Random();
201         /*
202          * populate a list with random integers
203          */
204         List<Integer> tosort = new ArrayList<Integer>();
205         for (int i = 0; i < 10; i++) {
206             tosort.add(Integer.valueOf(random.nextInt(10)));
207         }
208         /*
209          * and use java.util.Collections.sort to
210          * give us a sorted version to compare to
211          */
212         List<Integer> expected = new ArrayList<Integer>(tosort);
213         Collections.sort(expected);
214 
215         assertEquals(expected, quicksort(tosort));
216     }
217 
218 /*
219  * Finally, while this quicksort implementation is intended to
220  * illustrate the use of Commons Functor, not for performance,
221  * let's output some timings just to demonstrate that the
222  * performance is adequate.
223  */
224 
225     private static final int SIZE = 1000;
226     private static final int COUNT = 100;
227 
228     @Test
229     public void testTimings() {
230         /*
231          * We'll need the total elapsed time:
232          */
233         @SuppressWarnings("unused")
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<Object> tosort = new ArrayList<Object>(SIZE);
250             for (int j = 0; j < SIZE; j++) {
251                 tosort.add(Integer.valueOf(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 Function 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 Function<Object, Object> quicksort = new ConditionalFunction<Object, Object>(
341         /* if the list is empty... */
342         IsEmpty.instance(),
343         /* ...then return an empty list... */
344         new Constant<Object>(Collections.EMPTY_LIST),
345         /* ...else, apply the following function... */
346         new ListFunction() {
347             @Override
348             public Object evaluate(List<?> list) {
349                 /* Create a list to contain the results. */
350                 List<Object> result = new ArrayList<Object>(list.size());
351                 /*
352                  * Recursively apply quicksort the the elements in the
353                  * tail less than the head, adding the result to the
354                  * sorted list we're generating.
355                  */
356                 result.addAll(
357                     (List<?>) quicksort.evaluate(
358                         lesserTail.evaluate(
359                             head.evaluate(list),
360                             tail.evaluate(list))));
361                 /*
362                  * Add the head to the sorted list we're generating.
363                  */
364                 result.add(head.evaluate(list));
365                 /*
366                  * Recursively apply quicksort the the elements in the
367                  * tail greater than the head, adding the result to the
368                  * sorted list we're generating.
369                  */
370                 result.addAll(
371                     (List<?>) quicksort.evaluate(
372                         greaterTail.evaluate(
373                             head.evaluate(list),
374                             tail.evaluate(list))));
375                 /*
376                  * And return the generated list.
377                  */
378                 return result;
379             }
380     });
381 
382 /*
383  * Now let's look at the building blocks we need to flesh out that
384  * function.
385  *
386  * First, let's save ourselves some casting and error handling by
387  * definining some functor sub-types.
388  *
389  * Let ListFunction be a Function that operates on Lists:
390  */
391 
392     public abstract class ListFunction implements Function<Object, Object> {
393         public abstract Object evaluate(List<?> list);
394 
395         public Object evaluate(Object obj) {
396             if (obj instanceof List) {
397                 return evaluate((List<?>) obj);
398             } else if (null == obj) {
399                 throw new NullPointerException("The argument must not be null.");
400             } else {
401                 throw new ClassCastException(
402                     "The argument must be a List, found " +
403                     obj.getClass().getName());
404             }
405         }
406     }
407 
408 /*
409  * Let ObjectListFunction be a BinaryFunction that operates on
410  * an Object, List pair:
411  */
412 
413     public abstract class ObjectListFunction implements BinaryFunction<Object, Object, Object> {
414         public abstract Object evaluate(Object head, List<?> tail);
415 
416         public Object evaluate(Object left, Object right) {
417             if (left != null && right instanceof List) {
418                 return evaluate(left, (List<?>) right);
419             } else if (null == left) {
420                 throw new NullPointerException("The left argument must not be null.");
421             } else if (null == right) {
422                 throw new NullPointerException("The right argument must not be null.");
423             } else {
424                 throw new ClassCastException(
425                     "The right argument must be a List, found " +
426                     right.getClass().getName());
427             }
428         }
429     }
430 
431 /*
432  * Now for the implementations.
433  *
434  * Given a List, we need to be able to break it into its head:
435  */
436 
437     private Function<Object, Object> head = new ListFunction() {
438         @Override
439         public Object evaluate(List<?> list) {
440             return list.get(0);
441         }
442     };
443 
444 /*
445  * and its tail:
446  */
447     private Function<Object, Object> tail = new ListFunction() {
448         @Override
449         public Object evaluate(List<?> list) {
450             return list.size() < 2 ?
451                 Collections.EMPTY_LIST :
452                 list.subList(1, list.size());
453         }
454     };
455 
456 /*
457  * Given a List in head/tail form, we should be able to find
458  * the list of elements in the tail less than the head.
459  * We can simply apply the select algorithm here, using
460  * a predicate identifying elements less than the head.
461  */
462     @SuppressWarnings("rawtypes")
463     private BinaryFunction<Object, Object, Object> lesserTail = new ObjectListFunction() {
464         @Override
465         public Object evaluate(Object head, List<?> tail) {
466             return new FilteredGenerator(
467                     IteratorToGeneratorAdapter.adapt(tail.iterator()),
468                     IsLessThan.instance((Comparable<?>) head)).toCollection();
469         }
470     };
471 
472 /*
473  * We must also be able to find the List of elements in
474  * the tail greater than (or equal to) the head. This
475  * is similar to the lesserTail approach.
476  */
477     @SuppressWarnings("rawtypes")
478     private BinaryFunction greaterTail = new ObjectListFunction() {
479         @Override
480         public Object evaluate(Object head, List<?> tail) {
481             return new FilteredGenerator(
482                     IteratorToGeneratorAdapter.adapt(tail.iterator()),
483                  IsGreaterThanOrEqual.instance((Comparable<?>) head)).toCollection();
484         }
485     };
486 
487 /*
488  * Note that each of these smaller functors is readily testable
489  * in isolation:
490  */
491 
492     public void testHeadFunction() {
493         List<String> list = new ArrayList<String>();
494         try {
495             head.evaluate(list);
496             fail("Expected IndexOutOfBoundsException when evaluating head of an empty list");
497         } catch(IndexOutOfBoundsException e) {
498             // expected
499         }
500 
501         list.add("First");
502         assertEquals("First",head.evaluate(list));
503 
504         list.add("Second");
505         assertEquals("First",head.evaluate(list));
506 
507     }
508 
509     public void testTailFunction() {
510         List<String> list = new ArrayList<String>();
511         {
512             List<String> result = (List<String>)(tail.evaluate(list));
513             assertTrue("Tail of an empty list is empty.",result.isEmpty());
514         }
515         list.add("First");
516         {
517             List<String> result = (List<String>)(tail.evaluate(list));
518             assertTrue("Tail of a one element list is empty.",result.isEmpty());
519         }
520         list.add("Second");
521         {
522             List<String> result = (List<String>)(tail.evaluate(list));
523             assertEquals("Tail of a two element list has one element.",1,result.size());
524             assertEquals("Second",result.get(0));
525         }
526         list.add("Third");
527         {
528             List<String> result = (List<String>)(tail.evaluate(list));
529             assertEquals("Tail of a three element list has two elements.",2,result.size());
530             assertEquals("Second",result.get(0));
531             assertEquals("Third",result.get(1));
532         }
533     }
534 
535     public void testLesserTail() {
536         List<Integer> list = new ArrayList<Integer>();
537         for (int i=0;i<10;i++) {
538             list.add(Integer.valueOf(i));
539         }
540         for (int i=0;i<10;i++) {
541             Integer val = Integer.valueOf(i);
542             List<Integer> lesser = (List<Integer>) lesserTail.evaluate(val,list);
543             assertEquals(i,lesser.size());
544             for (int j=0;j<i;j++) {
545                 assertEquals(Integer.valueOf(j),lesser.get(j));
546             }
547         }
548     }
549 
550     public void testGreaterTail() {
551         List<Integer> list = new ArrayList<Integer>();
552         for (int i=0;i<10;i++) {
553             list.add(Integer.valueOf(i));
554         }
555         for (int i=0;i<10;i++) {
556             Integer val = Integer.valueOf(i);
557             List<Integer> greater = (List<Integer>) greaterTail.evaluate(val,list);
558             assertEquals(10-i,greater.size());
559             for (int j=i;j<10;j++) {
560                 assertEquals(Integer.valueOf(j),greater.get(j-i));
561             }
562         }
563     }
564 }