您可能想要的是将不同的字符串映射函数组合到一个函数中,然后您可以将其传递给map() 操作。最终组合的函数可以在运行时使用程序逻辑、数据结构中的数据等来确定。
在我们深入研究之前,我将在示例中使用一些不相关的技巧:
不要使用reduce((a, b) -> a + b) 连接字符串,因为它具有 O(n^2) 复杂度。请改用collect(Collectors.joining())。
如果您从字符串数组开始,则可以使用Arrays.stream() 将它们流式传输,而无需先将它们包装在List 中。
如果您从文件中读取行,您可以使用BufferedReader.lines() 获取行流,而无需先将它们加载到数据结构中。 (在我的示例中未显示。)
首先让我们从要组合的函数列表开始展示函数组合。
List<Function<String,String>> replList = new ArrayList<>();
replList.add(s -> s.replaceAll("_A_", " and "));
replList.add(s -> s.replaceAll("_O_", " or "));
replList.add(s -> s.replaceAll("_X_", " xor "));
我们想减少这个任意数量的函数列表到一个函数,通过流式传输列表并减少Function.compose()。 compose 所做的就是取两个函数 f 和 g 并创建一个调用 g 的新函数,然后调用 f 的结果是调用了 g。这似乎是倒退的,但在数学上是有道理的。如果您有 y = f(g(x)),则首先应用 g。 (还有另一个函数Function.andThen 以相反的顺序应用这些函数。)
执行此操作的代码如下所示:
Function<String,String> mapper = replList.stream()
.reduce(Function.identity(), Function::compose);
现在func 是一个复合函数,它调用replList 中的所有函数。我们现在可以将其用作流管道中单个 map() 操作的参数:
System.out.println(
Arrays.stream(input)
.map(mapper)
.collect(Collectors.joining()));
(请注意,我在上面使用了Function<String,String>,而不是可以说是等效的UnaryOperator<String>。问题是没有返回UnaryOperator 的compose 方法,所以我们必须坚持使用Function 请输入。)
如果您碰巧已经编写了要应用的功能,则此方法有效。如果您想根据从某处加载的数据进行替换,那么使用Map 是一个合理的想法。我们该怎么做?
您可以遍历映射并从每个键值对生成一个函数,将它们收集到一个列表中,然后如上所示减少该列表。但是没有必要有中间列表,因为可以对映射条目流进行缩减。让我们从你的例子开始:
Map<String,String> replMap = new HashMap<>();
replMap.put("_A_", " and ");
replMap.put("_O_", " or ");
replMap.put("_X_", " xor ");
我们希望流式传输映射条目,但我们希望简化为单个函数。这与上面的情况不同,我们有许多相同类型的函数,我们希望将它们简化为相同类型的单个函数。在这种情况下,我们希望输入类型是映射条目,但结果类型是函数。我们如何做到这一点?
我们需要使用reduce 的三参数重载,它接受一个identity、一个accumulator 和一个combiner。我们的身份函数和以前一样是Function.identity()。组合器也很简单,因为我们已经知道如何使用Function.compose() 组合两个函数。
棘手的是累加器功能。在每次调用时,获取输入类型的值并将其应用于中间结果,并返回该应用程序的结果。更棘手的是结果类型本身就是一个函数。所以我们的累加器需要接受一个函数,将一些东西累加到(到?)它,然后返回另一个函数。
这是一个执行此操作的 lambda 表达式:
(func, entry) ->
func.compose(s -> s.replaceAll(entry.getKey(), entry.getValue()))
所有类型都会被推断出来,所以它们没有被声明,但是func 的类型是Function<String,String>,entry 的类型是Map.Entry<String,String>,考虑到我们正在解决的问题。
这是流中的样子:
Function<String,String> mapper = replMap.entrySet().stream()
.reduce(Function.identity(),
(func, entry) ->
func.compose(s -> s.replaceAll(entry.getKey(), entry.getValue())),
Function::compose);
现在我们可以像上面一样在输入数据的流中使用生成的mapper 函数。
我认为这不太可能成为问题,但关于上述的一点是,复合函数每次处理输入元素时都会捕获每个映射条目并从每个条目中获取键和值。如果这让您感到困扰(它让我有点困扰),您可以编写一个稍大的 lambda,在将数据捕获到返回的 lambda 之前提取数据:
(func, entry) -> {
String key = entry.getKey();
String value = entry.getValue();
return func.compose(s -> s.replaceAll(key, value));
},
我认为这个函数本身更清晰一些,但使用多行 lambda 往往会使上游管道混乱。
无论如何,让我们把它们放在一起。给定输入:
String[] input = {
"[", "_A_", "_O_", "_X_", "_O_", "_M_", "_O_", "_X_", "_O_", "_A_", "]"
};
以及映射中的替换字符串集:
Map<String,String> replMap = new HashMap<>();
replMap.put("_A_", " and ");
replMap.put("_O_", " or ");
replMap.put("_X_", " xor ");
我们生成一个组合映射函数:
Function<String,String> mapper = replMap.entrySet().stream()
.reduce(Function.identity(),
(func, entry) -> {
String key = entry.getKey();
String value = entry.getValue();
return func.compose(s -> s.replaceAll(key, value));
},
Function::compose);
然后用它来处理输入:
System.out.println(
Arrays.stream(input)
.map(mapper)
.collect(Collectors.joining()));
最后,结果是:
[ and or xor or _M_ or xor or and ]
2015-02-05 更新
根据 Marko Topolnik 和 Holger 的一些建议,以下是映射器的简化版本:
Function<String,String> mapper = replMap.entrySet().stream()
.map(entry -> (Function<String,String>) s -> s.replaceAll(entry.getKey(), entry.getValue()))
.reduce(Function::compose)
.orElse(Function.identity());
这有两个简化。首先,从MapEntry 到Function 的映射在归约步骤之前完成,因此我们可以使用reduce 的更简单形式。请注意,我必须在此映射步骤中对Function<String,String> 进行显式强制转换,因为我无法使类型推断起作用。 (这是在 JDK 8u25 上。)其次,我们可以使用 one-arg 形式,而不是使用 Function.identity() 作为双参数 reduce 操作的标识值,它返回一个 Optional,然后替换 @如果结果Optional 中不存在该值,则为987654367@。整洁!