4
\$\begingroup\$

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]
-
\$\endgroup\$
  • \$\begingroup\$ Just out of curiosity, why do you need this specific behavior? \$\endgroup\$ – Simon Forsberg Dec 18 '17 at 22:21
  • \$\begingroup\$ I am generated some code (chained methods): .collect(Collectors.joining(").chain(", ".chain(", ")")) when there is no elements after filter, result should be "" and not "chain()" \$\endgroup\$ – Jmini Dec 19 '17 at 7:29
  • \$\begingroup\$ But I am wondering why. What is the context, what is the use-case? \$\endgroup\$ – Simon Forsberg Dec 19 '17 at 7:58
4
\$\begingroup\$

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("-");
}
\$\endgroup\$
3
\$\begingroup\$

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 ("[]", "-");
}
\$\endgroup\$
1
\$\begingroup\$

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();
            }
        });
}
\$\endgroup\$
0
\$\begingroup\$

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));
}
\$\endgroup\$
  • \$\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 Nov 19 '18 at 9:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.