Dictionary 如何在Java8中管道化多个映射

Dictionary 如何在Java8中管道化多个映射,dictionary,merge,functional-programming,java-8,java-stream,Dictionary,Merge,Functional Programming,Java 8,Java Stream,我想合并大量文本文件,每个文件包含约1000个字符。在合并过程中,我想用序列对替换序列对。我不太熟悉Java8中发布的功能特性,所以我的第一个解决方案是用映射函数将序列映射到它的替换,即 Arrays.asList(String[]).stream(). map( s -> s.replaceAll("_A_", " and ") ). map( s -> s.replaceAll("_O_", " or ") ).

我想合并大量文本文件,每个文件包含约1000个字符。在合并过程中,我想用序列对替换序列对。我不太熟悉Java8中发布的功能特性,所以我的第一个解决方案是用映射函数将序列映射到它的替换,即

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 );
显然,如果要添加/删除子项(尤其是在运行时),则此代码段不容易扩展。我想到的一个解决方案是将所有序列存储在一个映射中,比如
replacingMap
,然后迭代它来替换所有序列

final Map<String, String> replacingMap = new HashMap();
replacingMap.put("_A_"," and ");
replacingMap.put("_O_"," or ");
replacingMap.put("_x_"," xor ");
我对
f
的实现是命令式的,所有的序列都被替换在一个基本的
for
循环中


我的问题是,如何在不使用命令式循环的情况下以全函数式编写
f

您可能希望将不同的字符串映射函数组合成一个函数,然后将其传递给
map()
操作。最终合成的函数可以在运行时使用程序逻辑、数据结构中的数据等确定

在开始之前,我将在我的示例中使用一些不相关的提示:

  • 不要使用
    reduce((a,b)->a+b)
    来连接字符串,因为它的复杂性为O(n^2)。改用
    collector(Collectors.joining())

  • 如果从字符串数组开始,可以使用
    Arrays.stream()
    对它们进行流式处理,而不必首先将它们包装到
    列表中

  • 如果要从文件中读取行,可以使用
    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 "));
现在,
func
是一个复合函数,它调用
replList
中的所有函数。现在,我们可以将其用作流管道中单个
map()
操作的参数:

    System.out.println(
        Arrays.stream(input)
            .map(mapper)
            .collect(Collectors.joining()));
(请注意,我在上面使用的是
函数
,而不是有争议的等价的
UnaryOperator
。问题是没有
compose
方法返回
UnaryOperator
,因此我们必须坚持使用
函数
类型。)

如果您碰巧已经编写了要应用的函数,那么这一点就行了。如果您想根据从某处加载的数据进行替换,那么使用
Map
是一个合理的想法。我们怎么做

您可以运行映射并从每个键值对生成一个函数,将它们收集到一个列表中,然后如上图所示缩小该列表。但是不需要中间列表,因为可以对映射条目流进行缩减。让我们从您的示例开始:

    Map<String,String> replMap = new HashMap<>();
    replMap.put("_A_", " and ");
    replMap.put("_O_", " or ");
    replMap.put("_X_", " xor ");
所有类型都将被推断,因此它们不会被声明,但是
func
的类型是
Function
,而
entry
的类型是
Map.entry
,考虑到我们正在处理的问题,这应该不会太令人惊讶

下面是流中的情况:

    Function<String,String> mapper = replMap.entrySet().stream()
        .reduce(Function.identity(),
                (func, entry) ->
                    func.compose(s -> s.replaceAll(entry.getKey(), entry.getValue())),
                Function::compose);
我认为,这个函数本身更清晰一些,但使用多行lambda会使上游管道变得混乱

无论如何,让我们把一切都放在一起。根据输入:

String[] input = {
    "[", "_A_", "_O_", "_X_", "_O_", "_M_", "_O_", "_X_", "_O_", "_A_", "]"
};
    System.out.println(
        Arrays.stream(input)
            .map(mapper)
            .collect(Collectors.joining()));
以及映射中的替换字符串集:

    Map<String,String> replMap = new HashMap<>();
    replMap.put("_A_", " and ");
    replMap.put("_O_", " or ");
    replMap.put("_X_", " xor ");
最后,结果是:

[ 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());
Function mapper=replMap.entrySet().stream()
.map(entry->(函数)s->s.replaceAll(entry.getKey(),entry.getValue())
.reduce(函数::compose)
.orElse(Function.identity());

这有两个简化。首先,从
映射条目
函数
的映射在缩减步骤之前完成,因此我们可以使用更简单的
缩减
。注意,我必须在这个映射步骤中对
函数
进行显式转换,因为我无法使类型推断工作。(这是在JDK 8u25上的。)其次,我们可以使用one arg表单,它返回一个
可选
,然后替换
函数.identity()
,而不是使用
函数.identity()
作为两个arg
reduce
操作的标识值,如果结果
可选
中不存在该值,则替换
函数.identity()。干净利落

一种方法是使用正则表达式分割字符串,然后使用
Map.getOrDefault
替换所有匹配项。但是,以下示例假定所有匹配条目都基于相同的模式(
\uu
字母前后)

//替换数据
final Map m=新的HashMap();
m、 放入(“_A_”,“and”);
m、 放入(“或”);
m、 put(“X”,“xor”);
//此版本将行保留为数组。它创造了一个内在的
//每行的流以及最后的所有流都映射到一个新字符串[]
最终字符串[]多行=数组。流(行)

.map(行->数组.stream(行.split(“((?+1是一个非常详细的答案;但是我发现有一件事会让读者感到困惑:在第一个示例中,
compose
用作累加器,但没有明确提到这一点;然后在第二个示例中,在引入另一个累加器时,将相同的函数用作组合器。实际上,两个参数
reduce
将累加器重用为组合器,因此两个示例都使用
compose
作为组合器。所有这些都非常混乱,需要更明确的解释。另外,请看以下内容:
replMap.entrySet().stream().reduce(Function.identity()(func,entry)->func.compose(s->s.replaceAll(entry.getKey(),entry.getValue()),Function::compose)我会说什么
    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 ]
    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());
// Replacement data
final Map<String, String> m = new HashMap<>();
m.put("_A_", " and ");
m.put("_O_", " or ");
m.put("_X_", " xor ");

// This version keeps the lines as an array. It creates an inner
// stream for each line and in the end all is mapped to a new String[]
final String[] multipleLines = Arrays.stream(lines)
        .map(line -> Arrays.stream(line.split("((?<=_.?_)|(?=_.?_))"))
                .map(word -> m.getOrDefault(word, word))
                .collect(Collectors.joining()))
        .toArray(String[]::new);

// This version simply joins all strings to one big String.
// The flatMap-method combines a series of Streams to one Stream
final String oneLongString = Arrays.stream(lines)
        .flatMap(line -> Arrays.stream(line.split("((?<=_.?_)|(?=_.?_))")))
        .map(word -> m.getOrDefault(word, word))
        .collect(Collectors.joining());