blob: 67c5a3b8a2a85ca43845b0c89f290efbac7011b0 [file] [log] [blame]
/*
* Copyright (C) 2010 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.common.collect;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtCompatible;
import com.google.common.base.Function;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.RandomAccess;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
/**
* Static methods pertaining to sorted {@link List} instances.
*
* <p>In this documentation, the terms <i>greatest</i>, <i>greater</i>, <i>least</i>, and
* <i>lesser</i> are considered to refer to the comparator on the elements, and the terms
* <i>first</i> and <i>last</i> are considered to refer to the elements' ordering in a list.
*
* @author Louis Wasserman
*/
@GwtCompatible
@Beta final class SortedLists {
private SortedLists() {}
/**
* A specification for which index to return if the list contains at least one element that
* compares as equal to the key.
*/ enum KeyPresentBehavior {
/**
* Return the index of any list element that compares as equal to the key. No guarantees are
* made as to which index is returned, if more than one element compares as equal to the key.
*/
ANY_PRESENT {
@Override
<E> int resultIndex(
Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex) {
return foundIndex;
}
},
/** Return the index of the last list element that compares as equal to the key. */
LAST_PRESENT {
@Override
<E> int resultIndex(
Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex) {
// Of course, we have to use binary search to find the precise
// breakpoint...
int lower = foundIndex;
int upper = list.size() - 1;
// Everything between lower and upper inclusive compares at >= 0.
while (lower < upper) {
int middle = (lower + upper + 1) >>> 1;
int c = comparator.compare(list.get(middle), key);
if (c > 0) {
upper = middle - 1;
} else { // c == 0
lower = middle;
}
}
return lower;
}
},
/** Return the index of the first list element that compares as equal to the key. */
FIRST_PRESENT {
@Override
<E> int resultIndex(
Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex) {
// Of course, we have to use binary search to find the precise
// breakpoint...
int lower = 0;
int upper = foundIndex;
// Of course, we have to use binary search to find the precise breakpoint...
// Everything between lower and upper inclusive compares at <= 0.
while (lower < upper) {
int middle = (lower + upper) >>> 1;
int c = comparator.compare(list.get(middle), key);
if (c < 0) {
lower = middle + 1;
} else { // c == 0
upper = middle;
}
}
return lower;
}
},
/**
* Return the index of the first list element that compares as greater than the key, or {@code
* list.size()} if there is no such element.
*/
FIRST_AFTER {
@Override
public <E> int resultIndex(
Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex) {
return LAST_PRESENT.resultIndex(comparator, key, list, foundIndex) + 1;
}
},
/**
* Return the index of the last list element that compares as less than the key, or {@code -1}
* if there is no such element.
*/
LAST_BEFORE {
@Override
public <E> int resultIndex(
Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex) {
return FIRST_PRESENT.resultIndex(comparator, key, list, foundIndex) - 1;
}
};
abstract <E> int resultIndex(
Comparator<? super E> comparator, E key, List<? extends E> list, int foundIndex);
}
/**
* A specification for which index to return if the list contains no elements that compare as
* equal to the key.
*/ enum KeyAbsentBehavior {
/**
* Return the index of the next lower element in the list, or {@code -1} if there is no such
* element.
*/
NEXT_LOWER {
@Override
int resultIndex(int higherIndex) {
return higherIndex - 1;
}
},
/**
* Return the index of the next higher element in the list, or {@code list.size()} if there is
* no such element.
*/
NEXT_HIGHER {
@Override
public int resultIndex(int higherIndex) {
return higherIndex;
}
},
/**
* Return {@code ~insertionIndex}, where {@code insertionIndex} is defined as the point at which
* the key would be inserted into the list: the index of the next higher element in the list, or
* {@code list.size()} if there is no such element.
*
* <p>Note that the return value will be {@code >= 0} if and only if there is an element of the
* list that compares as equal to the key.
*
* <p>This is equivalent to the behavior of {@link java.util.Collections#binarySearch(List,
* Object)} when the key isn't present, since {@code ~insertionIndex} is equal to {@code -1 -
* insertionIndex}.
*/
INVERTED_INSERTION_INDEX {
@Override
public int resultIndex(int higherIndex) {
return ~higherIndex;
}
};
abstract int resultIndex(int higherIndex);
}
/**
* Searches the specified naturally ordered list for the specified object using the binary search
* algorithm.
*
* <p>Equivalent to {@link #binarySearch(List, Function, Object, Comparator, KeyPresentBehavior,
* KeyAbsentBehavior)} using {@link Ordering#natural}.
*/
public static <E extends Comparable> int binarySearch(
List<? extends E> list,
E e,
KeyPresentBehavior presentBehavior,
KeyAbsentBehavior absentBehavior) {
checkNotNull(e);
return binarySearch(list, e, Ordering.natural(), presentBehavior, absentBehavior);
}
/**
* Binary searches the list for the specified key, using the specified key function.
*
* <p>Equivalent to {@link #binarySearch(List, Function, Object, Comparator, KeyPresentBehavior,
* KeyAbsentBehavior)} using {@link Ordering#natural}.
*/
public static <E, K extends Comparable> int binarySearch(
List<E> list,
Function<? super E, K> keyFunction,
@NullableDecl K key,
KeyPresentBehavior presentBehavior,
KeyAbsentBehavior absentBehavior) {
return binarySearch(
list, keyFunction, key, Ordering.natural(), presentBehavior, absentBehavior);
}
/**
* Binary searches the list for the specified key, using the specified key function.
*
* <p>Equivalent to {@link #binarySearch(List, Object, Comparator, KeyPresentBehavior,
* KeyAbsentBehavior)} using {@link Lists#transform(List, Function) Lists.transform(list,
* keyFunction)}.
*/
public static <E, K> int binarySearch(
List<E> list,
Function<? super E, K> keyFunction,
@NullableDecl K key,
Comparator<? super K> keyComparator,
KeyPresentBehavior presentBehavior,
KeyAbsentBehavior absentBehavior) {
return binarySearch(
Lists.transform(list, keyFunction), key, keyComparator, presentBehavior, absentBehavior);
}
/**
* Searches the specified list for the specified object using the binary search algorithm. The
* list must be sorted into ascending order according to the specified comparator (as by the
* {@link Collections#sort(List, Comparator) Collections.sort(List, Comparator)} method), prior to
* making this call. If it is not sorted, the results are undefined.
*
* <p>If there are elements in the list which compare as equal to the key, the choice of {@link
* KeyPresentBehavior} decides which index is returned. If no elements compare as equal to the
* key, the choice of {@link KeyAbsentBehavior} decides which index is returned.
*
* <p>This method runs in log(n) time on random-access lists, which offer near-constant-time
* access to each list element.
*
* @param list the list to be searched.
* @param key the value to be searched for.
* @param comparator the comparator by which the list is ordered.
* @param presentBehavior the specification for what to do if at least one element of the list
* compares as equal to the key.
* @param absentBehavior the specification for what to do if no elements of the list compare as
* equal to the key.
* @return the index determined by the {@code KeyPresentBehavior}, if the key is in the list;
* otherwise the index determined by the {@code KeyAbsentBehavior}.
*/
public static <E> int binarySearch(
List<? extends E> list,
@NullableDecl E key,
Comparator<? super E> comparator,
KeyPresentBehavior presentBehavior,
KeyAbsentBehavior absentBehavior) {
checkNotNull(comparator);
checkNotNull(list);
checkNotNull(presentBehavior);
checkNotNull(absentBehavior);
if (!(list instanceof RandomAccess)) {
list = Lists.newArrayList(list);
}
// TODO(lowasser): benchmark when it's best to do a linear search
int lower = 0;
int upper = list.size() - 1;
while (lower <= upper) {
int middle = (lower + upper) >>> 1;
int c = comparator.compare(key, list.get(middle));
if (c < 0) {
upper = middle - 1;
} else if (c > 0) {
lower = middle + 1;
} else {
return lower
+ presentBehavior.resultIndex(
comparator, key, list.subList(lower, upper + 1), middle - lower);
}
}
return absentBehavior.resultIndex(lower);
}
}