使用Java8函数式编程生成字母序列

移动开发
Cabsguru 在投资前获得了百万美元的估值,但同一天它的创始人 Pulkit Ahuja 依旧选择接受一份工作 offer。本文就是他这段真实的经历,这段经历也为想当企业家的创始人提供了一个独特的经验。

在 Java 8 中使用函数式编程生成字母序列是一个很大的挑战。Lukas Eder 愉快地接受了这个挑战,他将告诉我们如何使用 Java 8 来生成ABC的序列——当然,肯定不是一种蹩脚的方式。

我被 Stack Overflow 上网友“mip”提的一个有趣的问题给难住了。该问题是:

 
  1. 我正在寻找一种生成下列字母序列的方式: 
  2.  
  3. A, B, C, ..., Z, AA, AB, AC, ..., ZZ. 

大家应该能够很快认出这是 Excel spreadsheet 的头部,准确的样子如下:

使用Java 8函数式编程生成字母序列

到现在为止,没有一个答案是使用 Java 8 的函数式编程实现的,因此我接受此挑战。我将使用 jOOλ,因为 Java 8 的 Stream API 提供的功能不足以完成该任务(我承认我错了——非常感谢 Sebastian 对这个问题的有趣解答)。

首先,我们用函数的方式分解这个算法。我们所需要的组件有:

  1. 一个(可重复)的字母表。
  2. 一个上界,例如想生成多少个字母。如要求生成序列ZZ,那上界就是2。
  3. 一种将字母表中的字母与先前生成的字母联合成一个笛卡尔积(cartesian product)的方法。

让我们看一下代码:

1、生成字母表

我们可以这样写入字母表,如:


  1. List<String> alphabet = Arrays.asList("A""B", ..., "Z"); 

但这很差劲。我们使用 jOOλ 代替:


  1. List<String> alphabet = Seq 
  2.     .rangeClosed('A''Z'
  3.     .map(Object::toString) 
  4.     .toList(); 

上面的代码生成从字符 A 到 Z 的封闭区间(Java-8-Stream-speak 是包含上边界的),然后将字符映射成字符串,***将其转换为列表。

目前为止,一切都很好。现在:

2、使用上边界:

要求的字符序列包括:


  1. A .. Z, AA, AB, .. ZZ 

但是我们应该很容易想到扩展该需求,能生成如下字符序列,或者更多:


  1. A .. Z, AA, AB, .. ZZ, AAA, AAB, .. ZZZ 

因此,我们将再次使用 rangeClosed():


  1. // 1 = A .. Z, 2 = AA .. ZZ, 3 = AAA .. ZZZ 
  2. Seq.rangeClosed(12
  3.    .flatMap(length -> ...) 
  4.    .forEach(System.out::println); 

这种方法是为范围[1..2]中每个长度生成一个单独的流,然后再将这些流合并到一个流中。flatMap() 的本质与命令式编程(imperative programming)中的嵌套循环类似。

3、合并字母到一个笛卡尔积中

这是最棘手的部分:我们需要合并字符及出现的次数。因此,我们将使用如下的流:


  1. Seq.rangeClosed(1, length - 1
  2.    .foldLeft(Seq.seq(alphabet), (s, i) ->  
  3.        s.crossJoin(Seq.seq(alphabet)) 
  4.         .map(t -> t.v1 + t.v2)) 
  5.     ); 

我们再次使用 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 到控制台的程序:


  1. import org.jooq.lambda.Seq; 
  2. public class Test { 
  3.     public static void main(String[] args) { 
  4.         int max = 3
  5.         List<String> alphabet = Seq 
  6.             .rangeClosed('A''Z'
  7.             .map(Object::toString) 
  8.             .toList(); 
  9.         Seq.rangeClosed(1, max) 
  10.            .flatMap(length -> 
  11.                Seq.rangeClosed(1, length - 1
  12.                   .foldLeft(Seq.seq(alphabet), (s, i) ->  
  13.                       s.crossJoin(Seq.seq(alphabet)) 
  14.                        .map(t -> t.v1 + t.v2))) 
  15.            .forEach(System.out::println); 
  16.     } 

声明

对于这个问题,这确实不是***的算法。在Stack Overflow,有一个匿名用户给出了一种***实现方法。


  1. import static java.lang.Math.*; 
  2. private static String getString(int n) { 
  3.     char[] buf = new char[(int) floor(log(25 * (n + 1)) / log(26))]; 
  4.     for (int i = buf.length - 1; i >= 0; i--) { 
  5.         n--; 
  6.         buf[i] = (char) ('A' + n % 26); 
  7.         n /= 26
  8.     } 
  9.     return new String(buf); 

不用说,这个算法比之前的函数式算法会快很多。

责任编辑:倪明 来源: ImportNew
相关推荐

2015-09-28 14:54:08

Java函数式编程

2020-05-25 16:25:17

Java8Stream函数式接口

2022-12-26 07:47:37

JDK8函数式接口

2023-07-26 07:13:55

函数接口Java 8

2022-09-22 08:19:26

WebFlux函数式编程

2020-05-29 07:20:00

Java8异步编程源码解读

2020-09-23 07:50:45

Java函数式编程

2020-09-22 11:00:11

Java技术开发

2020-10-16 10:07:03

Lambda表达式Java8

2023-05-12 07:40:01

Java8API工具

2022-09-26 08:54:39

Spring函数式编程

2024-02-28 08:37:28

Lambda表达式Java函数式接口

2013-09-09 09:41:34

2017-10-31 20:45:07

JavaJava8Optional

2018-11-15 10:20:59

Python函数式编程编程语言

2018-05-30 16:55:47

阿里Java多线程

2011-03-08 15:47:32

函数式编程

2020-09-24 10:57:12

编程函数式前端

2016-10-31 20:46:22

函数式编程Javascript

2011-08-24 09:13:40

编程
点赞
收藏

51CTO技术栈公众号