Simplest way to print an `IntStream` as a `String`
Asked Answered
I

10

60

With Java-8 I can easily treat a String (or any CharSequence) as an IntStream using either the chars or the codePoints method.

IntStream chars = "Hello world.".codePoints();

I can then manipulate the contents of the stream

IntStream stars = chars.map(c -> c == ' ' ? ' ': '*');

I have been hunting for a tidy way to print the results and I cant even find a simple way. How do I put this stream of ints back into a form that can be printed like I can a String.

From the above stars I hope to print

***** ******
Instigate answered 28/11, 2013 at 12:36 Comment(1)
BTW - This was a stage in the development of this answer. I would welcome comments there.Instigate
F
61
String result = "Hello world."
  .codePoints()
//.parallel()  // uncomment this line for large strings
  .map(c -> c == ' ' ? ' ': '*')
  .collect(StringBuilder::new,
           StringBuilder::appendCodePoint,
           StringBuilder::append)
  .toString();

But still, "Hello world.".replaceAll("[^ ]", "*") is simpler. Not everything benefits from lambdas.

Flavin answered 28/11, 2013 at 14:39 Comment(6)
I am much happier about that. I take your point about excessive use of lambdas - my example was just an example.Instigate
I know, I just couldn’t resist. A more complex character transformation even could benefit from parallel execution…Flavin
This is not straightforward at all. These kind of things should be removed from Java and become much more simpleAmazing
@GeorgiosPligoropoulos this is not the right place to discuss what Java should be or not.Flavin
@Flavin I think it is about time to bring all the user experience principles into the programming languages because if you see it from a different perspective users are users of the product and programmers are still users but of programming languages, apis, libraries etc. and all of these should be fit to minimize mental load instead of .... this!Amazing
@GeorgiosPligoropoulos Stackoverflow still is not the right place for such discussionsFlavin
D
28

A bit less efficient but more concise solution to Holger's:

String result = "Hello world."
    .codePoints()
    .mapToObj(c -> c == ' ' ? " ": "*")
    .collect(Collectors.joining());

Collectors.joining() internally uses StringBuilder, at least in OpenJDK sources.

Decastro answered 9/5, 2014 at 12:15 Comment(5)
Nice! Thanks for that - certainly a little tidier.Instigate
Why would this be less efficient?Backgammon
@MaartenBodewes because it converts every char to String before joining them.Decastro
Ah ok. What do you think about my solution here: https://mcmap.net/q/326407/-simplest-way-to-print-an-intstream-as-a-string I don't regularly use streams.Backgammon
customTag = secureRandom.ints(9, 0, chrs.length()).mapToObj(i -> chrs.charAt(i)).collect(Collectors.joining()); yields IDE compile error The method collect(Collector<? super Character,A,R>) in the type Stream<Character> is not applicable for the arguments (Collector<CharSequence,capture#1-of ?,String>)Binding
G
6

Other answers show how to collect a stream of strings into a single string and how to collect characters from an IntStream. This answer shows how to use a custom collector on a stream of characters.

If you want to collect a stream of ints into a string, I think the cleanest and most general solution is to create a static utility method that returns a collector. Then you can use the Stream.collect method as usual.

This utility can be implemented and used like this:

public static void main(String[] args){
    String s = "abcacb".codePoints()
        .filter(ch -> ch != 'b')
        .boxed()
        .collect(charsToString());

    System.out.println("s: " + s); // Prints "s: acac"
}

public static Collector<Integer, ?, String> charsToString() {
    return Collector.of(
        StringBuilder::new,
        StringBuilder::appendCodePoint,
        StringBuilder::append,
        StringBuilder::toString);
}

It's a bit surprising that there isn't something like this in the standard library.

One disadvantage of this approach is that it requires the chars to be boxed since the IntStream interface does not work with collectors.

An unsolved and hard problem is what the utility method should be named. The convention for collector utility methods is to call them toXXX, but toString is already taken.

Grant answered 7/2, 2015 at 13:48 Comment(4)
My solution does not collect strings but ints. In fact, it uses almost exactly the same operations as your solution. The only differences are that it does not require boxing, handles supplementary code points correctly and is more compact. The reason why there is no built-in collector for this purpose is exactly the disadvantage you have named: since IntStream doesn’t support Collectors, it would require boxing. On the other hand, if you don’t mind the boxing overhead, the solution using Collectors.joining() would be sufficient.Flavin
@Holger: When working Collectors.joining you have to wrap the chars into strings, which is slightly more expensive and does not express the intent as clearly. When working with int streams you can not use collectors. This gives some justification to the collector-on-boxed-chars approach. And it might also be a good idea to demonstrate a technique which is generally useful.Grant
It’s good to show alternative approaches, still your first sentence “Other answers show how to collect a stream of strings …” is wrong. For the scope of the question, there is no difference between boxing to Integer or mapping to Strings. The latter maps to one of two constant strings as in Lukasz Wiktor’s solution and your code will use instance from the mandatory Integer cache as you’re only using ASCII characters. For arbitrary characters, mapping to strings might be slightly more expensive than boxing to integer instances but if you care about performance you will avoid both.Flavin
@Holger: It's probably a good idea to fix the first sentence of the answer.Grant
B
1

There is a simple answer that is slightly less inclined to doing everything with streaming. Hence it is not a one-liner, but it is probably more efficient and very easy to read:

public static String stars(String t) {
    StringBuilder sb = new StringBuilder(t.length());
    t.codePoints().map(c -> c == ' ' ? ' ' : '*').forEach(sb::appendCodePoint);
    return sb.toString();
}

Sometimes short is not the same as concise, I don't think anybody has to wonder how the above function operates.

This solution makes sure that the code points are never converted back to characters. It is therefore somewhat more generic than some other solutions listed here.

Backgammon answered 21/11, 2016 at 23:21 Comment(2)
This solution is okay. However, it has one disadvantage - it's not ready for a parallel stream.Decastro
Would forEachOrdered fix this?Backgammon
B
1

If we must, we can make a one-liner this extremely ugly way:

public static String stars(String t) {
    return t.codePoints().map(c -> c == ' ' ? ' ': '*').mapToObj(i -> new String(new int[] { i }, 0, 1)).collect(Collectors.joining());
}

It performs the exact same version as my other answer but it uses streaming all the way. Some function to convert a single code point to a string is obviously needed:

public static String stars(String t) {
    return t.codePoints().map(c -> c == ' ' ? ' ': '*').mapToObj(Stars::codePointToString).collect(Collectors.joining());
}

private static String codePointToString(int codePoint) {
    return new String(new int[] { codePoint }, 0, 1);
}

which places these functions in the class Stars of course.

Backgammon answered 21/11, 2016 at 23:36 Comment(1)
Instead of i -> new String(new int[] { i }, 0, 1) you can also use i -> new StringBuilder().appendCodePoint(i), as the collector accepts any CharSequence. It’s slightly longer but more readable imho. Unrelated to that, the “codePointToString” operation has been added in Java 11 at last.Flavin
O
1

The way I do it is by using reduce. To get * for each character and space for each space it will look like this

    String hello = "hello world";
    String result = hello.chars()
        .map(val -> (val == ' ') ?  ' ' : '*')
        .mapToObj(val -> String.valueOf((char) val))
        .reduce("",(s1, s2) -> s1+s2);

The point is do whatever you want with the integers than cast it to characters then map it to String then concatenate it, you can use reduce or Collectors.joining()

Oregon answered 20/8, 2019 at 23:11 Comment(0)
M
0

You can do it directly with the following code :-

"Hello world".codePoints().forEach(n -> System.out.print(n == ' ' ? ' ':'*'));
Malayan answered 18/4, 2016 at 9:15 Comment(1)
True but I think the whole idea was to convert it to a string first, and then print it (converting to string is the underlying, most interesting problem). Then again, you could use a StringWriter instance I suppose.Backgammon
M
0

How about this.

  System.out.println(stars.mapToObj(c -> String.format("%c", c)).collect(
            Collectors.joining()));

  or

  String s = stars.mapToObj(c -> String.format("%c", c)).reduce("",
            (a, b) -> a + b);
Monaghan answered 20/8, 2019 at 23:20 Comment(0)
C
0
  IntStream charStream = "hello".chars();
  charStream.mapToObj(c -> (char)c).forEach(System.out::println);

This works for me.

Carleycarli answered 1/1, 2020 at 22:48 Comment(0)
K
-1

You can do:

chars.mapToObj(c -> c == ' ' ?  " ": "*").collect(joining());

Another example:

The following examples return the original string. But these can be combined with other intermediate operations such as filter().

chars.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.joining()));

"abc".chars().mapToObj(i -> "" + (char) i)).collect(Collectors.joining()));
Kliment answered 25/9, 2018 at 16:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.