\documentclass[a4paper,12pt]{article} \usepackage[english,vietnamese]{babel} \usepackage{amsmath} \usepackage{amssymb} \usepackage{booktabs} \usepackage{lmodern} \usepackage{graphicx} \usepackage{hyperref} \usepackage{lmodern} \usepackage{forest} \usepackage{textcomp} \usepackage[nottoc,numbib]{tocbibind} \renewcommand{\thefootnote}{\fnsymbol{footnote}} \begin{document} \setcounter{page}{0} \thispagestyle{empty} \vspace*{\stretch{1}} \begin{flushright} \setlength{\baselineskip}{1.4\baselineskip} \textbf{\Huge Midterm Report\\Sorting and Searching} \noindent\rule{\textwidth}{5pt} \emph{\Large Object Oriented Programming} \vspace{\stretch{1}} \textbf{by Nguyễn Công Thành, Nguyễn Gia Phong\\ Nguyễn Văn Tùng, Trần Minh Vương and Trần Hồng Minh\\} \selectlanguage{english} \today \end{flushright} \vspace*{\stretch{2}} \pagebreak \selectlanguage{english} \tableofcontents \pagebreak \section{Introduction} \subsection{Brief Description} Since the dawn of computer science, sorting and searching algorithms have drawn a significant amount of attention from researchers, due to their broad applications in solving a huge number of problems and sub-problems in many fields. In spite of their straightforward, familiar statements, these algorithm link with a variety of important programming concepts, namely data structures, divide and conquer technique and of course complexity analysis and time–space tradeoffs. In this report, we only discuss some of the most basic searching and sorting algorithms. While trying to implement them, we also analyze their strengths and weaknesses, as well as the ease and difficulties we encounter using the language Java and object-oriented programming in general. Whilst overall, we follow~\cite{wu}, we also try to make use of newer Java features to write more generic and concise code. This report is licensed under a \href{http://creativecommons.org/licenses/by-sa/4.0/}{Creative Commons Attribution-ShareAlike 4.0 International License.} \selectlanguage{vietnamese} \subsection{Authors and Credits} The work has been undertaken by group number 11, whose members are listed in the following table, with Nguyễn Công Thành being our leader. \begin{center} \begin{tabular}{c c} \toprule Full name & Student ID\\ \midrule Nguyễn Công Thành & BI9-210\\ Nguyễn Gia Phong & BI9-184\\ Nguyễn Văn Tùng & BI9-229\\ Trần Minh Vương & BI9-239\\ Trần Hồng Minh & BI8-114\\ \bottomrule \end{tabular} \end{center} We would like to express our special thanks to Dr. Nghiêm Thị Phương, whose lectures gave us basic understanding on the key principles of object-oriented programming. Without her guidance, we might never have a chance to take an in-depth exploration on this topic. \pagebreak \selectlanguage{english} \section{Searching} For the sake of simplicity, we only consider searching operating on array-like data structures with constant-time random access and without duplicated entries. This affects a few implementation design decision we will be making later on. The searching problem is then stated in ~\cite[p.~634]{wu} accordingly: \begin{quote} Given a value $x$, return the [zero-based] index of $x$ in the array, if such $x$ exists. Otherwise, return \verb|NOT_FOUND| (-1). \end{quote} In this section, we will discover two searching algorithms: linear search and binary search. Both of these, albeit being simple, are being used in many different programming tasks. \subsection{Linear Search} Linear search, by definition, sequentially checks each element of a list until a match is found or the whole list has been searched. This matches the algorithm implemented in \verb|java.util.List.indexOf|~\cite[indexOf]{list}. Given \begin{verbatim} var list = java.util.Arrays.asList(4, 20, 6, 9); \end{verbatim} where \verb|var| is a Java SE 10 feature for eliding ``the often-unnecessary manifest declaration of local variable types''~\cite{var}, which, in this case, is \verb|List|. \verb|list.indexOf(6)| would then give 2 and \verb|list.indexOf(7)| returns -1. For the matter of completeness, we also try to implement the description provided by the documentation~\cite[indexOf]{list} \begin{verbatim} import java.util.List; public class Search { public static final int NOT_FOUND = -1; public static linear(List l, Object o) { for (int i = 0; i < l.size(); ++i) if (o == null ? l.get(i) == null : o.equals(l.get(i))) return i; return NOT_FOUND; } } \end{verbatim} From this dummy implementation, it is trivial that linear search has, as it name suggests, \emph{linear time} complexity. The nullity check every iteration adds a bit of overhead, but if performance is ever concerned, the better tested and optimized standard library should be used instead. Moving \verb|o == null| to the upper level would require duplicating the \verb|for| loop, creating redundancy and making the code less readable and less maintainable. Unsurprisingly, 2 and -1 are returned from \verb|Search.linear(list, 6)| and \verb|Search.linear(list, 7)|, respectively. \subsection{Binary Search} Binary search find the position of a target value within a sorted array by ``testing the middle of an interval, eliminating the half of the [array] in which the key cannot lie, and then repeating the procedure iteratively'' \cite{bsearch}. This could be intuitively implemented using recursion: \begin{verbatim} // ... public class Search { // ... private static int binary(List> list, T key, int low, int high) { if (high < low) return NOT_FOUND; var mid = (low + high) / 2; var cmp = list.get(mid).compareTo(key); if (cmp < 0) return binary(list, key, mid + 1, high); if (cmp > 0) return binary(list, key, low, mid - 1); return mid; } } \end{verbatim} \verb|Search.binary| is declared as a \emph{generic method} to allow more implicit calls, where the compiler will \emph{infer} the type of \verb|key| as well as that of each element of \verb|list|~\cite{infer}. This makes the method work on any type of argument that is a subclass of \verb|Comparable|, which is required for \verb|Comparable.compareTo|. In fact, \verb|java.util.Collections.binarySearch|'s declaration follows the same strategy~\cite[binarySearch]{collections}. Instead of directly exposing this method where users need to provide the lower and higher bound of the indices, we overload it with a wrapper to ensure encapsulation: \begin{verbatim} // ... public class Search { // ... public static int binary(List> list, T key) { return binary(list, key, 0, list.size()); } } \end{verbatim} To test our implementation, we first need \verb|list| to be sorted. By providing \verb|null| to its \verb|sort| method, we can sort it in the elements' natural (ascending) order~\cite[sort]{list}. As \verb|list| is now [4, 6, 9, 20], \verb|Search.binary(list, 6)| and \verb|Search.binary(list, 7)| return 1 and -1 respectively. \verb|java.util.Collections.binarySearch(list, 7)| gives -3, however, due to its slightly different behavior: \begin{quote} [If \verb|key| is not in the list, returns] \verb|(-(insertion point) - 1)|. The insertion point is defined as the point at which the key would be inserted into the list: the index of the first element greater than the key, or \verb|list.size()| if all elements in the list are less than the specified key.~\cite[binarySearch]{collections} \end{quote} Since the algorithm only give up the search until the array is bisected to a nonpositive length (\verb|high < low|), the time-complexity of binary search is asymptotic to $\log_2 n$, where $n$ is the size of the array. In other words, on modern 64-bit computers with support for at most 18EB of address space, the running time is still negligible (64 comparisons). Despite the use of \verb|List| interface in the implementation, \verb|Search.binary| is not $\Theta(\lg n)$\footnote{The notation $f = \Theta(g)$ indicates that $f$ grows at the same rate as $g$.} on \verb|LinkedList|, which has $\Theta(n)$ random access. Since the algorithm only works on ordered lists, it is not suitable for iteratively inserting to a sorted list, as for linear data structures, either random access or middle insertion has to be $\Omega(n)$\footnote{The notation $f = \Omega(g)$ indicates that $f$ grows at least as as fast as $g$.}. In that case, self-balancing binary search tree should be used instead. \pagebreak \section{Sorting} The sorting problem is stated in~\cite[p. 638]{wu} as \begin{quote} Given an array of $n$ values, arrange the values into ascending order. \end{quote} This section discusses only three sorting algorithms, namely selection sort, bubble sort and heapsort. \subsection{Selection Sort} Selection sort could be performed by iterating through every position and select the minimum element from the current one to the end of the array. The in-place algorithm could be implemented in Java as follows: \begin{verbatim} import java.util.List; import static java.util.Collections.swap; public class Sort { public static > void selection(List list) { int i, j, m, n = list.size(); for (i = 0; i < n; ++i) { for (m = j = i; j < n; ++j) if (list.get(j).compareTo(list.get(m)) < 0) m = j; swap(list, i, m); } } } \end{verbatim} Since only atomic \verb|int|'s are introduced, the space complexity is $\Theta(1)$. The time complexity is, in term of swaps, $\Theta(n)$ and, in term of comparisons, \[T(n) = \sum_{i=0}^{n-1}\sum_{j=i}^{n-1}1 = \sum_{i=0}^{n-1}(n - i) = \sum_{i=1}^{n}i = \frac{n^2 + n}{2} = \Theta(n^2)\] One could micro-optimize by tweaking \verb|i|'s and \verb|j|'s bounds by an unit but that does not make the algorithm any faster asymptotically. Just to make sure everything works correctly, we do a simple check \begin{verbatim} var list = java.util.Arrays.asList("foo", "bar", "baz"); Sort.selection(list); System.out.println(list); \end{verbatim} and get \verb|[bar, baz, foo]| printed to \verb|stdout| as expected. \subsection{Bubble Sort} In~\cite[p. 646]{wu}, the author praised bubble sort to finish sooner than selection sort on average and best cases. Whilst the latter is true, we will try to explain why the former is generally incorrect. To do this, we first need to understand what bubble sort is. As defined in~\cite[p. 40]{clrs}, the code below operating on an array \verb|a| of size \verb|n| qualifies as bubble sort. \begin{verbatim} for (int i = n - 1; i > 0; --i) for (int j = 1; j < i; ++j) if (a[j] < a[j - 1]) swap(a, j, j - 1); \end{verbatim} This naïve version, like selection sort, has the time complexity $\Theta(n^2)$ in term of comparisons (it underperform selection sort in term of swaps however--this will be discussed right after the implementation is completed). Though, as pointed out in~\cite{bubblebad}, \begin{quote} Nearly every description of bubble sort describes how to terminate the sort early if the vector becomes sorted. \end{quote} Hence, we \emph{upgrade} the pseudocode to \begin{verbatim} boolean swapped; do { swapped = false; for (int j = 1; j < n; ++j) if (a[j] < a[j - 1]) { swap(a, j, j - 1); swapped = true; } } while (swapped); \end{verbatim} We can do further optimization avoiding comparing the last $t - 1$ items when running for the $t$-th time of the outer loop, as after iteration $t - 1$, these have already \emph{bubbled up} to its final place~\cite{wikibubble}. With this in mind, we write our final implementation: \begin{verbatim} // ... public class Sort { // ... public static > void bubble(List list) { for (int n = list.size(), m = 0; n > 1; n = m, m = 0) for (int i = 1; i < n; ++i) if (list.get(i).compareTo(list.get(i - 1)) < 0) swap(list, m = i, i - 1); } } \end{verbatim} In the best case where the list is already sorted, during the first iteration of the outer loop, \verb|m| remains zero, which is then assigned to \verb|n| to break the loop, hence the time complexity is that of the inner loop or $n - 1 = \Omega(n)$. Otherwise, denote $S$ as a subset of all positive integers not greater than \verb|list.size()|. The time complexity would then be \[T(n) = \sum_{n\in S}\Theta(n)\] On random data, $\#S = \Theta(n)$ and thus $T(n) = \Theta(n^2)$. As demonstrated in~\cite{bubblebad}, selection sort outperforms bubble sort on average cage, despite their asymptotically equivalent time complexity: \begin{center} \includegraphics[width=0.57\textwidth]{bubbleplot.jpg} \end{center} Looking back at~\cite[p. 646]{wu}, we are now able to spot Wu's misconception: \begin{quote} On average, we expect the bubble sort to finish sorting sooner than the selection sort, \emph{because there will be more data movements for the same number of comparisons}, and there is a test to exit the method when the array gets sorted. \end{quote} While bubble sort may take advantage of the pre-sorted parts of the array to do fewer comparisons, the number of swaps is $\Theta(n^2)$, which grows strictly faster than selection sort's $\Theta(n)$. Consequently, instead of swapping more to finish faster, the bubble variant does more unnecessary data movements. Furthermore, write operations on memory are usually slower than reading, which makes swaps much costlier than comparisons. Finally, it could be summarized by a quote from Donald Knuth~\cite{peculiar}, \begin{quote} In fact, one of Demuth's main results was that in a certain sense `bubble sorting' is the optimum way to sort. [...] It turns out that [all other sorting methods studied] are always better in practice, in spite of the fact that Demuth has proved the optimality of bubble sorting on a certain peculiar type of machine. \end{quote} \subsection{Heapsort} Heapsort could be viewed as an improved version of selection sort, where the selection is done on the right data structure: the heap~\cite{heapselect}. To sort an array ascendingly in place, we use a binary max-heap to keep track of the largest element in the unsorted region and move it to the beginning of the sorted one, iteratively. The binary heap is an array object $A$ which resembles a nearly completely filled binary tree, except possibly the lowest level. This kind of heap has two attributes: $length$ of the array and $size$ of the heap, where $0 \le size \le length$ and valid elements of the heap only reside within the $[0, size)$ index interval of the array~\cite[p. 151]{clrs}. We choose \verb|java.util.List| as the interface for the inner representation of heap and come up with the following declaration: \begin{verbatim} import java.util.List; public class Heap> { private List list; private int size; public int getSize() { return size; } public int getLength() { return list.size(); } public T get(int i) { return list.get(i); } } \end{verbatim} Since the elements are of the type \verb|Comparable|, \verb|Heap| is a max-heap in a purely conceptual sense. That is, the order totally depends on \verb|T.compareTo|, which could be defined or overridden by the users. The max-heap property is then defined as that for every node $i$ other than the root $A_0$, $A_{\mathrm{parent}(i)} \ge A_i$, where the indices of a node's parent, left child and right child are given by \[\begin{array}{ll} \mathrm{parent}(i) &= \left\lfloor\dfrac{i - 1}{2}\right\rfloor\\ \mathrm{left}(i) &= 2i + 1\\ \mathrm{right}(i) &= 2i + 2 \end{array}\] To maintain this property at node $i$, with the assumption that the binary trees rooted at left($i$) and right($i$) are already max-heaps, we use \verb|heapify|, which sift the value at $A_i$ down the heap if it is smaller than its children: \begin{verbatim} // ... import static java.util.Collections.swap; public class Heap> { // ... public void heapify(int i) { int right = i + 1 << 1; int left = right - 1; int largest = i; if (left < size && get(left).compareTo(get(largest)) > 0) largest = left; if (right < size && get(right).compareTo(get(largest)) > 0) largest = right; if (largest != i) { swap(list, i, largest); heapify(largest); } } } \end{verbatim} At each call, the index of the greatest element among $A_i$, $A_{\mathrm{left}(i)}$ and $A_{\mathrm{right}(i)}$ is assigned to \verb|largest|. In case $A_i$ is greatest, the subtree rooted at $A_i$ is already a max-heap and the method terminates. Otherwise, $A_i$ is swapped with $A_{\mathtt{largest}}$, which makes node $i$ and its children to satisfy the max-heap property. The subtree rooted at \verb|largest|, though, \emph{might} violate the property so \verb|heapify| needs to be called on it. For instance, given $i$ points to the subtree on the left, then \verb|heapify| would step-by-step turn it to the one on the right: \begin{center} \begin{forest} for tree={circle,draw} [,phantom [4,fill=yellow [9 [0] [6]] [8 [3] [,phantom]]] [9 [4,fill=yellow [0] [6]] [8 [3] [,phantom]]] [9 [6 [0] [4,fill=yellow]] [8 [3] [,phantom]]]] \end{forest} \end{center} The running time of \verb|heapify| on a subtree of height (number of edges between the root and the furthest leaf) $h$ is the time $\Theta(1)$) to make $A_i$, $A_{\mathrm{left}(i)}$ and $A_{\mathrm{right}(i)}$ a max-heap, plus the time to run \verb|heapify| on one of its children if necessary: \[T(h) \le T(h - 1) + \Theta(1) \le T(0) + \sum_{i=1}^{h}\Theta(1) = T(0) + \Theta(h)\] Since $T(0)$ is obviously $\Theta(1)$, \[T(h) \leq \Theta(h) \Longrightarrow T(h) = O(h)\footnotemark\] A binary tree of size $n$ may have at maximum height of $h = \lfloor\log_2 n\rfloor$, thus the time complexity with respect to the size of the subtree is $O(\log_2 n)$. \footnotetext{The notation $f = O(g)$ indicates that $f$ grows at most as as fast as $g$.} Noticeably, $\forall i \ge \lfloor n/2\rfloor,\; 2i + 2 > 2i + 1 \ge n \iff \mathrm{right}(i) > \mathrm{left}(i) \ge n$. In other words, only nodes of indices less than $\lfloor n/2\rfloor$ have children. Therefore, a heap could be constructed as follows \begin{verbatim} // ... public class Heap> { // ... public Heap(List a) { list = a; size = a.size(); for (int i = size >> 1; i-- > 0;) heapify(i); } } \end{verbatim} Before the construction, each node from $\lfloor n/2\rfloor$ (\verb|size >> 2|) to $n - 1$ is a leaf and a trivial heap on its own. Consider the iteration where \verb|heapify| is called on node $i$ and assume every node after $i$ is the root of a heap beforehand, then after the iteration, all nodes from $i$ to $n - 1$ are foots of max-heaps. Consequently, by mathematical induction, after the construction, the whole array becomes a heap. According to~\cite[p. 159]{clrs}, this constructor runs in linear time ($O(n)$). Heapsort uses the max-heap to select the next largest element to move it to the start of the sorted region. This in-place movement could be implemented as the method \verb|pop|, which also return the largest element to make the method make sense on its own. \begin{verbatim} // ... public class Heap> { // ... public T pop() throws RuntimeException { if (size < 1) throw new RuntimeException("heap underflow"); swap(list, 0, --size); heapify(0); return get(size); } } \end{verbatim} Choosing the $n - 1$ largest element, we are left with the minimum value at the beginning of the list and the rest of the list are all greater or equal to the first element. \begin{verbatim} // ... public class Sort { // ... public static > void heap(List list) { var heap = new Heap(list); for (int i = 1; i < list.size(); ++i) heap.pop(); } } \end{verbatim} The step-by-step selection from the example heap is shown below as an illustration. One may notice that the result qualifies as a min-heap. \begin{center} \begin{forest} for tree={circle,draw} [,phantom [9 [6 [0] [4]] [8 [3] [,phantom]]] [8 [6 [0] [4]] [3 [9,edge=dotted] [,phantom]]] [6 [4 [0] [8,edge=dotted]] [3 [9,edge=dotted] [,phantom]]]] \end{forest} \end{center} \begin{center} \begin{forest} for tree={circle,draw} [,phantom [4 [0 [6,edge=dotted] [8,edge=dotted]] [3 [9,edge=dotted] [,phantom]]] [3 [0 [6,edge=dotted] [8,edge=dotted]] [4,edge=dotted [9,edge=dotted] [,phantom]]] [0 [3,edge=dotted [6,edge=dotted] [8,edge=dotted]] [4,edge=dotted [9,edge=dotted] [,phantom]]]] \end{forest} \end{center} The running time of \verb|Sort.heap| is that of \verb|Heap| constructor, plus $n - 1$ times that of \verb|Heap.pop|, which is \begin{align*} T(n) &= O(n) + (n - 1)(\Theta(1) + O(\log_2 n))\\ &= O(n) + \Theta(n)O(\log_2 n)\\ &= O(n) + O(n\log_2 n)\\ &= O(n\log_2 n) \end{align*} This is a huge improvement from selection sort's and bubble sort's $O(n^2)$. \pagebreak \section{Comparing}\label{sec:cmp} Our implementations of searching and sorting algorithms in Java work on any collection of data whose \emph{natural} order is specified, that is, where elements implement \verb|Comparable|. However, they does not cover the case \emph{another} order is required, there is not any option to use our current library other than subclassing every item with a different \verb|compareTo| method, which, in every sense, is counter-intuitive. In~\cite{wu}, Wu dedicated a whole ``Sample Development'' section to address this issue and came up with the final solution of using the standard interface \verb|java.util.Comparator|~\cite{cmp} which declare the method \verb|compare|. We start with refactoring \verb|Sort.selection| to comply with this facility: \begin{verbatim} // ... import java.util.Comparator; public class Sort { public static void selection(List list, Comparator comparator) { int i, j, m, n = list.size(); for (i = 0; i < n; ++i) { for (m = j = i; j < n; ++j) if (comparator.compare(list.get(j), list.get(m)) < 0) m = j; swap(list, i, m); } } } \end{verbatim} To sort in reversed order, we can subclass \verb|Comparator| anonymously~\cite{anon} \begin{verbatim} var list = java.util.Arrays.asList(4, 2, 0, 6, 9); Sort.selection(list, new java.util.Comparator() { public int compare(Integer a, Integer b) { return -a.compareTo(b); } }); System.out.println(list); \end{verbatim} As expected, the snippet above prints \verb|[9, 6, 4, 2, 0]| to \verb|stdout|. In order to maintain backward-comparability, we write a helper class extracting the natural order from any \verb|Comparable| to a \verb|Comparator|: \begin{verbatim} import java.util.Comparator; public class Compare> implements Comparator { public int compare(T a, T b) { return a.compareTo(b); } } \end{verbatim} and wrap the current \verb|Sort.selection| with \begin{verbatim} // ... public class Sort { // ... public static > void selection(List list) { selection(list, new Compare()); } } \end{verbatim} \verb|Sort.bubble| can be refactored similarly, but since \verb|Sort.heap| does all the comparisons in \verb|Heap|, we are required to make \verb|Heap| work with \verb|Comparator|'s \begin{verbatim} // ... import java.util.Comparator; public class Heap { // ... private Comparator cmp; public void heapify(int i) { int right = i + 1 << 1; int left = right - 1; int largest = i; if (left < size && cmp.compare(get(left), get(largest)) > 0) largest = left; if (right < size && cmp.compare(get(right), get(largest)) > 0) largest = right; if (largest != i) { swap(list, i, largest); heapify(largest); } } public Heap(List a, Comparator c) { list = a; size = a.size(); cmp = c; for (int i = size >> 1; i-- > 0;) heapify(i); } } \end{verbatim} and re-write \verb|Sort.heap| to use the new \verb|Heap| \begin{verbatim} // ... public class Sort { // ... public static void heap(List list, Comparator comparator) { var heap = new Heap(list, comparator); for (int i = 1; i < list.size(); ++i) heap.pop(); } public static > void heap(List list) { heap(list, new Compare()); } } \end{verbatim} For the next example, we use a minimal, read-only version of the \verb|Person| class from~\cite[p. 666]{wu}, which is ordered by \verb|name| by default: \begin{verbatim} public class Person implements Comparable { private String name; private Integer age; private Character gender; public Person(String name, Integer age, Character gender) { this.name = name; this.age = age; this.gender = gender; } public int compareTo(Person other) { return this.name.compareTo(other.name); } public String toString() { return String.format("%s (%d%c)", name, age, gender); } public String getName() { return name; } public Integer getAge() { return age; } public Character getGender() { return gender; } } \end{verbatim} We use a list of the oldest current state leaders~\cite{old} for this test \begin{verbatim} var list = java.util.Arrays.asList( new Person("Mahathir Mohamad", 94, 'M'), new Person("Elizabeth II", 93, 'F'), new Person("Sheikh Sabah Al-Ahmad Al-Jaber Al-Sabah", 90, 'M'), new Person("Paul Biya", 86, 'M'), new Person("Michel Aoun", 84, 'M'), new Person("Mahmoud Abbas", 83, 'M'), new Person("Francis", 82, 'M')); Sort.heap(list); list.forEach(System.out::println); \end{verbatim} and get \begin{verbatim} Elizabeth II (93F) Francis (82M) Mahathir Mohamad (94M) Mahmoud Abbas (83M) Michel Aoun (84M) Paul Biya (86M) Sheikh Sabah Al-Ahmad Al-Jaber Al-Sabah (90M) \end{verbatim} It would neither be challenging to re-sort this list by their ages: \begin{verbatim} var ageComparator = new java.util.Comparator() { public int compare(Person a, Person b) { return a.getAge().compareTo(b.getAge()); } }; Sort.heap(list, ageComparator); list.forEach(System.out::println); \end{verbatim} which gives us \begin{verbatim} Francis (82M) Mahmoud Abbas (83M) Michel Aoun (84M) Paul Biya (86M) Sheikh Sabah Al-Ahmad Al-Jaber Al-Sabah (90M) Elizabeth II (93F) Mahathir Mohamad (94M) \end{verbatim} Linear search is able to run without any modification: we would still receive 4 from \verb|Search.linear(list, list.get(4))|. Binary search, however, would not work correctly if the order of the list is not \emph{natural}. One with \verb|Comparator| support may be implemented as follows. \begin{verbatim} import java.util.List; import java.util.Comparator; public class Search { // ... private static int binary(List list, T key, Comparator c, int low, int high) { if (high < low) return NOT_FOUND; var mid = (low + high) / 2; var cmp = c.compare(list.get(mid), key); if (cmp < 0) return binary(list, key, c, mid + 1, high); if (cmp > 0) return binary(list, key, c, low, mid - 1); return mid; } public static int binary(List list, T key, Comparator c) { return binary(list, key, c, 0, list.size()); } } \end{verbatim} Now \verb|Search.binary(list, list.get(5), ageComparator)| would return the right index 5 instead of -1 like \verb|Search.binary(list, list.get(5))|. \pagebreak \section{Conclusion} Albeit searching and sorting belong to the category of algorithms, by implementing them, we were still able to recognize a variety of object-oriented concepts, namely: \begin{enumerate} \item Through encapsulation of the inner representations and behaviors, we can create intuitive programming interfaces, whilst keeping the code clear and concise. Notable examples are the implementation of binary search and the heap data structure. \item Through polymorphism, generic and convenient libraries can be written, which allow more \emph{code reuse} and thus facilitate more effective development. We wrote all of the classes and methods with this principle in mind. \item Through inheritance, it is possible to extend the functionalities of each object. Together with polymorphism (which defines the behavioral interfaces), it enable us to generalize our codebase one step further, as shown in section~\ref{sec:cmp}, without spending too much energy on refactoring. \end{enumerate} Generally, though, we find shoving every self-contained function into a class as a static method rather redundant. \pagebreak \begin{thebibliography}{99} \bibitem{wu} C. Thomas Wu. ``Chapter 11: Sorting and Searching''. \emph{An Introduction to Object-Oriented Programming with Java}, 5 ed. McGraw-Hill, 2010. ISBN 978–0–07–352330–9. \bibitem{list} \href{https://docs.oracle.com/javase/8/docs/api/java/util/List.html}{List (Java Platform SE 8)}. \emph{Oracle}. \bibitem{var} \href{http://openjdk.java.net/jeps/286}{JEP 286: Local-Variable Type Inference}. \emph{OpenJDK}. \bibitem{bsearch} \href{http://mathworld.wolfram.com/BinarySearch.html}{Binary Search}. \emph{Wolfram WathWorld}. \bibitem{infer} \href{https://docs.oracle.com/javase/tutorial/java/generics/methods.html}{Generic Methods}. \emph{Oracle}. \bibitem{collections} \href{https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html}{Collections (Java Platform SE 8)}. \emph{Oracle}. \bibitem{clrs} Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein. \emph{Introduction to Algorithms}, 3 ed. MIT Press, 2009. ISBN 978-0-262-03384-8. \bibitem{bubblebad} Owen Astrachan. \emph{\href{https://users.cs.duke.edu/~ola/bubble/bubble.html}{Bubble Sort: An Archaeological Algorithmic Analysis}}. Duke University. \bibitem{wikibubble} \href{https://en.wikipedia.org/wiki/Bubble_sort#Optimizing_bubble_sort}{Bubble sort}. \emph{Wikipedia}. Section 2.2: \emph{Optimizing bubble sort}. \bibitem{peculiar} Donald E. Knuth. The Dangers of Computer-Science Theory. \emph{Studies in Logic and the Foundations of Mathematics}, vol. 74. Elsevier, 1973. doi:\href{https://doi.org/10.1016/S0049-237X(09)70357-X}{10.1016/S0049-237X(09)70357-X}. \bibitem{heapselect} Steven Skiena. ``Searching and Sorting''. \emph{The Algorithm Design Manual}, p. 109. Springer, 2008. doi:\href{https://doi.org/10.1007/978-1-84800-070-4_4}{10.1007/978-1-84800-070-4{\_}4}. ISBN 978-1-84800-069-8. \bibitem{cmp} \href{https://docs.oracle.com/javase/8/docs/api/java/util/mparator.html}{Comparator (Java Platform SE 8)}. \emph{Oracle}. \bibitem{anon} \href{https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html}{Anonymous Classes}. \emph{Oracle}. \bibitem{old} \href{https://en.wikipedia.org/wiki/Lists_of_state_leaders_by_age#10_oldest_currently_serving_state_leaders}{Lists of state leaders by age}. \emph{Wikipedia}. Section 1: \emph{10 oldest currently serving state leaders}. \end{thebibliography} \end{document}