使用 Java 8 函數語言程式設計生成字母序列
在 Java 8 中使用函數語言程式設計生成字母序列是一個很大的挑戰。Lukas Eder 愉快地接受了這個挑戰,他將告訴我們如何使用 Java 8 來生成ABC的序列——當然,肯定不是一種蹩腳的方式。
我被 Stack Overflow 上網友“mip”提的一個有趣的問題給難住了。該問題是:
我正在尋找一種生成下列字母序列的方式: A, B, C, ..., Z, AA, AB, AC, ..., ZZ.
大家應該能夠很快認出這是 Excel spreadsheet 的頭部,準確的樣子如下:
到現在為止,沒有一個答案是使用 Java 8 的函數語言程式設計實現的,因此我接受此挑戰。我將使用 jOOλ,因為 Java 8 的 Stream API 提供的功能不足以完成該任務(我承認我錯了——非常感謝 Sebastian 對這個問題的有趣解答)。
首先,我們用函式的方式分解這個演算法。我們所需要的元件有:
1、一個(可重複)的字母表。
2、一個上界,例如想生成多少個字母。如要求生成序列ZZ,那上界就是2。
3、一種將字母表中的字母與先前生成的字母聯合成一個笛卡爾積(cartesian product)的方法。
讓我們看一下程式碼:
1、生成字母表
我們可以這樣寫入字母表,如:
List<String> alphabet = Arrays.asList("A", "B", ..., "Z");
但這很差勁。我們使用 jOOλ 代替:
List<String> alphabet = Seq .rangeClosed('A', 'Z') .map(Object::toString) .toList();
上面的程式碼生成從字元 A 到 Z 的封閉區間(Java-8-Stream-speak 是包含上邊界的),然後將字元對映成字串,最後將其轉換為列表。
目前為止,一切都很好。現在:
2、使用上邊界:
要求的字元序列包括:
A .. Z, AA, AB, .. ZZ
但是我們應該很容易想到擴充套件該需求,能生成如下字元序列,或者更多:
A .. Z, AA, AB, .. ZZ, AAA, AAB, .. ZZZ
因此,我們將再次使用 rangeClosed():
// 1 = A .. Z, 2 = AA .. ZZ, 3 = AAA .. ZZZ Seq.rangeClosed(1, 2) .flatMap(length -> ...) .forEach(System.out::println);
這種方法是為範圍[1..2]中每個長度生成一個單獨的流,然後再將這些流合併到一個流中。flatMap() 的本質與指令式程式設計(imperative programming)中的巢狀迴圈類似。
3、合併字母到一個笛卡爾積中
這是最棘手的部分:我們需要合併字元及出現的次數。因此,我們將使用如下的流:
Seq.rangeClosed(1, length - 1) .foldLeft(Seq.seq(alphabet), (s, i) -> s.crossJoin(Seq.seq(alphabet)) .map(t -> t.v1 + t.v2)) );
我們再次使用 rangeClosed() 來生成範圍 [1 .. length-1] 的值。foldLeft() 與 reduce() 基本一致,區別在於 foldLeft() 保證在流中的順序是從“左至右”的,不需要 fold 函式來關聯。
另一方面,這是一個共容易懂的詞彙:foldLeft() 僅代表一條迴圈的命令。迴圈的“起源”(即迴圈的初始化值)是一個完整的字母表(Seq.seq(alphabet))。現在,在範圍[1..length-1] 中的值生成一個笛卡爾積(crossJoin()),產生一個新的字母表,然後我們將每個合併的字母再組成一個單獨的字串(t.v1 與 t.v2)。
這就是整個過程。
將上面的內容合併到一起
下面是一個簡單的列印 A .. Z, AA .. ZZ, AAA .. ZZZ 到控制檯的程式:
import org.jooq.lambda.Seq; public class Test { public static void main(String[] args) { int max = 3; List<String> alphabet = Seq .rangeClosed('A', 'Z') .map(Object::toString) .toList(); Seq.rangeClosed(1, max) .flatMap(length -> Seq.rangeClosed(1, length - 1) .foldLeft(Seq.seq(alphabet), (s, i) -> s.crossJoin(Seq.seq(alphabet)) .map(t -> t.v1 + t.v2))) .forEach(System.out::println); } }
宣告
對於這個問題,這確實不是最優的演算法。在Stack Overflow,有一個匿名使用者給出了一種最好實現方法。
import static java.lang.Math.*; private static String getString(int n) { char[] buf = new char[(int) floor(log(25 * (n + 1)) / log(26))]; for (int i = buf.length - 1; i >= 0; i--) { n--; buf[i] = (char) ('A' + n % 26); n /= 26; } return new String(buf); }
不用說,這個演算法比之前的函式式演算法會快很多。
相關文章
- Java8的函數語言程式設計Java函數程式設計
- 《Java 8函數語言程式設計》選讀:為什麼要給Java 8中加入函數語言程式設計?Java函數程式設計
- 重識Java8函數語言程式設計Java函數程式設計
- Java8 新特性 —— 函數語言程式設計Java函數程式設計
- Java8函數語言程式設計應用Java函數程式設計
- 書推薦《Java 8函數語言程式設計》Java函數程式設計
- 淺談Java 8的函數語言程式設計Java函數程式設計
- Java 函數語言程式設計Java函數程式設計
- 隨便聊聊 Java 8 的函數語言程式設計Java函數程式設計
- 快速掌握Java8 Stream函數語言程式設計技巧Java函數程式設計
- 幽默圖:升級到Java 8使用函數語言程式設計以後Java函數程式設計
- java8函數語言程式設計筆記-科裡化Java函數程式設計筆記
- java8函數語言程式設計筆記-延遲性Java函數程式設計筆記
- 函數語言程式設計函數程式設計
- Scala 函數語言程式設計(一) 什麼是函數語言程式設計?函數程式設計
- Java 函數語言程式設計的前生今世Java函數程式設計
- Java 函數語言程式設計(三)流(Stream)Java函數程式設計
- Java函數語言程式設計知識分享!Java函數程式設計
- 《Java8函數語言程式設計》讀書筆記---類庫Java函數程式設計筆記
- Java 8用函數語言程式設計實現集合操作案例Java函數程式設計
- 函數語言程式設計,真香函數程式設計
- javascript函數語言程式設計JavaScript函數程式設計
- 初探函數語言程式設計函數程式設計
- 函數語言程式設計初探函數程式設計
- JavaScript 函數語言程式設計JavaScript函數程式設計
- Java函數語言程式設計中歸約reduce()的使用教程Java函數程式設計
- Java技術分享之函數語言程式設計!Java函數程式設計
- Java技術分享之函數語言程式設計Java函數程式設計
- 淺談函數語言程式設計與 Java Stream函數程式設計Java
- Java 函數語言程式設計(二)Lambda表示式Java函數程式設計
- Java 函數語言程式設計(一)初識篇Java函數程式設計
- Java 帝國之函數語言程式設計(上)Java函數程式設計
- 《Java8函數語言程式設計》讀書筆記---收集器Java函數程式設計筆記
- 《Java 8函數語言程式設計》選讀:第一個Lambda表示式Java函數程式設計
- Python函數語言程式設計指南(4):生成器Python函數程式設計
- 《Java 8函數語言程式設計》作者Richard Warbourton:Java的亮點不是語言本身(圖靈訪談)Java函數程式設計圖靈
- 使用函數語言程式設計重構模板模式函數程式設計模式
- 使用 Go 泛型的函數語言程式設計Go泛型函數程式設計