我偶然发现了用户“ mip”一个有趣的堆栈溢出问题 。 问题是:
我正在寻找一种生成字母序列的方法:
A, B, C, ..., Z, AA, AB, AC, ..., ZZ.
可以很快将其识别为Excel电子表格的标题,正是这样:
到目前为止,没有答案使用任何Java 8函数式编程,我认为这是一个挑战。 我们将使用jOOλ ,因为Java 8 Stream API不能为该任务提供足够的功能 。
但首先,让我们以功能性方式分解算法。 我们需要的是以下组件:
- 字母的(可再现)表示
- 上限,即我们要产生多少个字母。 请求的序列转到
ZZ
,这意味着上限为2 - 一种将笛卡尔积中的每个字母与先前生成的组合字母进行组合的方法
让我们看一些代码:
1.生成字母
我们可以这样写字母:
List<String> alphabet = Arrays.asList("A", "B", ..., "Z");
但这很la脚。 让我们使用jOOλ生成它:
List<String> alphabet = Seq
.rangeClosed('A', 'Z')
.map(Object::toString)
.toList();
上面的代码生成A
和Z
之间的字符的“封闭”范围( 对于上限为包含范围的范围,使用Java-8流表示 ),将字符映射为字符串并将其收集到列表中。
到目前为止,一切都很好。 现在:
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()
本质上与命令式编程中的嵌套循环相同。
3.将字母组合成笛卡尔积
这是最棘手的部分:我们需要将每个字母与每个字母的length
进行组合。 为此,我们将使用以下流:
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()
可以在流中从“左向右”移动,而无需折叠函数具有关联性。 ew。
换句话说,更容易理解的词是: foldLeft()
只是命令性循环。 循环的“种子”,即循环的初始值,是完整的字母( Seq.seq(alphabet)
)。 现在,对于[1 .. length-1]
范围内的每个值,我们产生一个笛卡尔积( crossJoin()
)到到目前为止“折叠”的一个字母和一个新的字母之间,并将每个组合连接成一个新的字符串( t.v1
和t.v2
)。
而已!
结合一切
以下简单程序将A .. Z, AA .. ZZ, AAA .. ZZZ
所有值打印到控制台:
import java.util.List;
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);
}
不必说后者比以前的功能算法快得多。