use*_*493 5 merge dictionary functional-programming java-8 java-stream
我想合并大量的文本文件,每个文件包含〜1000个字符.在合并期间,我想用它们的对替换几个序列.我对Java8中发布的功能特性并不是很熟悉,因此我的第一个解决方案是将序列映射到其替换为map函数,即
Arrays.asList(String[]).stream().
map( s -> s.replaceAll("_A_", " and ") ).
map( s -> s.replaceAll("_O_", " or ") ).
map( s -> s.replaceAll("_X_", " xor ") ).
reduce( (a,b) -> a + b );
Run Code Online (Sandbox Code Playgroud)
显然,如果想要在运行时添加/删除子语句,则此代码段不易扩展.我想到的一个解决方案是将所有序列存储在地图中replacingMap,然后迭代它以替换所有序列.
final Map<String, String> replacingMap = new HashMap();
replacingMap.put("_A_"," and ");
replacingMap.put("_O_"," or ");
replacingMap.put("_x_"," xor ");
Run Code Online (Sandbox Code Playgroud)
现在,原来的代码可以改写为以下,其中f需要s为一个字符串.基于给定的映射,它替换所有序列并返回替换的字符串.
Arrays.asList(String[]).stream().
map( s -> f(s) ).
reduce( (a,b) -> a + b );
Run Code Online (Sandbox Code Playgroud)
我的实现f是命令式的,其中所有序列都在基本for循环中被替换.
我的问题是如何f在不使用命令式循环的情况下以完整功能的方式编写?
你可能需要的是组成不同的字符串映射功能集成到一个单一的功能,然后可以传递给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 "));
Run Code Online (Sandbox Code Playgroud)
我们希望通过流式传输列表并减少来减少这个任意数量的函数列表,直到一个函数Function.compose().什么compose做的是采取两个函数˚F和摹并创建调用一个新的功能摹,然后调用˚F与具有称为结果摹.这可能看起来像是倒退,但它在数学上是有道理的.如果你有y = f(g(x))则首先应用g.(还有另一个函数Function.andThen以相反的顺序应用函数.)
以下是执行此操作的代码:
Function<String,String> mapper = replList.stream()
.reduce(Function.identity(), Function::compose);
Run Code Online (Sandbox Code Playgroud)
现在func是一个复合函数,它调用所有函数replList.我们现在可以使用它作为map()流管道中单个操作的参数:
System.out.println(
Arrays.stream(input)
.map(mapper)
.collect(Collectors.joining()));
Run Code Online (Sandbox Code Playgroud)
(注意我Function<String,String>在上面使用而不是可论证的等价UnaryOperator<String>.问题是没有compose返回a的方法UnaryOperator,所以我们必须坚持使用Function类型.)
如果您碰巧已经编写了要应用的函数,则此方法有效.如果你想根据从某个地方加载的数据进行替换,那么使用Mapfor这是一个合理的想法.我们怎么做?
您可以浏览地图并从每个键值对生成一个函数,将它们收集到一个列表中,并减少该列表,如上所示.但是没有必要拥有中间列表,因为可以对地图条目流进行缩减.让我们从你的例子开始:
Map<String,String> replMap = new HashMap<>();
replMap.put("_A_", " and ");
replMap.put("_O_", " or ");
replMap.put("_X_", " xor ");
Run Code Online (Sandbox Code Playgroud)
我们想要流式传输地图条目,但我们希望减少到单个函数.这与上面的情况不同,我们有许多相同类型的函数,我们希望将它们简化为相同类型的单个函数.在这种情况下,我们希望输入类型是映射条目,但结果类型是函数.我们怎么做?
我们需要使用三个arg重载reduce,它带有一个标识符,一个累加器和一个组合器.我们的身份功能和以前一样Function.identity().组合器也很简单,因为我们已经知道如何使用组合两个函数Function.compose().
棘手的是累加器功能.在每次调用时,获取输入类型的值并将其应用于中间结果,并返回该应用程序的结果.使这更棘手的是结果类型本身就是一个函数.所以我们的累加器需要一个函数,积累一些东西(在?上),并返回另一个函数.
这是一个执行该操作的lambda表达式:
(func, entry) ->
func.compose(s -> s.replaceAll(entry.getKey(), entry.getValue()))
Run Code Online (Sandbox Code Playgroud)
该类型都将被推断,所以他们没有宣布,但类型funcIS 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);
Run Code Online (Sandbox Code Playgroud)
现在我们可以mapper在输入数据的流中使用结果函数,就像我们上面所做的那样.
我认为这不太可能是一个问题,但关于上述问题的一点是,复合函数捕获每个映射条目,并在每次处理输入元素时从每个条目中获取键和值.如果这困扰你(它困扰我,一点点)你可以编写一个稍大的lambda来提取数据,然后在返回的lambda中捕获它们:
(func, entry) -> {
String key = entry.getKey();
String value = entry.getValue();
return func.compose(s -> s.replaceAll(key, value));
},
Run Code Online (Sandbox Code Playgroud)
我认为这个函数本身更清晰,但使用多行lambda会使流管道混乱.
无论如何,让我们把它们放在一起.鉴于输入:
String[] input = {
"[", "_A_", "_O_", "_X_", "_O_", "_M_", "_O_", "_X_", "_O_", "_A_", "]"
};
Run Code Online (Sandbox Code Playgroud)
和地图中的替换字符串集:
Map<String,String> replMap = new HashMap<>();
replMap.put("_A_", " and ");
replMap.put("_O_", " or ");
replMap.put("_X_", " xor ");
Run Code Online (Sandbox Code Playgroud)
我们生成一个组合映射函数:
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);
Run Code Online (Sandbox Code Playgroud)
然后用它来处理输入:
System.out.println(
Arrays.stream(input)
.map(mapper)
.collect(Collectors.joining()));
Run Code Online (Sandbox Code Playgroud)
最后,结果是:
[ and or xor or _M_ or xor or and ]
Run Code Online (Sandbox Code Playgroud)
更新2015-02-05
基于Marko Topolnik和Holger的一些建议,这里是mapper的简化版本:
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());
Run Code Online (Sandbox Code Playgroud)
这有两个简化.首先,在缩减步骤之前完成从a MapEntry到a 的映射Function,因此我们可以使用更简单的形式reduce.请注意,我必须将一个显式强制转换Function<String,String>放入此映射步骤,因为我无法使用类型推断工作.(这是在JDK 8u25上.)其次,我们可以使用one-arg形式,它返回一个,然后替换,如果结果中不存在该值,而不是使用Function.identity()两个arg reduce操作的标识值..整齐!OptionalFunction.identity()Optional