2019 ICPC 南京 J. Spy (離散化+字首和+組合數學+二分圖最大權匹配【KM BFS實現】)
2020/12/08
目錄Lambda表示式
函數語言程式設計
- 函式當成基本運算單元
- 函式可以當引數
- 函式可以接收引數
- 函式可以返回引數
Lambda表示式
- 簡化語法
- JDK >= 1.8
- 只使用於函式式介面
函式式介面
-
介面只有一個抽象方法
-
不包括object那些public方法
interface並沒有繼承Object類,它只是隱式的聲明瞭Object類裡的所有的public方法。並且在介面的實現中可以不用override這些方法,因為呼叫這些隱式方法的時候預設會直接呼叫到Object類裡的方法。Object類裡的protected方法(finalize)不會被隱式宣告。
所以下面2個介面都是函式式介面:
將匿名類簡化成Lambda表示式
String[] words = "Improving code with lambda expressions in java".split(" "); Arrays.sort(words, new Comparator<String>() { @Override public int compare(String str1, String str2) { // 全部小寫後再比較 return str1.toLowerCase().compareTo(str2.toLowerCase()); } });
簡化:
Arrays.sort(words, (s1, s2) -> {
return s1.toLowerCase().compareTo(s2.toLowerCase());
});
再簡化:
Arrays.sort(words, (s1, s2) -> s1.toLowerCase().compareTo(s2.toLowerCase()));
System.out.println(Arrays.toString(words));
s1,s2的型別編譯器會自動判斷
方法引用
非構造方法的引用
定義一個Student類用於測試
public class Student { private String name; private int score; public Student() { } public Student(String name, int score) { this.name = name; this.score = score; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } public static int compareStudentByScore(Student student1, Student student2) { return student1.getScore() - student2.getScore(); } public static int compareStudentByName(Student student1, Student student2) { return student1.getName().compareToIgnoreCase(student2.getName()); }
使用之前提到的Lambda表示式進行排序
Student student1 = new Student("zhangsan", 60);
Student student2 = new Student("lisi", 70);
Student student3 = new Student("wangwu", 80);
Student student4 = new Student("zhaoliu", 90);
List<Student> students = Arrays.asList(student1, student2, student3, student4);
students.sort((o1, o2) -> o1.getScore() - o2.getScore());
students.forEach(student -> System.out.println(student.getScore()));
使用類名::靜態方法
進行排序
先理解方法簽名的概念
如果兩個方法引數型別和返回值型別相同,那麼這兩個方法簽名相同
例如上圖的ignoreCase
和compare
方法簽名相同
所以之前傳入的Lambda表示式,或者說是函式式介面,可以使用與該介面中那個抽象方法簽名相同的方法替換,程式會自動將傳入的方法轉換成對應的函式式介面
public static int compareStudentByScore(Student student1, Student student2) {
return student1.getScore() - student2.getScore();
}
students.sort(Student::compareStudentByScore);
使用例項::例項方法
進行排序
由於簽名相同的判斷條件不包括static
,因此也可以使用類名::例項方法
來替換原來的函式式介面(前提也是簽名相同)
建立一個Student的排序器
public class StudentComparator {
public int compareStudentByScore(Student student1, Student student2) {
return student2.getScore() - student1.getScore();
}
}
將方法傳入
StudentComparator comparator = new StudentComparator();
students.sort(comparator::compareStudentByScore);
使用類名::例項方法
進行排序
先看一下之前的靜態方法
public static int compareStudentByScore(Student student1, Student student2) {
return student1.getScore() - student2.getScore();
}
雖然這個方法在語法上沒有任何問題,可以作為一個工具正常使用,但是有沒有覺得其在設計上是不合適的或者是錯誤的。這樣的方法定義放在任何一個類中都可以正常使用,而不只是從屬於Student這個類,那如果要定義一個只能從屬於Student類的比較方法下面這個例項方法更合適一些
public int compareByScore(Student student) {
return this.getScore() - student.getScore();
}
students.sort(Student::compareByScore);
那麼該方法的簽名和Comparator介面中的compare方法不同,為何可以替換?
因為類中每個方法都會隱式傳入this引數,所以實際呼叫的是
public int compareByScore(Student this, Student student) {
return this.getScore() - student.getScore();
}
這樣簽名就相同了
構造方法的引用
使用下方介面進行測試
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
使用Lambda表示式
Supplier<Student> supplier = () -> new Student();
Student student = supplier.get();
是用構造方法的引用(前提要有無參構造)
Supplier<Student> supplier = Student::new;
Student student = supplier.get();
stream
- 可以“儲存”有限個或無限個元素
- 一個stream可以轉換成另一個stream
- 引數都是Lambda表示式
- 不會儲存元素
- 惰性計算:在需要獲取結果時才進行計算
建立stream
// 1. 通過Collections系列集合提供的stream() 或 parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream01 = list.stream();
// 2. 通過Arrays中的靜態方法stream()獲取陣列流
String[] strList = new String[10];
Stream<String> stream02 = Arrays.stream(strList);
// 3. 通過Stream類中的靜態方法
Stream<String> stream03 = Stream.of(strList);
// 4. 迭代
Stream<Integer> stream04 = Stream.iterate(0, (x) -> (x + 1));
stream04.limit(10).forEach(System.out::println);
// 5. 生成
Stream<Double> stream05 = Stream.generate(() -> Math.random());
stream05.limit(10).forEach(System.out::println);
map
-
將一個stream對映成另一個stream
-
map的引數是Function介面
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Function介面
@FunctionalInterface public interface Function<T, R> { // 將T型別轉換為R型別 R apply(T t); }
示例一
String[] words = "Stream API supports functional-style operations".split(" ");
Stream<String> stream = Arrays.stream(words);
stream.map(String::toUpperCase).forEach(System.out::println);
示例二(將String轉為Student)
String[] students = {"小明, 1", "張三, 2", "李四, 3", "王五, 4", "小劉, 5", "小王, 6"};
Stream<String> stream = Arrays.stream(students);
Stream<Student> studentStream = stream.map(s -> {
int index = s.indexOf(',');
String name = s.substring(0, index);
int id = Integer.parseInt(s.substring(index + 2));
return new Student(id, name);
});
studentStream.forEach(System.out::println);
filter
-
用來過濾stream
-
傳入引數為Predicate介面
Stream<T> filter(Predicate<? super T> predicate);
Predicate介面
@FunctionalInterface public interface Predicate<T> { // 傳入引數T,判斷是否滿足條件 boolean test(T t); }
示例一
class NaturalSupplier implements Supplier<Long> {
private long x;
@Override
public Long get() {
x = x + 1;
return x;
}
}
Stream<Long> natural = Stream.generate(new NaturalSupplier());
// 奇數
Stream<Long> odd = natural.filter(n -> n % 2 == 1);
odd.limit(15).forEach(System.out::println);
示例二
String[] words = {"Java", " Python ", null, "\n\n", " Ruby "};
// Java中的trim()函式去除了字串前後兩端的所有包括空格、換行、回車
Arrays.stream(words).
filter(s -> s != null && !s.trim().isEmpty()).
map(String::trim).
forEach(System.out::println);
reduce
-
用來聚合stream
-
傳入引數為BinaryOperator介面
Optional<T> reduce(BinaryOperator<T> accumulator);
BinaryOperator介面
@FunctionalInterface public interface BinaryOperator<T> extends BiFunction<T,T,T> { // 兩個輸入,一個輸出,並且型別相同 T apply(T t, T u); }
示例一
Optional<Integer> reduce = Stream.of(1, 2, 6, 8, 9).reduce((acc, n) -> acc + n);
System.out.println(reduce.get());
示例二
Integer reduce = Stream.of(1, 2, 6, 8, 9).reduce(1000, (acc, n) -> acc * n);
System.out.println(reduce);
示例三
String[] students = {"小明, 1", "張三, 2", "李四, 3", "王五, 4", "小劉, 5", "小王, 6"};
Stream<String> stream = Arrays.stream(students);
Optional<String> reduce = stream.reduce((acc, s) -> acc + "~" + s);
System.out.println(reduce.get());
其它方法
排序
Stream<T> sorted() //按元素預設大小排序(必須實現comparable介面)
Stream<T> sorted(Comparator<? super T> cp) //按指定comparator比較的結果排序
去重
stream<T> distinct() // 返回去除重複元素的新Stream
// [1, 2, 5, 2, 6, 5, 7] => [ 1, 2, 5, 6, 7]
擷取
Stream<T> limit(long) // 擷取當前Stream的最多前N個元素
stream<T> skip(long) // 跳過當前stream的前N個元素
合併
合併stream
stream<Integer> s = Stream.concat (
stream.of(1, 2, 3),
stream.of (4, 5, 6)
};
// 1, 2, 3, 4, 5, 6
合併集合成一個stream
Stream<List<Integer>> s = Stream.of(
Arrays. asList (1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList (7, 8, 9));
// 轉換為stream<Integer>
stream<Integer> i = s.flatMap(list -> list.stream()) ;
flatMap把元素對映成stream,然後合併成一個stream
stream並行處理
stream的操作是單執行緒的,可以手動設定成可多執行緒處理
stream<String> s = ...
string[] result = s.parallel() // 變成一個可以並行處理的stream
.sorted() // 可以進行並行排序
.toArray(String[]::new);
聚合方法
Optional<T> reduce(BinaryOperator<T> bo)
long count() // 元素個數
T max(Comparator<? super T> cp) // 找最大元素
T min(Comparator<? super T> cp) // 找最小元素
// 針對IntStream / LongStream / DoubleStream
sum() // 求和
average() // 求平均數
sum和average只適用於3個基本資料型別
任意與存在
boolean allMatch(Predicate<? super T>) // 所有元素均滿足測試條件?
boolean anyMatch(Predicate<? super T>) // 至少有一個元素滿足測試條件?
迴圈處理每個元素
可以傳入side-effects
(副作用)
void forEach(Consumer<? super T> action)
// 示例
Stream<String> s = ...
s.forEach(str -> {
System.out.println("Hello, " + str);
});
把stream轉換為其它型別
object[] toArray() // 轉換為object陣列
A[] toArray(IntFunction<A[]>) // 轉換為A[]陣列
<R, A> R collect(Collector<? super T,A,R> collector) // 轉換為List/Set等集合型別
示例
Stream<String> s = ...
String[] arr = s.toArray(string[]::new); // 轉換為string陣列
List<String> list = s.collect(Collectors.toList()); // 轉換為List