0

I need to apply a list of regex to a string, so I thought to use java8 map reduce:

List<SimpleEntry<String, String>> list = new ArrayList<>();

list.add(new SimpleEntry<>("\\s*\\bper\\s+.*$", ""));
list.add(new SimpleEntry<>("\\s*\\bda\\s+.*$", ""));
list.add(new SimpleEntry<>("\\s*\\bcon\\s+.*$", ""));

String s = "Tavolo da cucina";

String reduced = list.stream()
    .reduce(s, (v, entry) -> v.replaceAll(entry.getKey(), entry.getValue()) , (c, d) -> c);

Actually this code may be is not very beautiful, but it works. I know this cannot be parallelised and for me is ok.

Now my question is: is there any chance with Java8 (or higher version) to write something more elegant? I mean also avoiding to add the useless combiner function.

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
freedev
  • 25,946
  • 8
  • 108
  • 125
  • What is this code supposed to do? To remove all the words from the string which at least one character is matched with any character from `bper`, `bda` or `bcon`? – Nikolas Charalambidis Jun 01 '18 at 09:19
  • 1
    @Eugene it does, `replaceAll` returns a new `String` each time. – freedev Jun 01 '18 at 09:23
  • 3
    Sorry, but I isn't simple loop enough? Why involve streams if you don't use their benefits like parallelism, filtering, etc? Why not use something like `for (SimpleEntry<> entry : list){s = s.replaceAll(entry.getKey(), entry.getValue());}`? – Pshemo Jun 01 '18 at 09:24
  • 1
    @Pshemo yes, but it is not fun :) ... well, to be honest I'm just playing (and learning) java8 stream. – freedev Jun 01 '18 at 09:26
  • 1
    `(c, d) -> c` will give invalid results for parallel processing, you are better if throwing an `Exception` in the form of `(c, d) -> throw new AssertionError("Not for parallel") ` – Eugene Jun 01 '18 at 09:36
  • 2
    I found a very interesting [example](https://stackoverflow.com/questions/24011597/java-8-stream-how-to-return-replace-a-strings-contents-with-a-list-of-items-to#answer-24013344), written by Holger. – Oleksandr Pyrohov Jun 01 '18 at 09:39
  • @Eugene thanks, it make sense. – freedev Jun 01 '18 at 09:40
  • 1
    but watch it... read this also https://stackoverflow.com/questions/29210176/can-a-collectors-combiner-function-ever-be-used-on-sequential-streams – Eugene Jun 01 '18 at 09:40
  • @Oleksandr very very interesting, thanks... although I'm still not sure I got Holger answer :)) – freedev Jun 01 '18 at 09:46
  • 1
    @freedev You `map` a pattern to a `Function`, reducing it all to a composed function like `x.andThen(y).apply(inputStr)` applying it to an input string in the end. – Oleksandr Pyrohov Jun 01 '18 at 09:55
  • @Oleksandr thanks, inspired by your comment, I tried to answer to my own questions. – freedev Jun 01 '18 at 10:02
  • As side note, the patterns suggest that you are actually intend to do `replaceFirst`. – Holger Jun 01 '18 at 10:11
  • @Holger looking only at the regexs I wrote in the question you're right, but the idea was to have the chance to run many kind of regexs, an in some case there could be more matching occurrences. – freedev Jun 01 '18 at 12:59

2 Answers2

2

Inspired by Oleksandr's comment and Holger I wrote this

String reduced = list.stream()
.map(entry-> 
    (Function<String, String>) v -> v.replaceAll(entry.getKey(), entry.getValue()))
.reduce(Function.identity(), Function::andThen)
.apply(s);

This also reduce all entries to a function composition.

freedev
  • 25,946
  • 8
  • 108
  • 125
1

Here's another, interesting approach: reduce all entries to a function composition, then apply that composed function on the original input:

String result = list.stream()
        .map(entry -> 
            (Function<String, String>) text -> 
                       text.replaceAll(entry.getKey(), entry.getValue()))
        //following op also be written as .reduce(Function::compose) (see comment by Eugene)
        .reduce((f1, f2) -> f1.andThen(f2)) //compose functions
        .map(func -> func.apply(s)) //this basically runs all `replaceAll`
        .get();

The result of this is your expected string. While this function composition is not intuitive, it nonetheless seems to fit the idea that your original list is in fact a sort of "transformation logic" chain.

ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • 1
    `(f1, f2) -> f1.compose(f2)` can be replaced with `Function::compose`, or even better `Function::andThen` I guess – Eugene Jun 01 '18 at 09:56
  • 3
    The difference between `andThen` and `compose` is the order in which the functions are applied. If you want to do `f` before `g` you have to use `f.andThen(g)` or `g.compose(f)`. – Holger Jun 01 '18 at 10:06
  • @ErnestKiwele another smallish problem is when you have an empty `List`, this can be written as `... .reduce(Function::andThen).orElse(Function.identity()).apply(s);` I did not compile this btw, just looks like it would work – Eugene Jun 01 '18 at 10:07
  • @Holger Right, `compose` will make functions run from last to first, but this all doesn't matter in this case because all `replaceAll` functions will be executed and the result will be the same. But that's a good observation, thanks – ernest_k Jun 01 '18 at 10:14