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

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

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

1、一个(可重复)的字母表。

2、一个上界,例如想生成多少个字母。如要求生成序列ZZ,那上界就是2。

3、一种将字母表中的字母与先前生成的字母联合成一个笛卡尔积(cartesian product)的方法。

让我们看一下代码:

1、生成字母表

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

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

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

1 2 3 4 List alphabet = Seq      .rangeClosed( 'A' , 'Z' )      .map(Object::toString)      .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 2 3 4 // 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、合并字母到一个笛卡尔积中

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

1 2 3 4 5 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 到控制台的程序:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import org.jooq.lambda.Seq; public class Test {      public static void main(String[] args) {          int max = 3 ;          List 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,有一个匿名用户给出了一种***实现方法。

1 2 3 4 5 6 7 8 9 10 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); }

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

本文题目:使用Java 8函数式编程生成字母序列
分享网址:http://www.shufengxianlan.com/qtweb/news26/350326.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联