Thinking logic of computer programs (53), thinking 53
The previous sections introduced various specific container classes and abstract container classes. As we mentioned above, Java has a collection class that provides many common functions for container interfaces, these functions are provided in static mode.
What functions are available? There are two possible types:
For the first type, operations can be divided into three groups:
- Search and replace
- Sort and adjust order
- Add and modify
The second type can be divided into two groups:
- Adapter: convert other types of data to container interface objects
- Modifier: modifies a given container interface object and adds a certain property.
They all focus on container interface objects. The first type is general operations for container interfaces. This is a reflection of interface-Oriented Programming introduced in the essence section of interfaces, this is a typical interface usage. The second type is to make more types of data more convenient and secure to participate in the Container-type collaboration system.
Due to the large amount of content, we can divide it into two sections. This section discusses the first category, and the next section discusses the second category. Next, let's look at the algorithms in the first category.
Search and replace
Search and replace include multiple methods. Let's take a look at them.
Binary Search
We introduced binary lookup when analyzing the Arrays class. The Arrays class has a binary lookup Method for array objects. Collections provides binary lookup for the List interface, as shown below:
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)
It is easy to understand from method parameters that one requires that each element of the List implement the Comparable interface, while the other does not, but requires that Comparator be provided.
Binary Search assumes that the elements in the List are sorted in ascending order. If it is sorted from large to small, it is easy to pass a backward Comparator object. Collections provides the method to return the backward Comparator, which we have used before:
public static <T> Comparator<T> reverseOrder()public static <T> Comparator<T> reverseOrder(Comparator<T> cmp)
For example, it can be used as follows:
List<Integer> list = new ArrayList<>(Arrays.asList(new Integer[]{ 35, 24, 13, 12, 8, 7, 1}));System.out.println(Collections.binarySearch(list, 7, Collections.reverseOrder()));
Output:
5
The basic idea of List binary search is the same as that in Arrays. However, it is very efficient to directly locate any element in an Array Based on the index, but the List is not necessarily the same, let's look at its implementation code:
public static <T>int binarySearch(List<? extends Comparable<? super T>> list, T key) { if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) return Collections.indexedBinarySearch(list, key); else return Collections.iteratorBinarySearch(list, key);}
In two cases, if the List can be accessed randomly (such as an array), that is, the RandomAccess interface is implemented, or the number of elements is relatively small, the implementation idea is the same as that of Arrays, call indexedBinarySearch to directly access the intermediate element based on the index for search. Otherwise, call iteratorBinarySearch to access the intermediate element for search using the iterator.
The Code of indexedBinarySearch is:
private static <T>int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key){ int low = 0; int high = list.size()-1; while (low <= high) { int mid = (low + high) >>> 1; Comparable<? super T> midVal = list.get(mid); int cmp = midVal.compareTo(key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found}
Call list. get (mid) to access the intermediate element.
The iteratorBinarySearch code is:
private static <T>int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key){ int low = 0; int high = list.size()-1; ListIterator<? extends Comparable<? super T>> i = list.listIterator(); while (low <= high) { int mid = (low + high) >>> 1; Comparable<? super T> midVal = get(i, mid); int cmp = midVal.compareTo(key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found}
Call get (I, mid) to find the intermediate element. The get method code is:
private static <T> T get(ListIterator<? extends T> i, int index) { T obj = null; int pos = i.nextIndex(); if (pos <= index) { do { obj = i.next(); } while (pos++ < index); } else { do { obj = i.previous(); } while (--pos > index); } return obj;}
You can use the iterator method to move one by one to the expected position.
Let's analyze the efficiency. If the List supports random access, the efficiency is O (log2 (N). If the iterator is used, the number of comparisons is O (log2 (N )), however, the number of traversal moves is O (N), and N is the list length.
Find the Maximum/minimum values
Collections provides the following methods to find the maximum and minimum values:
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp)public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)public static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp)
The meaning and usage are very direct, and the Implementation idea is very simple, that is, compare with the iterator. For example, the Code of one of the methods is:
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) { Iterator<? extends T> i = coll.iterator(); T candidate = i.next(); while (i.hasNext()) { T next = i.next(); if (next.compareTo(candidate) > 0) candidate = next; } return candidate;}
Other methods will not be described in detail.
Number of times an element appears
Method:
public static int frequency(Collection<?> c, Object o)
Returns the number of times that the element o appears in container c. The o value can be null. The meaning is very simple. The implementation idea is also to compare and count through the iterator.
Find sub-List
In the analysis of the String class section, we have introduced that the String class has the following methods to find Sub-strings:
public int indexOf(String str)public int lastIndexOf(String str)
For List interface objects, Collections provides a similar method to find the target List position in the source List:
public static int indexOfSubList(List<?> source, List<?> target)public static int lastIndexOfSubList(List<?> source, List<?> target)
IndexOfSubList is searched from the beginning, lastIndexOfSubList is searched from the end,-1 is not returned, and the index location of the First Matching Element is returned, for example:
List<Integer> source = Arrays.asList(new Integer[]{ 35, 24, 13, 12, 8, 24, 13, 7, 1});System.out.println(Collections.indexOfSubList(source, Arrays.asList(new Integer[]{24, 13})));System.out.println(Collections.lastIndexOfSubList(source, Arrays.asList(new Integer[]{24, 13})));
Output:
15
The implementation of both methods belongs to the "brute-force cracking" type. The target list and the source list start from the first element and are compared one by one. If they do not match, it is compared with the list of source Elements starting from the second element. It does not match any more. It is compared with the list of source Elements starting from the third element, and so on.
Check whether two sets have an intersection
Method:
public static boolean disjoint(Collection<?> c1, Collection<?> c2)
If c1 and c2 have an intersection, the return value is false, no intersection, and the return value is true.
The implementation principle is also very simple. traverse one container and check whether this element is included in each element in another container by using the contains method. If this element is included, false is returned, returns true if it does not contain any element. The code of this method will optimize the performance based on whether the container is Set and the Set size, that is, select the container to traverse and the container to check to reduce the total number of comparisons, we will not go into details.
Replace
The replacement method is as follows:
public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)
Replace all oldVal values in the List with newVal. If yes, the return value is true. Otherwise, the return value is false. The usage and implementation are both simple and will not be described in detail.
Sort and adjust order
For List interface objects, Collections not only provides basic sorting, but also provides several methods to adjust the order, including switching element positions, turning List order, randomization shuffling, and cyclic shift, let's look at it one by one.
Sort
The Arrays class provides sorting methods for array objects. Collections provides sorting methods for the List interface, as shown below:
public static <T extends Comparable<? super T>> void sort(List<T> list)public static <T> void sort(List<T> list, Comparator<? super T> c)
It's easy to use. It's not an example. Inside, it uses Arrays. in sort, first copy the List element to an array, and then use Arrays. sort. After sorting, copy back to List. The Code is as follows:
public static <T extends Comparable<? super T>> void sort(List<T> list) { Object[] a = list.toArray(); Arrays.sort(a); ListIterator<T> i = list.listIterator(); for (int j=0; j<a.length; j++) { i.next(); i.set((T)a[j]); }}
Switch element location
Method:
public static void swap(List<?> list, int i, int j)
Exchange the content of the I and j elements in the list. Implementation Code:
public static void swap(List<?> list, int i, int j) { final List l = list; l.set(i, l.set(j, l.get(i)));}
Reverse list order
Method:
public static void reverse(List<?> list)
Turn over the element sequence in the list. The idea is to exchange the first and last elements, the second and the last, and so on until the two elements in the middle are fully exchanged.
If the list implements the RandomAccess interface or the list is small, use the swap method above to exchange the index location. Otherwise, the efficiency of locating elements based on the index location is relatively low, use one or two listIterator to locate the elements to be exchanged. The Code is as follows:
public static void reverse(List<?> list) { int size = list.size(); if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) { for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--) swap(list, i, j); } else { ListIterator fwd = list.listIterator(); ListIterator rev = list.listIterator(size); for (int i=0, mid=list.size()>>1; i<mid; i++) { Object tmp = fwd.next(); fwd.set(rev.previous()); rev.set(tmp); } }}
Randomization
We have introduced the shuffling algorithm in the random section. Collections directly provides the method for shuffling List elements:
public static void shuffle(List<?> list)public static void shuffle(List<?> list, Random rnd)
The implementation idea is the same as that described in the random section. The list is traversed from the back to the front, and each position is assigned a value one by one. The values are randomly selected from the elements that have not been assigned a value again. If the List implements the RandomAccess interface or the list is small, use the swap method to exchange the list directly. Otherwise, copy the list content to an array, shuffles it, And then copies it back to the list. The Code is as follows:
public static void shuffle(List<?> list, Random rnd) { int size = list.size(); if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) { for (int i=size; i>1; i--) swap(list, i-1, rnd.nextInt(i)); } else { Object arr[] = list.toArray(); // Shuffle array for (int i=size; i>1; i--) swap(arr, i-1, rnd.nextInt(i)); // Dump array back into list ListIterator it = list.listIterator(); for (int i=0; i<arr.length; i++) { it.next(); it.set(arr[i]); } }}
Cyclic shift
We will explain the concept of circular shift, for example, the list is:
[8, 5, 3, 6, 2]
The loop shifts two places to the right and changes:
[6, 2, 8, 5, 3]
If the cycle shifts two places to the left, the result will be changed:
[3, 6, 2, 8, 5]
Because the length of the list is 5, the result of moving three places to the left of the loop is the same as that of moving two places to the right of the loop.
The cyclic shift method is as follows:
public static void rotate(List<?> list, int distance)
Distance indicates the number of cyclic shifts. A positive number indicates moving to the right, and a negative number indicates moving to the left. For example:
List<Integer> list1 = Arrays.asList(new Integer[]{ 8, 5, 3, 6, 2});Collections.rotate(list1, 2);System.out.println(list1);List<Integer> list2 = Arrays.asList(new Integer[]{ 8, 5, 3, 6, 2});Collections.rotate(list2, -2);System.out.println(list2);
Output:
[6, 2, 8, 5, 3][3, 6, 2, 8, 5]
This method can also be used in sublists to adjust the order of sublists without changing the positions of other elements. For example, if you move the element j to k (k> j), you can write it as follows:
Collections.rotate(list.subList(j, k+1), -1);
Another example:
List<Integer> list = Arrays.asList(new Integer[]{ 8, 5, 3, 6, 2, 19, 21});Collections.rotate(list.subList(1, 5), 2);System.out.println(list);
Output:
[8, 6, 2, 5, 3, 19, 21]
This is similar to "cut" and "Paste" in the list. The sublist [5, 3] "cut", "Paste" to the end of 2. To implement functions similar to "cut" and "Paste", you can use the rotate method.
The internal implementation of cyclic shift is clever. Two algorithms are clever, based on the list size and whether the RandomAccess interface is implemented, the two algorithms are described in section 2.3 of programming Pearl.
Due to limited space, we only need to explain the second algorithm. It regards cyclic shift as two sublists in the list for sequential switching. Let's take a look at the example above. The cycle shifts two places to the left:
[8, 5, 3, 6, 2] -> [3, 6, 2, 8, 5]
It is to swap the order of the sublists [8, 5] and [3, 6, 2.
Shift two places to the right of the Loop:
[8, 5, 3, 6, 2] -> [6, 2, 8, 5, 3]
It is to swap the order of the sublists [8, 5, 3] and [6, 2.
Based on the List Length size and shift number distance, you can calculate the Split points of the two sub-lists. After two sub-lists are available, the Order switching between the two sub-lists can be achieved through three flip operations, for example, there are two sublists A and B, A has m elements, and B has n elements:
To change:
It can be flipped three times:
1. rotor flip List
2. rotor List B
3. Flip the entire list
The overall implementation code of this algorithm is:
private static void rotate2(List<?> list, int distance) { int size = list.size(); if (size == 0) return; int mid = -distance % size; if (mid < 0) mid += size; if (mid == 0) return; reverse(list.subList(0, mid)); reverse(list.subList(mid, size)); reverse(list);}
Mid is the split point of two sublists, and three reverse calls are called to realize the sequential exchange of sublists.
Add and modify
Collections also provides several methods for batch addition and modification. The logic is simple. Let's take a look.
Batch add
Method:
public static <T> boolean addAll(Collection<? super T> c, T... elements)
Elements is a variable parameter that adds all elements to container c. This method is very convenient. For example, you can do this:
List <String> list = new ArrayList <String> (); String [] arr = new String [] {"", ""}; Collections. addAll (list, "hello", "world", "lauma", "programming"); Collections. addAll (list, arr); System. out. println (list );
Output:
[Hello, world, lauma, programming, in-depth, simple]
Batch fill fixed values
Method:
public static <T> void fill(List<? super T> list, T obj)
This method is similar to the fill method in the Arrays class and sets the same value for each element.
Batch copy
Method:
public static <T> void copy(List<? super T> dest, List<? extends T> src)
Copy each element in the src list to the corresponding location of the dest list to overwrite the original value in the dest list. The length of the dest list cannot be less than src, elements that exceed the src length in dest are not affected.
Summary
This section describes some common algorithms in Collections, including searching, replacing, sorting, adjusting the order, adding, and modifying. These algorithms operate on container interface objects, this is an embodiment of interface-oriented programming. These algorithms can be used as long as objects implement these interfaces.
When collaborating with the containers and Collections algorithms, you often need to convert other types of data to container interface objects. Therefore, Collections also provides many methods. What are the methods? What is the purpose? What kind of design patterns and thinking are embodied? Let's continue exploring in the next section.
----------------
For more information, see the latest article. Please pay attention to the Public Account "lauma says programming" (scan the QR code below), from entry to advanced, ma and you explore the essence of Java programming and computer technology. Retain All copyrights with original intent.