I have a List of object sorted and I want to find the first occurrence and the last occurrence of an object. In C++, I can easily use std::equal_range (or just one lower_bound and one upper_bound).
For example:
bool mygreater (int i,int j) { return (i>j); }
int main () {
int myints[] = {10,20,30,30,20,10,10,20};
std::vector<int> v(myints,myints+8); // 10 20 30 30 20 10 10 20
std::pair<std::vector<int>::iterator,std::vector<int>::iterator> bounds;
// using default comparison:
std::sort (v.begin(), v.end()); // 10 10 10 20 20 20 30 30
bounds=std::equal_range (v.begin(), v.end(), 20); // ^ ^
// using "mygreater" as comp:
std::sort (v.begin(), v.end(), mygreater); // 30 30 20 20 20 10 10 10
bounds=std::equal_range (v.begin(), v.end(), 20, mygreater); // ^ ^
std::cout << "bounds at positions " << (bounds.first - v.begin());
std::cout << " and " << (bounds.second - v.begin()) << '\n';
return 0;
}
In Java, there seems to be no simple equivalence? How should I do with the equal range with
List<MyClass> myList;
By the way, I am using a standard import java.util.List;
In Java, you use Collections.binarySearch
to find the lower bound of the equal range in a sorted list (Arrays.binarySearch
provides a similar capability for arrays). Then you continue iterating linearly until you hit to the end of the equal range.
These methods work for methods implementing the Comparable
interface. For classes that do not implement the Comparable
, you can supply an instance of a custom Comparator
for comparing the elements of your specific type.
You can try something like this:
public class TestSOF {
private ArrayList <Integer> testList = new ArrayList <Integer>();
private Integer first, last;
public void fillArray(){
testList.add(10);
testList.add(20);
testList.add(30);
testList.add(30);
testList.add(20);
testList.add(10);
testList.add(10);
testList.add(20);
}
public ArrayList getArray(){
return this.testList;
}
public void sortArray(){
Collections.sort(testList);
}
public void checkPosition(int element){
if (testList.contains(element)){
first = testList.indexOf(element);
last = testList.lastIndexOf(element);
System.out.println("The element " + element + "has it's first appeareance on position "
+ first + "and it's last on position " + last);
}
else{
System.out.println("Your element " + element + " is not into the arraylist!");
}
}
public static void main (String [] args){
TestSOF testSOF = new TestSOF();
testSOF.fillArray();
testSOF.sortArray();
testSOF.checkPosition(20);
}
}
In binary search , when you find the element then you can keep doing binary search to its left in order to find first occurrence and to right in order to find last element.
The idea should be clear with the code:
/*
B: element to find first or last occurrence of
searchFirst: true to find first occurrence, false to find last
*/
Integer bound(final List<Integer> A,int B,boolean searchFirst){
int n = A.size();
int low = 0;
int high = n-1;
int res = -1; //if element not found
int mid ;
while(low<=high){
mid = low+(high-low)/2;
if(A.get(mid)==B){
res=mid;
if(searchFirst){high=mid-1;} //to find first , go left
else{low=mid+1;} // to find last, go right
}
else if(B>A.get(mid)){low=mid+1;}
else{high=mid-1;}
}
return res;
}
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Collections;
import java.util.Vector;
public class Bounds {
public static void main(String[] args) throws IOException {
Vector<Float> data = new Vector<>();
for (int i = 29; i >= 0; i -= 2) {
data.add(Float.valueOf(i));
}
Collections.sort(data);
float element = 14;
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter log = new BufferedWriter(new OutputStreamWriter(System.out));
String string = bf.readLine();
while (!string.equals("q")) {
element=Float.parseFloat(string);
int first = 0;
int last = data.size();
int mid;
while (first < last) {
mid = first + ((last - first) >> 1);
if (data.get(mid) < element) //lower bound. for upper use <=
first = mid + 1;
else
last = mid;
}
log.write("data is: "+data+"\n");
if(first==data.size())
first=data.size()-1;
log.write("element is : " + first+ "\n");
log.flush();
string= bf.readLine();
}
bf.close();
}
}
This is the implementation for lower_bound and upper_bound similar to c++.
Note that the element you are searching for need not be present in the vector or list. This implementation only gives the element's upper and lower bounds.
Java have already built-in binary search functionality that calculates lower/upper bounds for an element in an array, there is no need to implement custom methods.
When we speak about upper/lower bounds or equal ranges, we always mean indexes of a container (in this case of ArrayList), and not the elements contained.
Let's consider an array (we assume the array is sorted, otherwise we sort it first):
List<Integer> nums = new ArrayList<>(Arrays.asList(2,3,5,5,7,9,10,18,22));
The "lower bound" function must return the index of the array, where the element must be inserted to keep the array sorted. The "upper bound" must return the index of the smallest element in the array, that is bigger than the looked for element.
For example
lowerBound(nums, 6)
must return 3, because 3 is the position of the array (starting counting with 0), where 6 must be inserted to keep array sorted.
The
upperBound(nums, 6)
must return 4, because 4 is the position of the smallest element in the array, that is bigger than 5 or 6, (number 7 on position 4).
In C++ in standard library the both algorithms already implemented in standard library. In Java you can use
Collections.binarySearch(nums, element)
to calculate the position in logarithmic time complexity.
If the array contains the element, Collections.binarySearch returns the first index of the element (in the array above 2). Otherwise it returns a negative number that specifies the position in the array of the next bigger element, counting backwards from the last index of the array. The number found in this position is the smallest element of the array that is bigger than the element you look for.
For example, if you call
int idx = Collections.binarySearch(nums, 6)
the function returns -5. If you count backwards from the last index of the array (-1, -2, ...) the index -5 points to number 7 - the smallest number in the array that is bigger than the element 6.
Conclusion:
if the sorted array contains the looked for element, the lower bound is the position of the element, and the upper bound is the position of the next bigger element.
If the array does not contains the element, the lower bound is the position
Math.abs(idx) - 2
and the upper bound is the position
Math.abs(idx) - 1
where
idx = Collections.binarySearch(nums, element)
And please always keep in mind the border cases. For example, if you look for 1 in the above specified array:
idx = Collections.binarySearch(nums, 1)
The functon returns -1. So, the upperBound = Math.abs(idx) - 1 = 0 - the element 2 at position 0. But there is no lower bound for element 1, because 2 is the smallest number in the array.
The same logic applies to elements bigger than the biggest number in the array: if you look for lower/upper bounds of number 25, you will get
idx = Collections.binarySearch(nums, 25)
ix = -10. You can calculate the lower bound : lb = Math.abs(-10) - 2 = 8, that is the last index of the array, but there is no upper bound, because 22 is already biggest element in the array and there is no element at position 9.
The equal_range specifies all indexes of the array in the range starting from the lower bound index up to (but not including) the upper bound.
For example, the equal range of number 5 in the array above are indexes
[2,3]
Equal range of number 6 is empty, because there is no number 6 in the array.