3

What's the easiest/fastest way to convert an Iterable<Character> to a String of the characters from the Iterable?

For example, how to convert an Iterable of "A", "B", and "C", to a String "ABC"?

The iter.toString() returns a String "[A, B, C]".

Rok Povsic
  • 4,626
  • 5
  • 37
  • 53
  • Where did the iterable come from? There's likely an easier way, but outside of that the shortest would be putting it into a stream and collecting it. – Rogue Mar 06 '17 at 13:40
  • Rogue: I can change the design and pass String instead of Iterable but it would take some work. – Rok Povsic Mar 06 '17 at 13:47
  • @yper if you can make it `Iterable`, then you could simply use [`Strings.join`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#join-java.lang.CharSequence-java.lang.Iterable-) – kapex Mar 06 '17 at 14:10

4 Answers4

7

5 Versions:

1) Java 8 streams collecting via StringBuilder.append():

public static String streamAppend(Iterable<Character> chars){
    return StreamSupport.stream(chars.spliterator(), true)
                        .collect(
                            StringBuilder::new,
                            StringBuilder::append,
                            StringBuilder::append
                         )
                        .toString();
}

Java 8 Streams collecting via Collectors.joining()

public static String streamJoin(Iterable<Character> chars){
    return StreamSupport.stream(chars.spliterator(), true)
                        .map(Object::toString)
                        .collect(Collectors.joining(""));
}

Pre-Java8-Version using Java 5 for loops:

public static String java7(Iterable<Character> chars) {
    StringBuilder sb = new StringBuilder();
    for (Character c: chars) {
        sb.append(c);
    }
    return sb.toString();
}

Guava version using a Joiner:

public static String guavaJoin(Iterable<Character> chars) {
    return Joiner.on("").join(chars);
}

Java 8 version using Iterable.forEach() and method references:

public static String iterableForEach(Iterable<Character> chars){
    StringBuilder sb = new StringBuilder();
    chars.forEach(sb::append);
    return sb.toString();
}

After the discussions regarding performance, I ran a JMH micro-benchmark to see for myself. The results are obvious, but much more drastic than expected.

Benchmark                           (mode)  (size)          Score    Units
CharsToString.stringJoin     STREAM_APPEND       1    5071451.223 ±  ops/s
CharsToString.stringJoin     STREAM_APPEND       2     481656.870 ±  ops/s
CharsToString.stringJoin     STREAM_APPEND       5     162359.508 ±  ops/s
CharsToString.stringJoin     STREAM_APPEND      10      76910.668 ±  ops/s
CharsToString.stringJoin     STREAM_APPEND      20      49590.249 ±  ops/s
CharsToString.stringJoin     STREAM_APPEND      50      44608.948 ±  ops/s
CharsToString.stringJoin     STREAM_APPEND     100      20940.993 ±  ops/s
CharsToString.stringJoin     STREAM_APPEND     200      29634.118 ±  ops/s
CharsToString.stringJoin     STREAM_APPEND     500      19387.956 ±  ops/s
CharsToString.stringJoin     STREAM_APPEND    1000      17629.508 ±  ops/s
CharsToString.stringJoin       STREAM_JOIN       1    3342341.147 ±  ops/s
CharsToString.stringJoin       STREAM_JOIN       2     279516.584 ±  ops/s
CharsToString.stringJoin       STREAM_JOIN       5     102312.667 ±  ops/s
CharsToString.stringJoin       STREAM_JOIN      10      61759.122 ±  ops/s
CharsToString.stringJoin       STREAM_JOIN      20      34802.386 ±  ops/s
CharsToString.stringJoin       STREAM_JOIN      50      37629.593 ±  ops/s
CharsToString.stringJoin       STREAM_JOIN     100      33493.715 ±  ops/s
CharsToString.stringJoin       STREAM_JOIN     200      26186.986 ±  ops/s
CharsToString.stringJoin       STREAM_JOIN     500      19264.628 ±  ops/s
CharsToString.stringJoin       STREAM_JOIN    1000      14446.396 ±  ops/s
CharsToString.stringJoin        GUAVA_JOIN       1    6570784.907 ±  ops/s
CharsToString.stringJoin        GUAVA_JOIN       2    3821031.465 ±  ops/s
CharsToString.stringJoin        GUAVA_JOIN       5    1574828.190 ±  ops/s
CharsToString.stringJoin        GUAVA_JOIN      10     806057.685 ±  ops/s
CharsToString.stringJoin        GUAVA_JOIN      20     356533.358 ±  ops/s
CharsToString.stringJoin        GUAVA_JOIN      50     156129.534 ±  ops/s
CharsToString.stringJoin        GUAVA_JOIN     100     100195.171 ±  ops/s
CharsToString.stringJoin        GUAVA_JOIN     200      54820.347 ±  ops/s
CharsToString.stringJoin        GUAVA_JOIN     500      20577.137 ±  ops/s
CharsToString.stringJoin        GUAVA_JOIN    1000      11465.704 ±  ops/s
CharsToString.stringJoin  ITERABLE_FOREACH       1   11921819.833 ±  ops/s
CharsToString.stringJoin  ITERABLE_FOREACH       2    7007911.144 ±  ops/s
CharsToString.stringJoin  ITERABLE_FOREACH       5    4415785.561 ±  ops/s
CharsToString.stringJoin  ITERABLE_FOREACH      10    2107685.852 ±  ops/s
CharsToString.stringJoin  ITERABLE_FOREACH      20    1158806.591 ±  ops/s
CharsToString.stringJoin  ITERABLE_FOREACH      50     482412.510 ±  ops/s
CharsToString.stringJoin  ITERABLE_FOREACH     100     265362.511 ±  ops/s
CharsToString.stringJoin  ITERABLE_FOREACH     200     123663.470 ±  ops/s
CharsToString.stringJoin  ITERABLE_FOREACH     500      49238.673 ±  ops/s
CharsToString.stringJoin  ITERABLE_FOREACH    1000      24328.723 ±  ops/s
CharsToString.stringJoin             JAVA7       1    9746936.478 ±  ops/s
CharsToString.stringJoin             JAVA7       2    6431473.785 ±  ops/s
CharsToString.stringJoin             JAVA7       5    2736936.112 ±  ops/s
CharsToString.stringJoin             JAVA7      10    1764353.273 ±  ops/s
CharsToString.stringJoin             JAVA7      20     833322.493 ±  ops/s
CharsToString.stringJoin             JAVA7      50     278354.933 ±  ops/s
CharsToString.stringJoin             JAVA7     100     180763.740 ±  ops/s
CharsToString.stringJoin             JAVA7     200      86729.675 ±  ops/s
CharsToString.stringJoin             JAVA7     500      38560.347 ±  ops/s
CharsToString.stringJoin             JAVA7    1000      17798.159 ±  ops/s

line chart

As you can see, the last version (iterableForEach) is actually the fastest one, with the Java 7 and Guava versions at least in a similar ballpark. For Sizes under several hundred elements, Streams fail miserably, they are obviously optimized for large data sets. But at 1000 elements, they perform significantly better and almost on par with the Java 7 version. At about 10000 elements (not on this chart), Streams outperform the other solutions.

The benchmark code is available as a GitHub gist, feel free to tinker with the parameters and check the results on your machine.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • The pre-Java 8 way is most likely the fastest. – Stephen C Mar 06 '17 at 13:54
  • True, and I'd personally go with the Guava version that abstracts the boilerplate away, while being just as efficient as the plain old imperative version. – Sean Patrick Floyd Mar 06 '17 at 13:58
  • Not convinced by the "just as efficient". My reading of the source code is that there will be redundant calls to `StringBuilder.append("")`. They *might* be optimized by the JIT compiler, but it is not a given. – Stephen C Mar 06 '17 at 14:06
  • True. I'll rephrase that to "almost as efficient", while much more readable and less error-prone. – Sean Patrick Floyd Mar 06 '17 at 14:08
  • 1
    Talking about micro-optimizations ignores the significant cost of depending on any third-party library, none of which are as rigorously tested, documented, or as stable as Java SE. There are much bigger reasons to avoid a third-party library than just tiny differences in efficiency. – VGR Mar 06 '17 at 15:09
  • @VGR "none of which are as rigorously tested, documented, or as stable as Java SE" that may be the case for most libraries, but certainly not for Guava, which is an external version of Google's internal libraries and heavily tested (the Guava test suite is impressive). Quite the contrary: I wish every aspect of the standard library was working as well as Guava. – Sean Patrick Floyd Mar 06 '17 at 15:12
  • 1
    @SeanPatrickFloyd there are some things that are a bit off IMHO. 1) is that `streamAppend` a parallel stream while all others are sequential 2) I don't think `streams are optimized`; as much as `everything is optimized` after a few thousand runs: http://stackoverflow.com/questions/42375003/why-lambda-intstream-anymatch-is-10-slower-than-naive-implementation/42381159#42381159. 3) I have not done the math, but some guys from Oracle showed an up to 30% increase in time... I'll do the measurements myself, as it seems really interesting. – Eugene Mar 14 '17 at 19:38
1

Given x is a Iterable<Character> you can convert in a String with:

public static String iterableToString(Iterable<Character> chars){
   return Stream.of(x).map(String::valueOf).collect(Collectors.joining());
}
freedev
  • 25,946
  • 8
  • 108
  • 125
0

You can use StreamSupport class:

get the Iterable:

Iterable<Character> iterableChars = Arrays.asList('1', '2', '3', '4');

append to string:

String commaSeparatedChars = StreamSupport.stream(iterableChars.spliterator(), false)
            .map(i -> i.toString()).collect(Collectors.joining(", "));

and print it:

System.out.println(commaSeparatedChars );
ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
0

Simple solution using StringBuilder:

Iterable<Character> cs = ...; // wherever you get this from

StringBuilder sb = new StringBuilder();
for (Character c : cs) {
    sb.append(c);
}
String result = sb.toString();

Solution using streams:

String result = cs.stream()
         .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
         .toString();
Jesper
  • 202,709
  • 46
  • 318
  • 350
  • 1
    Iterable doesn't have a stream() method, unfortunately. You have to use StreamSupport.stream(Spliterator, parallel) to stream an Iterable – Sean Patrick Floyd Mar 06 '17 at 13:55
  • Hmm, yes, good catch. I tested it with a `List` which obviously does have a `stream()` method. – Jesper Mar 06 '17 at 14:01