Given a list of integers, I would like to:
- filter odds numbers
- produce a list inside brackets:
[n1, n2, n3]
- if the list is empty it should be a dash
-
My current implementation is:
private static String doIt(List<Integer> list) {
List<String> l = list.stream()
.filter(i -> (i % 2 == 0))
.map(Object::toString)
.collect(Collectors.toList());
if (l.isEmpty()) {
return "-";
}
return l.stream()
.collect(Collectors.joining("; ", "[", "]"));
}
I am wondering if it is possible to do it without the intermediate list result l
.
There is a problem with following implementation:
private static String doIt(List<Integer> list) {
return list.stream()
.filter(i -> (i % 2 == 0))
.map(Object::toString)
.collect(Collectors.joining("; ", "[", "]"));
}
The result in case of empty list is []
(expected is -
)
Test method:
public static void main(String[] args) {
System.out.println(doIt(Arrays.asList(1, 2, 3)));
System.out.println(doIt(Arrays.asList(2, 3, 4)));
System.out.println(doIt(Arrays.asList(1, 3)));
}
Expected Result:
[2]
[2; 4]
-
4 Answers 4
You can use reduce
, it returns an Optional
that is empty if the stream is empty. You can use map
to create the csv (or reduce) and add the brackets and orElse
to return "-".
private static String doIt(List<Integer> list) {
return list.stream()
.filter(i -> (i % 2 == 0))
.map(Object::toString)
.reduce((left, right)-> left+"; "+right)
.map(csv -> "["+csv+"]")
.orElse("-");
}
If you want to still rely on Collectors.joining
(and not implement that logic yourself), you can simply add a .replace ("[]", "-")
to alter the output of the empty case.
private static String doIt(List<Integer> list) {
return list.stream()
.filter(i -> (i % 2 == 0))
.map(Object::toString)
.collect(Collectors.joining("; ", "[", "]"))
.replace ("[]", "-");
}
Just for the sake of showing a different approach, and answering your tweet https://twitter.com/j2r2b/status/943163833975177216. With a custom Collector and StringJoiner. You can convert the Integer to String in the same Collector and even apply some null check there if needed. Tbh, if you need this feature a lot, you can create a class with a constructor that accepts all separator, prefix, suffix and defaultValue and reuse it. Otherwise, for a single use, I'd stick with one of the other options or the enunciate itself.
private static String doIt(List<Integer> list) {
return list.stream()
.filter(i -> (i % 2 == 0))
.collect(new Collector<Integer, StringJoiner, String>() {
@Override
public Supplier<StringJoiner> supplier() {
return () ->
new StringJoiner("; ", "[", "]")
.setEmptyValue("-");
}
@Override
public BiConsumer<StringJoiner, Integer> accumulator() {
return (joiner, val) -> joiner.add(val.toString());
}
@Override
public BinaryOperator<StringJoiner> combiner() {
return (joiner1, joiner2) -> joiner1.merge(joiner2);
}
@Override
public Function<StringJoiner, String> finisher() {
return joiner -> joiner.toString();
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
});
}
Improving StringJoiner solution (that is way-more efficient than String concatenation):
private static String doIt(List<Integer> list) {
return list.stream()
.filter(i -> (i % 2 == 0))
.map(Object::toString)
.collect(Collector.of(
() -> new StringJoiner("; ", "[", "]").setEmptyValue("-"),
StringJoiner::add,
StringJoiner::merge,
StringJoiner::toString));
}
-
\$\begingroup\$ Welcome to Code Review! You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. \$\endgroup\$Toby Speight– Toby Speight2018年11月19日 09:05:47 +00:00Commented Nov 19, 2018 at 9:05
.collect(Collectors.joining(").chain(", ".chain(", ")"))
when there is no elements after filter, result should be""
and not"chain()"
\$\endgroup\$