0

For e.g. when n = 5, I need to generate a string "012345".

I could do it by running a for loop from 0 to n and appending numbers to a string.

for(int i = 0; i <= n; i++) {
    s += i;
}

Is there a simple way of doing it without the for loop? Perhaps using streams?

Dharman
  • 30,962
  • 25
  • 85
  • 135
dreambigcoder
  • 1,859
  • 4
  • 22
  • 32
  • 0 to n numbers or 0 to 9 inclusive digits. (i.e. max of 10 characters)? – WJS Nov 01 '20 at 17:41
  • 2
    `String s="";for(int i=0;i<=n;i++)s+=i;`. I removed the spaces to make it more compact if you want. Don't use streams at all costs, loops are here for a reason you know... – Matthieu Nov 01 '20 at 17:44
  • 1
    I agree streams is an inferior way to go. But you **should** use StringBuilder. – WJS Nov 01 '20 at 17:46
  • @WJS true. Though until Java 8 StringBuilder was used internally when using `+=` on Strings. That changed in more recent versions of Java for a better mechanism which name I forgot... (see also a [related answer](https://stackoverflow.com/questions/1532461/stringbuilder-vs-string-concatenation-in-tostring-in-java) on that topic) – Matthieu Nov 02 '20 at 10:43
  • 2
    @Matthieu it doesn’t matter whether the compiler generated code uses `StringBuilder` or a different technique. What matters, is that your `+=` inside a loop will create a complete `String` in each iteration, leading to the O(n²) time complexity that you should avoid. Regardless of the Java version, the compiler will not transform the loop. Look the [second answer](https://stackoverflow.com/a/1532483/2711488), right below the one you’ve linked. – Holger Nov 03 '20 at 09:14

2 Answers2

5

I think the best way to do this is the way you're already done it in your Question using a loop. Reading the comments, it seems to be the general consensus. Nikolas Charalambidis pointed out in a comment that the solution could do with a StringBuilder instead:

StringBuilder s = new StringBuilder();
for(int i = 0; i <= n; i++) {
  s.append(i);
}

If you must use a Stream, I believe the accepted Answer is the way to go because

  1. I think it just looks "clean" and
  2. It also uses StringBuilder instead of String. There are other approaches though.

One approach is relying on side-effects. Create a variable and update it from within the stream. The JavaDoc on Package java.util.stream warns about the use:

Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement, as well as other thread-safety hazards.

String[] alternativeWayA = {""};
IntStream.rangeClosed(0,n).forEach(i -> alternativeWayA[0] = alternativeWayA[0] + i);

Luiggi Mendoza mentions in one comment

You could use collect(Collectors.joining()) and have the same output

and in another comment

Well, you could use boxed before calling collect

String alternativeWayB = IntStream.rangeClosed(0,n)
                                  .boxed()
                                  .map(i -> i.toString())
                                  .collect(Collectors.joining(""));

..if one prefers using reduce instead of Collectors.joining:

String alternativeWayC = IntStream.rangeClosed(0,n)
                                  .boxed()
                                  .map(i -> i.toString())
                                  .reduce("", String::concat);

..or as Nikolas Charalambidis suggests in this comment

you need to mapToObj as this collector is not available for IntStream

String alternativeWayD = IntStream.rangeClosed(0,n)
                                  .mapToObj(i -> String.valueOf(i))
                                  .collect(Collectors.joining(""));

Holger posted a comment with

String string = new String(IntStream.rangeClosed('1', '1'+n).toArray(), 0, n);

which I think is very elegant and works for n < 10. For n = 15 the result becomes 0123456789:;<=>? since it's using integer values of characters. I added a + 1 to the last parameter of the String constructor:

String alternativeWayE = new String(IntStream.rangeClosed('0', '0'+n).toArray(), 0, n + 1);
Scratte
  • 3,056
  • 6
  • 19
  • 26
  • A nice summary! Finally, a for-loop with `StringBuilder` would be the best for me :) Have +1 for pointing out the *side-effects*. – Nikolas Charalambidis Nov 10 '20 at 23:11
  • @NikolasCharalambidis Yes, of course :) I'm reluctant to add that though, since the Question is looking for a Stream implementation. – Scratte Nov 10 '20 at 23:18
  • 1
    I feel it's no wrong to point that information out. However, with respect to the tags, the [java-stream] answer should be present (if such solution is possible, of course) with alternative ways to make the things better (a for-loop in such simple case). – Nikolas Charalambidis Nov 10 '20 at 23:20
4

Use IntStream and StringBuilder:

int n = 5;
String string = IntStream.rangeClosed(0, n)
     .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
     .toString();

However, for this particular case it's better to use a for-loop with StringBuilder.

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • You could use `collect(Collectors.joining())` and have the same output – Luiggi Mendoza Nov 01 '20 at 17:41
  • Yes, but first you need to `mapToObj` as this collector is not available for `IntStream`. – Nikolas Charalambidis Nov 01 '20 at 17:43
  • There is no need to use streams. It is inferior. If you don't believe me, time it. Change to a loop and I will remove my downvote. – WJS Nov 01 '20 at 17:54
  • 1
    Well, you could use `boxed` before calling `collect` – Luiggi Mendoza Nov 01 '20 at 18:23
  • 2
    @WJS Notice the tag is [tag:java-stream] and the question might be a part of something bigger where Streams are justified. While I agree the for-loop is more suitable for a prticular case, I dont find a reason to persuade the OP that it's the only correct solution. Moreover the OP might just want to learn and explore the possibilies of the Stream AP. I can add a note about the for-loop approach which is definetly more suitable to this particular case, but I still dont u derstand a reason behind the downvote. Anyway, thanks for the comment. – Nikolas Charalambidis Nov 01 '20 at 18:27
  • @NikolasCharalambidis Point taken. Downvote removed. – WJS Nov 01 '20 at 18:38
  • 3
    `String string = new String(IntStream.rangeClosed('1', '1'+n).toArray(), 0, n);` – Holger Nov 03 '20 at 09:02
  • @Holger: Pretty neat! But I got `new String(...)` associated with that the String will not be pooled in the String pool. Out of curiosity, do you have a comment justifying this approach? – Nikolas Charalambidis Nov 03 '20 at 09:18
  • 1
    You should never expect a newly constructed string to be in the pool. That applies to the result of your approach as well (as any result of calling `toString` on a `StringBuilder`). Since putting a string into the pool is an expensive operation, that is not a disadvantage. The only strings that are guaranteed to be pooled, are compile time constants and the result of calling `intern()` (but the latter should be used judiciously, as said, adding to the pool is not cheap). Use whatever is simpler. – Holger Nov 03 '20 at 09:28