Joining Restricted Rows

Using only the standard Java library, what simple mechanism concatenates strings to the limit and adds ellipsis when the limit results in a shorter line?

Efficiency is desirable. Combining all strings and then using String.substring() can lead to excessive memory and time. A mechanism that can be used in the Java 8 thread pipeline is preferred, so that lines outside the bounds can never even be created.

For my purposes, I would be pleased with the limit expressed in:

  • Maximum number of rows to join
  • The maximum number of characters in the result, including any delimiter characters.

For example, this is one way to maximize the number of strings attached in Java 8 with the standard library. Is there a simpler approach?

 final int LIMIT = 8; Set<String> mySet = ...; String s = mySet.stream().limit( LIMIT ).collect( Collectors.joining(", ")); if ( LIMIT < mySet.size()) { s += ", ..."; } 
+6
source share
3 answers

You can write your collector for this. This option is based on another that I wrote for a similar case :

 private static Collector<String, List<String>, String> limitingJoin(String delimiter, int limit, String ellipsis) { return Collector.of( ArrayList::new, (l, e) -> { if (l.size() < limit) l.add(e); else if (l.size() == limit) l.add(ellipsis); }, (l1, l2) -> { l1.addAll(l2.subList(0, Math.min(l2.size(), Math.max(0, limit - l1.size())))); if (l1.size() == limit) l1.add(ellipsis); return l1; }, l -> String.join(delimiter, l) ); } 

In this code, we save the ArrayList<String> all related lines. When an element is accepted, the size of the current list is checked at the limit: strictly less than it, an element is added; equal to it, an ellipsis is added. The same thing is done for the combiner part, which is a bit more complicated, because we need to correctly handle the sizes of the sub lists so as not to cross the limit. Finally, the finisher simply joins this list with the specified separator.

This implementation works for parallel threads. It will store the main elements of the stream in a meeting order . Note that it consumes all the elements in the stream, even if no elements are added after reaching the limit.

Working example:

 List<String> list = Arrays.asList("foo", "bar", "baz"); System.out.println(list.stream().collect(limitingJoin(", ", 2, "..."))); // prints "foo, bar, ..." 
+8
source

Using third-party code for an opponent is not an option; it may be acceptable to other readers. Even when writing a custom collector, you still have a problem: all input will be processed, since standard collectors cannot short-circuit (in particular, it is impossible to process an endless stream). My StreamEx library extends the concept of collectors, allowing you to create collectors with a short circuit. The Joining team is also easy to provide:

 StreamEx.of(mySet).collect( Joining.with(", ").ellipsis("...").maxChars(100).cutAfterDelimiter() ); 

The result is guaranteed to not exceed 100 characters. You can use various counting strategies: you can limit it to symbols, code points, or graphemes (the union of Unicode characters will not be counted). You can also cut the result at any position ("First record, second en ...") either after the word ("First record, second ...") or after the separator ("First record, ...") or before the separator ("First record, second record ..."). It also works for parallel flow, although it is probably not very efficient in an ordered case.

+5
source

Using only the standard Java library

I do not believe that there is anything in this that can do what you ask.

You need to write your own Collector . It will not be so difficult, so I don’t understand why writing my own would be a problem.

+1
source

All Articles