使用一个 List<UnaryOperator<…>> 进行对象映射。

huangapple go评论72阅读模式
英文:

Mapping an object using a List<UnaryOperator<...>>

问题

我有一个 List<UnaryOperator<String>>,需要通过操作符列表映射/转换一个字符串。我有以下功能性的 Java 11 代码:

UnaryOperator<String> addA = (startString) -> startString + "a";
UnaryOperator<String> addB = (startString) -> startString + "b";
UnaryOperator<String> addc = (startString) -> startString + "c";
List<UnaryOperator<String>> operators = List.of(addA, addB, addc);
String concatenatedString =
    operators
        .stream()
        .reduce(
            "", // 起始值
            (value, op) -> op.apply(value), // 累加器
            (value1, value2) -> value1.concat(value2) // 合并器
        );
System.out.println(concatenatedString); // 预期输出 "abc"。

我关心的问题是字符串的连接在两个地方都有体现第一处在每个 `UnaryOperator`第二处在合并器参数中我在想是否有更好的方法来实现这个
英文:

I have a List&lt;UnaryOperator&lt;String&gt;&gt; and need to map/transform a String by passing it through the list of operators. I have the following functional Java 11 code:

UnaryOperator&lt;String&gt; addA = (startString) -&gt; startString + &quot;a&quot;;
UnaryOperator&lt;String&gt; addB = (startString) -&gt; startString + &quot;b&quot;;
UnaryOperator&lt;String&gt; addc = (startString) -&gt; startString + &quot;c&quot;;
List&lt;UnaryOperator&lt;String&gt;&gt; operators = List.of(addA, addB, addc);
String concatenatedString =
    operators
        .stream()
        .reduce(
            &quot;&quot;, // identity
            (value, op) -&gt; op.apply(value), // accumulator
            (value1, value2) -&gt; value1.concat(value2) // combiner
        );
System.out.println(concatenatedString); // prints &quot;abc&quot; as expected.

The concern I have is the string concatenation is expressed in 2 places. First in each of the UnaryOperators and second in the combiner argument. Makes me wonder if there is a better way to do this?

答案1

得分: 3

你可以利用 UnaryOperator&lt;T&gt; 扩展了 Function&lt;T, T&gt; 这一优势,并链式调用多个 Function::andThen 来获得一个合并的 UnaryOperator&lt;String&gt;,其中包含列表中的所有操作:

UnaryOperator&lt;String&gt; mergedUnaryOperators = operators.stream()
            .reduce((l, r) -&gt; (string) -&gt; l.andThen(r).apply(string))
            .orElseGet(UnaryOperator::identity);

String output = mergedUnaryOperators.apply(&quot;&quot;);      // 结果为 &quot;abc&quot;

为了更清楚地了解其工作原理,以下是在 reduce 方法内部调用的部分:

new BinaryOperator&lt;UnaryOperator&lt;String&gt;&gt;() {
  @Override
  public UnaryOperator&lt;String&gt; apply(UnaryOperator&lt;String&gt; l, UnaryOperator&lt;String&gt; r) {
     return string -&gt; l.andThen(r).apply(string);
  }
}
英文:

You can use the advantage that UnaryUperator&lt;T&gt; extends Function&lt;T, T&gt; and chain multiple calls of Function::andThen to get a composed UnaryOperator&lt;String&gt; of all within the list:

UnaryOperator&lt;String&gt; mergedUnaryOperators = operators.stream()
			.reduce((l, r) -&gt; (string) -&gt; l.andThen(r).apply(string))
			.orElseGet(UnaryOperator::identity);

String output = mergedUnaryOperators.apply(&quot;&quot;);      // results in &quot;abc&quot;

To have a clearer picture how does it work, this is called inside the reduce method:

new BinaryOperator&lt;UnaryOperator&lt;String&gt;&gt;() {
  @Override
  public UnaryOperator&lt;String&gt; apply(UnaryOperator&lt;String&gt; l, UnaryOperator&lt;String&gt; r) {
     return string -&gt; l.andThen(r).apply(string);
  }
}

答案2

得分: 1

你可以使用andThen将这三个UnaryOperator组合成一个单一的Function,从而形成一个转换流水线。

Function<String, String> addABC = addA
                .andThen(addB)
                .andThen(addC);
System.out.println(addABC.apply("")); //输出 abc

如果你有许多要开始的UnaryOperator,请参阅尼古拉斯的回答,了解如何将它们减少为一个单一的UnaryOperator

英文:

You can compose the three UnaryOperators into a single Function using andThen to form a pipeline of transformation.

Function&lt;String, String&gt; addABC = addA
            .andThen(addB)
            .andThen(addc);
System.out.println(addABC.apply(&quot;&quot;)); //Prints abc

If you have many UnaryOperators to start with, see Nicholas' answer on how to reduce them to a single UnaryOperator.

答案3

得分: 1

以下是翻译好的内容:

而这个解决方案呢:

Function<String, String> combineF = operators.stream()
    .reduce(UnaryOperator.identity(), (l, r) -> (string) ->  r.compose(l).apply(string));
System.out.println(combineF.apply(""));
英文:

And what about this solution:

Function&lt;String, String&gt; combineF = operators.stream()
    .reduce(UnaryOperator.identity(), (l, r) -&gt; (string) -&gt;  r.compose(l).apply(string));
System.out.println(combineF.apply(&quot;&quot;));

答案4

得分: 0

reduce 方法使用合并器(combiner)来实现并行处理。在并行流中,reduce 可以同时运行许多一元操作符,并使用提供的合并器来合并结果。combiner 的存在是有意设计的,并没有问题。

这段代码的问题不在于你在源代码中两个地方都使用了连接操作,而是在运行时进行了大量的字符串连接操作,从而创建了许多不必要的字符串。

你应该改为使用字符串构建器(StringBuilder)。因为 StringBuilder 是可变的,所以你不会创建很多 StringBuilder 实例(除非使用并行流,但那是另外的故事),而且你将使用 collect 而不是 reduce。还要注意从 UnaryOperator 改为了 Consumer

Consumer<StringBuilder> addA = (startString) -> startString.append("a");
Consumer<StringBuilder> addB = (startString) -> startString.append("b");
Consumer<StringBuilder> addC = (startString) -> startString.append("c");
List<Consumer<StringBuilder>> operators = List.of(addA, addB, addC);
StringBuilder concatenatedString =
    operators
        .stream()
        .collect(
            StringBuilder::new, // 身份元素
            (value, op) -> op.accept(value), // 累加器
            StringBuilder::append // 合并器
        );
System.out.println(concatenatedString);
英文:

The reduce method uses the combiner to achieve parallelism. In a parallel stream, reduce can run many of your unary operators at once, and use the provided combiner to combine the results. The existence of combiner is by-design, and it is not at fault.

The problem with this code is not that you have expressed concatenation in two places in the source code, but rather that you are doing string concatenation a lot of times at runtime, which creates a lot of unnecessary strings.

You should use a string builder instead. Since StringBuilder is mutable, you won't be creating a lot of StringBuilders (unless you use parallel streams, but that's another story), and you will be using collect, rather than reduce. Notice the other change from UnaryOperator to Consumer too:

Consumer&lt;StringBuilder&gt; addA = (startString) -&gt; startString.append(&quot;a&quot;);
Consumer&lt;StringBuilder&gt; addB = (startString) -&gt; startString.append(&quot;b&quot;);
Consumer&lt;StringBuilder&gt; addc = (startString) -&gt; startString.append(&quot;c&quot;);
List&lt;Consumer&lt;StringBuilder&gt;&gt; operators = List.of(addA, addB, addc);
StringBuilder concatenatedString =
    operators
        .stream()
        .collect(
            StringBuilder::new, // identity
            (value, op) -&gt; op.accept(value), // accumulator
            StringBuilder::append // combiner
        );
System.out.println(concatenatedString);

答案5

得分: 0

我可以提供另一种使用二进制运算符连接字符串的方法,但是在您的代码片段中,这个提案不太可能进行优化。

String concatenatedString = operators
                .stream().reduce((op1, op2) -> (val) -> op1.apply(val) + op2.apply(val))
                .get().apply("");
英文:

I can suggest another way to concatenate strings using the binary operator, but this proposal is unlikely to be an optimization in a snippet of your code.

String concatenatedString = operators
                .stream().reduce((op1, op2) -&gt; (val) -&gt; op1.apply(val) + op2.apply(val))
                .get().apply(&quot;&quot;);

huangapple
  • 本文由 发表于 2020年9月26日 21:46:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/64078391.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定