58

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

***** ******
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • BTW - This was a stage in the development of [this](http://stackoverflow.com/a/20270504/823393) answer. I would welcome comments there. – OldCurmudgeon Nov 28 '13 at 16:11

10 Answers10

57
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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Holger
  • 285,553
  • 42
  • 434
  • 765
  • I am much happier about that. I take your point about excessive use of lambdas - my example was just an example. – OldCurmudgeon Nov 28 '13 at 14:45
  • 8
    I know, I just couldn’t resist. A more complex character transformation even could benefit from parallel execution… – Holger Nov 28 '13 at 14:52
  • This is not straightforward at all. These kind of things should be removed from Java and become much more simple – George Pligoropoulos Aug 29 '19 at 13:38
  • 2
    @GeorgiosPligoropoulos this is not the right place to discuss what Java should be or not. – Holger Aug 29 '19 at 15:56
  • 1
    @Holger 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! – George Pligoropoulos Aug 30 '19 at 18:04
  • 3
    @GeorgiosPligoropoulos Stackoverflow still is not the right place for such discussions – Holger Sep 02 '19 at 07:03
27

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.

Naman
  • 27,789
  • 26
  • 218
  • 353
Lukasz Wiktor
  • 19,644
  • 5
  • 69
  • 82
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.

Lii
  • 11,553
  • 8
  • 64
  • 88
  • 2
    My solution does *not* collect strings but `int`s. 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 `Collector`s, it would require boxing. On the other hand, if you don’t mind the boxing overhead, the solution using `Collectors.joining()` would be sufficient. – Holger May 27 '15 at 08:26
  • @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. – Lii May 27 '15 at 09:40
  • 1
    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 `String`s. 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. – Holger May 27 '15 at 10:40
  • @Holger: It's probably a good idea to fix the first sentence of the answer. – Lii May 28 '15 at 07:51
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.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
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.

Community
  • 1
  • 1
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
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()

Ahmad
  • 115
  • 7
0

You can do it directly with the following code :-

"Hello world".codePoints().forEach(n -> System.out.print(n == ' ' ? ' ':'*'));
MikeT
  • 51,415
  • 16
  • 49
  • 68
  • 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. – Maarten Bodewes Nov 21 '16 at 23:12
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);
WJS
  • 36,363
  • 4
  • 24
  • 39
0
  IntStream charStream = "hello".chars();
  charStream.mapToObj(c -> (char)c).forEach(System.out::println);

This works for me.

DanielM
  • 3,598
  • 5
  • 37
  • 53
-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()));
Gayan Weerakutti
  • 11,904
  • 2
  • 71
  • 68