Can I do this with streams?
StringBuilder text = new StringBuilder();
StringBuilder dupText = new StringBuilder();
String lastLetter = "";
for (Container cont : containersList) {
String letter = cont.getLetter();
text.append(letter);
if (letter.equals(lastLetter) == false) {
dupText.append(letter);
}
lastLetter = letter;
}
System.out.println(text);
System.out.println(dupText);
I go over list of continers, each one has a char. I need to assemble two strings - one is all the chars combine, and the other one is all the chars but without coupled duplicates (ABABAAAB -> ABABAB)
Can this be done with streams?
I tried doing it like this:
Optional<String> text = containersList.stream()
.map(Container::getLetter)
.reduce((letter,accumalator) -> accumalator += letter);
Optional<String> dupText = session.containersList().stream()
.map(Container::getLetter)
.reduce((letter, accumalator) ->{
if ((accumalator.endsWith(letter) == false)) {
accumalator += letter;
}
return accumalator;
});
You can do this in a single Stream pipeline using the StreamEx library.
List<Container> containersList = Arrays.asList(new Container("A"), new Container("B"), new Container("A"), new Container("A"), new Container("B"));
String[] result =
StreamEx.of(containersList)
.map(Container::getLetter)
.groupRuns(Object::equals)
.collect(MoreCollectors.pairing(
MoreCollectors.flatMapping(List::stream, Collectors.joining()),
MoreCollectors.mapping(l -> l.get(0), Collectors.joining()),
(s1, s2) -> new String[] { s1, s2 }
));
System.out.println(result[0]);
System.out.println(result[1]);
This code creates a Stream of the containers and maps each of those to their letter.
Then, the method groupRuns
collapses into a List
the successive elements that matches the given predicate. In this case, the predicate is the equality of the String: so if you start with the stream [A, A, B]
, this method will collapse it into the Stream [List(A, A), List(B)]
(the first element is the list of 2 A
successive elements in the input).
Finally, this is collected with the pairing
collector that allows to collect into two different collector. The first one joins the flat map result of each list while the second one joins only the first element of the list (hence removing the successive elements).
The result is stored inside an array which just serves as a holder for two values.
Output:
ABAAB
ABAB
If you want to stay with the current API and not using a library, your best bet would be to write a custom Collector
:
public static void main(String[] args) {
List<Container> containersList = Arrays.asList(new Container("A"), new Container("B"), new Container("A"), new Container("A"), new Container("B"));
String[] result = containersList.stream().parallel().map(Container::getLetter).collect(ContainerCollector.collector());
System.out.println(result[0]);
System.out.println(result[1]);
}
private static final class ContainerCollector {
private StringBuilder text = new StringBuilder();
private StringBuilder dupText = new StringBuilder();
private void accept(String letter) {
text.append(letter);
if (dupText.indexOf(letter, dupText.length() - letter.length()) < 0) {
dupText.append(letter);
}
}
private ContainerCollector combine(ContainerCollector other) {
text.append(other.text);
other.dupText.codePoints().forEach(i -> {
String letter = new String(Character.toChars(i));
if (dupText.indexOf(letter, dupText.length() - letter.length()) < 0) {
dupText.append(letter);
}
});
return this;
}
private String[] finish() {
return new String[] { text.toString(), dupText.toString() };
}
private static Collector<String, ?, String[]> collector() {
return Collector.of(ContainerCollector::new, ContainerCollector::accept, ContainerCollector::combine, ContainerCollector::finish);
}
}
This custom collector builds the text
and dupText
when each letter is accepted. For the text
String, the letter is always appended. For the dupText
, the letter is only appended if the last one is different.
The combiner code (ran in case of parallel execution) is a bit tricky for the dupText
: the second one is appended if it does not start with the end of the first one. Otherwise, the first letter is dropped and the rest is appended.
The output is the same.
I would make it in two separate operations. First, to get the text with duplicates:
String dupText = containersList.stream()
.map(Container::getLetter)
.collect(Collectors.joining());
And the second to remove the duplicates using the regexp:
String text = dupText.replaceAll("(.)\\1+", "$1");
While it's technically two-pass solution, it does not traverse input container twice and, I believe, it should be quite fast, at least not slower than other proposed solutions. And it's simple and does not require third-party libraries.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With