Home | History | Annotate | Download | only in collect
      1 /*
      2  * Copyright (C) 2010 The Guava Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 
     15 package com.google.common.collect;
     16 
     17 import static com.google.common.base.Preconditions.checkNotNull;
     18 
     19 import com.google.common.annotations.Beta;
     20 import com.google.common.annotations.GwtCompatible;
     21 import com.google.common.base.Function;
     22 
     23 import java.util.Collections;
     24 import java.util.Comparator;
     25 import java.util.List;
     26 import java.util.RandomAccess;
     27 
     28 import javax.annotation.Nullable;
     29 
     30 /**
     31  * Static methods pertaining to sorted {@link List} instances.
     32  *
     33  * In this documentation, the terms <i>greatest</i>, <i>greater</i>, <i>least</i>, and
     34  * <i>lesser</i> are considered to refer to the comparator on the elements, and the terms
     35  * <i>first</i> and <i>last</i> are considered to refer to the elements' ordering in a
     36  * list.
     37  *
     38  * @author Louis Wasserman
     39  */
     40 @GwtCompatible
     41 @Beta final class SortedLists {
     42   private SortedLists() {}
     43 
     44   /**
     45    * A specification for which index to return if the list contains at least one element that
     46    * compares as equal to the key.
     47    */
     48   public enum KeyPresentBehavior {
     49     /**
     50      * Return the index of any list element that compares as equal to the key. No guarantees are
     51      * made as to which index is returned, if more than one element compares as equal to the key.
     52      */
     53     ANY_PRESENT {
     54       @Override
     55       <E> int resultIndex(
     56           Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex) {
     57         return foundIndex;
     58       }
     59     },
     60     /**
     61      * Return the index of the last list element that compares as equal to the key.
     62      */
     63     LAST_PRESENT {
     64       @Override
     65       <E> int resultIndex(
     66           Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex) {
     67         // Of course, we have to use binary search to find the precise
     68         // breakpoint...
     69         int lower = foundIndex;
     70         int upper = list.size() - 1;
     71         // Everything between lower and upper inclusive compares at >= 0.
     72         while (lower < upper) {
     73           int middle = (lower + upper + 1) >>> 1;
     74           int c = comparator.compare(list.get(middle), key);
     75           if (c > 0) {
     76             upper = middle - 1;
     77           } else { // c == 0
     78             lower = middle;
     79           }
     80         }
     81         return lower;
     82       }
     83     },
     84     /**
     85      * Return the index of the first list element that compares as equal to the key.
     86      */
     87     FIRST_PRESENT {
     88       @Override
     89       <E> int resultIndex(
     90           Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex) {
     91         // Of course, we have to use binary search to find the precise
     92         // breakpoint...
     93         int lower = 0;
     94         int upper = foundIndex;
     95         // Of course, we have to use binary search to find the precise breakpoint...
     96         // Everything between lower and upper inclusive compares at <= 0.
     97         while (lower < upper) {
     98           int middle = (lower + upper) >>> 1;
     99           int c = comparator.compare(list.get(middle), key);
    100           if (c < 0) {
    101             lower = middle + 1;
    102           } else { // c == 0
    103             upper = middle;
    104           }
    105         }
    106         return lower;
    107       }
    108     },
    109     /**
    110      * Return the index of the first list element that compares as greater than the key, or {@code
    111      * list.size()} if there is no such element.
    112      */
    113     FIRST_AFTER {
    114       @Override
    115       public <E> int resultIndex(
    116           Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex) {
    117         return LAST_PRESENT.resultIndex(comparator, key, list, foundIndex) + 1;
    118       }
    119     },
    120     /**
    121      * Return the index of the last list element that compares as less than the key, or {@code -1}
    122      * if there is no such element.
    123      */
    124     LAST_BEFORE {
    125       @Override
    126       public <E> int resultIndex(
    127           Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex) {
    128         return FIRST_PRESENT.resultIndex(comparator, key, list, foundIndex) - 1;
    129       }
    130     };
    131     abstract <E> int resultIndex(
    132         Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex);
    133   }
    134 
    135   /**
    136    * A specification for which index to return if the list contains no elements that compare as
    137    * equal to the key.
    138    */
    139   public enum KeyAbsentBehavior {
    140     /**
    141      * Return the index of the next lower element in the list, or {@code -1} if there is no such
    142      * element.
    143      */
    144     NEXT_LOWER {
    145       @Override
    146       <E> int resultIndex(int higherIndex) {
    147         return higherIndex - 1;
    148       }
    149     },
    150     /**
    151      * Return the index of the next higher element in the list, or {@code list.size()} if there is
    152      * no such element.
    153      */
    154     NEXT_HIGHER {
    155       @Override
    156       public <E> int resultIndex(int higherIndex) {
    157         return higherIndex;
    158       }
    159     },
    160     /**
    161      * Return {@code ~insertionIndex}, where {@code insertionIndex} is defined as the point at
    162      * which the key would be inserted into the list: the index of the next higher element in the
    163      * list, or {@code list.size()} if there is no such element.
    164      *
    165      * <p>Note that the return value will be {@code >= 0} if and only if there is an element of the
    166      * list that compares as equal to the key.
    167      *
    168      * <p>This is equivalent to the behavior of
    169      * {@link java.util.Collections#binarySearch(List, Object)} when the key isn't present, since
    170      * {@code ~insertionIndex} is equal to {@code -1 - insertionIndex}.
    171      */
    172     INVERTED_INSERTION_INDEX {
    173       @Override
    174       public <E> int resultIndex(int higherIndex) {
    175         return ~higherIndex;
    176       }
    177     };
    178 
    179     abstract <E> int resultIndex(int higherIndex);
    180   }
    181 
    182   /**
    183    * Searches the specified naturally ordered list for the specified object using the binary search
    184    * algorithm.
    185    *
    186    * <p>Equivalent to {@link #binarySearch(List, Function, Object, Comparator, KeyPresentBehavior,
    187    * KeyAbsentBehavior)} using {@link Ordering#natural}.
    188    */
    189   public static <E extends Comparable> int binarySearch(List<? extends E> list, E e,
    190       KeyPresentBehavior presentBehavior, KeyAbsentBehavior absentBehavior) {
    191     checkNotNull(e);
    192     return binarySearch(
    193         list, checkNotNull(e), Ordering.natural(), presentBehavior, absentBehavior);
    194   }
    195 
    196   /**
    197    * Binary searches the list for the specified key, using the specified key function.
    198    *
    199    * <p>Equivalent to {@link #binarySearch(List, Function, Object, Comparator, KeyPresentBehavior,
    200    * KeyAbsentBehavior)} using {@link Ordering#natural}.
    201    */
    202   public static <E, K extends Comparable> int binarySearch(List<E> list,
    203       Function<? super E, K> keyFunction, K key, KeyPresentBehavior presentBehavior,
    204       KeyAbsentBehavior absentBehavior) {
    205     return binarySearch(
    206         list,
    207         keyFunction,
    208         key,
    209         Ordering.natural(),
    210         presentBehavior,
    211         absentBehavior);
    212   }
    213 
    214   /**
    215    * Binary searches the list for the specified key, using the specified key function.
    216    *
    217    * <p>Equivalent to
    218    * {@link #binarySearch(List, Object, Comparator, KeyPresentBehavior, KeyAbsentBehavior)} using
    219    * {@link Lists#transform(List, Function) Lists.transform(list, keyFunction)}.
    220    */
    221   public static <E, K> int binarySearch(
    222       List<E> list,
    223       Function<? super E, K> keyFunction,
    224       K key,
    225       Comparator<? super K> keyComparator,
    226       KeyPresentBehavior presentBehavior,
    227       KeyAbsentBehavior absentBehavior) {
    228     return binarySearch(
    229         Lists.transform(list, keyFunction), key, keyComparator, presentBehavior, absentBehavior);
    230   }
    231 
    232   /**
    233    * Searches the specified list for the specified object using the binary search algorithm. The
    234    * list must be sorted into ascending order according to the specified comparator (as by the
    235    * {@link Collections#sort(List, Comparator) Collections.sort(List, Comparator)} method), prior
    236    * to making this call. If it is not sorted, the results are undefined.
    237    *
    238    * <p>If there are elements in the list which compare as equal to the key, the choice of
    239    * {@link KeyPresentBehavior} decides which index is returned. If no elements compare as equal to
    240    * the key, the choice of {@link KeyAbsentBehavior} decides which index is returned.
    241    *
    242    * <p>This method runs in log(n) time on random-access lists, which offer near-constant-time
    243    * access to each list element.
    244    *
    245    * @param list the list to be searched.
    246    * @param key the value to be searched for.
    247    * @param comparator the comparator by which the list is ordered.
    248    * @param presentBehavior the specification for what to do if at least one element of the list
    249    *        compares as equal to the key.
    250    * @param absentBehavior the specification for what to do if no elements of the list compare as
    251    *        equal to the key.
    252    * @return the index determined by the {@code KeyPresentBehavior}, if the key is in the list;
    253    *         otherwise the index determined by the {@code KeyAbsentBehavior}.
    254    */
    255   public static <E> int binarySearch(List<? extends E> list, @Nullable E key,
    256       Comparator<? super E> comparator, KeyPresentBehavior presentBehavior,
    257       KeyAbsentBehavior absentBehavior) {
    258     checkNotNull(comparator);
    259     checkNotNull(list);
    260     checkNotNull(presentBehavior);
    261     checkNotNull(absentBehavior);
    262     if (!(list instanceof RandomAccess)) {
    263       list = Lists.newArrayList(list);
    264     }
    265     // TODO(user): benchmark when it's best to do a linear search
    266 
    267     int lower = 0;
    268     int upper = list.size() - 1;
    269 
    270     while (lower <= upper) {
    271       int middle = (lower + upper) >>> 1;
    272       int c = comparator.compare(key, list.get(middle));
    273       if (c < 0) {
    274         upper = middle - 1;
    275       } else if (c > 0) {
    276         lower = middle + 1;
    277       } else {
    278         return lower + presentBehavior.resultIndex(
    279             comparator, key, list.subList(lower, upper + 1), middle - lower);
    280       }
    281     }
    282     return absentBehavior.resultIndex(lower);
    283   }
    284 }
    285