前言
写好了一种排序算法,想要测试一下其性能,代码无怪乎:先记录一下开始时间,然后执行算法,再记录一下结束时间,把结束时间与开始想减,就可以得出算法运行时间,通过这个时间值,可以估算一下算法运行效率。
面对的问题
- 写不同的算法,可能使用不同的类
- 不同的类中的算法代码,比如排序方法名,也可能各不相同
- 所以为了能统计不同算法的运行效率,可能需要对不同类,不同的方法,写大量重复代码,尤其是反复写记录开始、结束时间,以及两者相减值的代码
解决方案
- 可以使用反射机制来写通用代码,但这个有点复杂,暂不讨论
- 使用函数式接口来处理,直接看代码
代码:排序的辅助类
import java.util.function.BiFunction;
import java.util.function.Consumer;
public class SortingHelper {
// 不让在外部生成实例,可以不用此构造器,有也不影响什么
private SortingHelper() {
}
// 这是一个奇葩的方法,是用来检查排序之后的数组,是否每一个元素都按照指定顺序排列好
public static <E extends Comparable<E>> boolean isSorted(E[] arr) {
for (int i = 1; i < arr.length; i++)
if (arr[i - 1].compareTo(arr[i]) > 0)
return false;
return true;
}
// 排序的核心代码,用来交换不同索引值中的数据内容的
public static <E> void swap(E[] arr, int position, int i) {
if (position == i) return;
E temp = arr[i];
arr[i] = arr[position];
arr[position] = temp;
}
/**
* 核心逻辑方法
* @param n:表示生成数组的长度,与生成数据的范围,比如5,数组长度是5,数据值为不超过5
* @param biFunction:是生成数组的函数式接口,通过这个引用,可以生成有序的数组,或乱序的数组
* @param consumer:调用排序算法
* @param errInfo:提示信息,可以告诉当前调用算法的名称等
* @param <E>
*/
public static <E extends Comparable<E>> void showExerciseTime
(int n, BiFunction<Integer, Integer, Integer[]> biFunction, Consumer<Integer[]> consumer, String info) {
Integer[] integers = biFunction.apply(n, n);// 这里进行了简化,只生成Integer类型的数组
long startTime = System.nanoTime();// 记录开始时间
consumer.accept(integers);// 调用排序算法
long endTime = System.nanoTime();// 记录结束时间
double time = (endTime - startTime) / 1_000_000_000.0;// 计算耗时
if (!isSorted(integers)) {// 如果发现没有按照指定顺序排列好数组,就报异常
throw new RuntimeException(info);
}
System.out.printf("%s: Data length: %d, Time: %fs\n", info, n, time);// 显示信息,包括数组长度、耗时,算法名称
}
}
代码:数组生成类
import java.util.Arrays;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.IntStream;
public class ArrayGenerator {
private ArrayGenerator() {
}
public static Integer[] generateRandomArray(int len, int bound) {
return IntStream.generate(() -> Double.valueOf(Math.random() * bound).intValue()).distinct()
.limit(len).boxed().toArray(Integer[]::new);
}
public static Function<Integer, Integer[]> arrayGenerator = len ->
IntStream.iterate(1, v -> v + 1).limit(len).boxed().toArray(Integer[]::new);
public static Function<Integer, Integer[]> arrayGeneratorWithASCOrder = len ->
IntStream.rangeClosed(0, len).boxed().toArray(Integer[]::new);
public static BiFunction<Integer, Integer, Integer[]> arrayGeneratorWithRandomElement = (len, bound) ->
IntStream.generate(() -> Double.valueOf(Math.random() * bound).intValue()).distinct().limit(len).boxed()
.toArray(Integer[]::new);
public static void main(String[] args) {// main方法测试用
System.out.println(Arrays.toString(ArrayGenerator.arrayGenerator.apply(5)));
System.out.println(Arrays.toString(ArrayGenerator.arrayGeneratorWithRandomElement.apply(10, 15)));
System.out.println(Arrays.toString(ArrayGenerator.arrayGeneratorWithASCOrder.apply(10)));
}
}
代码:排序类
// 选择排序类
import com.tyhj.codecamp.algorithm.ArrayGenerator;
import com.tyhj.codecamp.algorithm.SortingHelper;
import java.util.Arrays;
import java.util.stream.IntStream;
public class SelectionSort {
private SelectionSort() {
}
// 排序方法
public static <E extends Comparable<E>> void sort(E[] arr) {
for (int i = 0; i < arr.length; i++) {
int position = i;
for (int j = i; j < arr.length; j++) {
if (arr[position].compareTo(arr[j]) > 0) {
position = j;
}
}
SortingHelper.swap(arr, position, i);
}
}
public static void main(String[] args) {
IntStream.of(10_000, 100_000).forEach(v -> SortingHelper.showExerciseTime(v,
ArrayGenerator::generateRandomArray, SelectionSort::sort, "Selection sort"));
}
}
import com.tyhj.codecamp.algorithm.ArrayGenerator;
import com.tyhj.codecamp.algorithm.SortingHelper;
import java.util.stream.IntStream;
// 插入排序
public class InsertionSort {
private InsertionSort() {
}
public static <E extends Comparable<E>> void sort(E[] arr) {
System.out.println("Insertion Sort:");
for (int i = 0; i < arr.length; i++)
for (int j = i; j - 1 >= 0 && arr[j].compareTo(arr[j - 1]) < 0; j--)
SortingHelper.swap(arr, j, j - 1);
}
public static void main(String[] args) {// 测试类
IntStream.of(10_000, 100_000).forEach(v -> SortingHelper.showExerciseTime(v,
ArrayGenerator::generateRandomArray, InsertionSort::sort, "Insertion sort"));
}
}
小结
- 通过以上代码,本质上与自定义一个接口,然后让所有的算法来实现这个接口,最后在测试代码中使用这个接口中的方法来调用不同的实现,从而实现代码的简化与统一
- 现在这个自定义接口,可以使用Java官方提供的接口来统一实现,这是接口方式的一种统一的趋势。意义在于,可能未来不需要大量定义接口了,使用官方的统一接口,可以让代码可共享度有进一步的提升,是接口书写方法诸侯分治转为大一统的开始(当然这是一种自愿的,不愿意的,自己依然可以编写自己的接口规范,而希望更通用者,可采取这种统一的方式)
- 函数式接口的出现,带来另一个好处是,可以放弃编写大量的方法了,用类属性就可以实现方法级的实现,这在本文中“数组生成类”中就有鲜明的应用
- 流式的代码书写方式,真的是一种非常有趣的体验,把以前必须要使用for、while语句才能实现,而改用Stream流式写法来完成,真是一种妙不可言的体验