dictionary - 如何在 Java 8 中管道多个 map

标签 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 ") ).
                map( s -> s.replaceAll("_X_", " xor ") ).
                reduce( (a,b) -> a + b );

显然,如果您想添加/删除替换项,尤其是在运行时,则此代码片段不容易扩展。我想到的一个解决方案是将所有序列存储在 map 中,例如 replacingMap ,并迭代它以替换所有序列。
final Map<String, String> replacingMap = new HashMap();
replacingMap.put("_A_"," and ");
replacingMap.put("_O_"," or ");
replacingMap.put("_x_"," xor ");

现在原来的代码可以改写为下面的f需要 s作为字符串。基于给定的映射,它替换所有序列并返回替换后的字符串。
Arrays.asList(String[]).stream().
                map( s -> f(s) ).
                reduce( (a,b) -> a + b );

我对 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 "));
    

    我们想通过流式处理列表并减少 Function.compose() 来将这个任意数量的函数列表减少到一个函数。 .什么 compose do 是取两个函数 f 和 g 并创建一个调用 g 的新函数,然后以调用 g 的结果调用 f。这似乎是倒退,但在数学上是有道理的。如果您有 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> 。问题是没有 compose 方法返回 UnaryOperator ,所以我们必须坚持使用 Function改为键入。)

    如果您碰巧已经编写了要应用的函数,则此方法有效。如果您想根据从某处加载的数据进行替换,请使用 Map因为这是一个合理的想法。我们怎么做?

    您可以遍历 map 并从每个键值对生成一个函数,将它们收集到一个列表中,然后如上所示减少该列表。但是没有必要拥有中间列表,因为可以对映射条目流进行减少。让我们从你的例子开始:
        Map<String,String> replMap = new HashMap<>();
        replMap.put("_A_", " and ");
        replMap.put("_O_", " or ");
        replMap.put("_X_", " xor ");
    

    我们想要流过 map 条目,但我们想要减少到单个函数。这与上面的情况不同,在上面的情况下,我们有许多相同类型的函数,我们希望将它们简化为相同类型的单个函数。在这种情况下,我们希望输入类型是映射条目,但结果类型是函数。我们怎么做?

    我们需要使用 reduce 的三参数重载,它需要一个身份、一个累加器和一个组合器。和以前一样,我们的恒等函数是 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 中的替换字符串集:
        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 上。)第二,而不是使用 Function.identity()作为两个参数的身份值 reduce操作,我们可以使用 one-arg 形式,它返回一个 Optional ,然后替换 Function.identity()如果结果中不存在该值 Optional .整洁的!

    关于dictionary - 如何在 Java 8 中管道多个 map ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28118886/

    相关文章:

    silverlight - Silverlight 4 和 Windows Phone 7 的 Bing map 控件中的交互式图层

    GIT 将文件从 dev 重新 merge 到 master

    javascript - 使用 Ramda.js 的函数的 Pointfree 版本

    function - 函数算术?

    c# - C# 的类似 V8 的哈希表?

    list - 如何删除字典列表项

    Python元组字典加法

    git - 如何让git在 merge 时忽略目录

    r - 如何通过两列合并其中一列

    haskell - 功能性思考。在 Haskell/Purescript 中构建新数组