3

This is usually how I accept an Array from a user. I ask for the size, then loop and populate the Array.

Scanner scan = new Scanner(System.in);
int N = scan.nextInt();
int[] numbers = new int[N];
for (int n=0; n<N; n++){
    numbers[n] = scan.nextInt();
}

I have been trying to learn java 8 and I noticed that the Random class has a method now to create a stream. It is pretty easy now to declare a n-sized array with random numbers.

int[] randomNumbers = new Random().ints().limit(N).toArray();

What I have been trying to do is create an array doing something similar with either streams or lambda expressions but for user input. What I tried doing is creating an IntStream, map the values to Scanner#nextInt, then create an array.

int[] numbers = new IntStream().map(x -> scan.nextInt()).limit(N).toArray();

What I can do is something like this:

int[] numbers = new int[N];
Arrays.fill(numbers, 0);
numbers = Arrays.stream(numbers).map(x -> scan.nextInt()).toArray();
System.out.println(Arrays.toString(numbers));

But that still feels a bit redundant. Filling the array with some arbitrary number only to replace it in the next line.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
gonzo
  • 2,103
  • 1
  • 15
  • 27
  • 1
    [Don’t do `.ints().limit(N)`](http://stackoverflow.com/q/33929363/2711488) on a `Random`. Use `ints(N)`; it will be more efficient when calling `toArray()` as it knows the array size in advance then. – Holger Dec 03 '15 at 18:38
  • @Holger I am not using the `Random` class. Was just an example. Thanks though for the heads up. Will keep that in mind when I use it in future projects. – gonzo Dec 03 '15 at 18:39

2 Answers2

3

Use IntStream.generate:

int[] numbers = IntStream.generate(() -> scan.nextInt()).limit(N).toArray();

As mentionned in the comments, this generates an unordered stream. If you want it to be ordered, you can use:

IntStream.range(0, N).map(i -> scan.nextInt()).toArray();
Jean Logeart
  • 52,687
  • 11
  • 83
  • 118
  • Yup that did it. So basically use generate when we do not have values? and map when the stream is already filled? – gonzo Dec 03 '15 at 18:27
  • 1
    Exactly, in your ``map``, you can see you do not use ``x`` so you basically need a ``Supplier`` instead of a ``Function`` – Jean Logeart Dec 03 '15 at 18:29
  • 1
    Ahem, [`IntStream.generate`](http://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#generate-java.util.function.IntSupplier-) creates an *unordered* stream which is unlikely what you want here. While the current implementation won’t change the ordering for this use case as there’s no benefit in that for a sequential stream, this behavior still is not guaranteed… – Holger Dec 03 '15 at 18:34
  • @Holger never specified _ordered_ or _unordered_. This is what I am looking for. – gonzo Dec 03 '15 at 18:36
  • 1
    @gonzo: ok, but in most cases, developers don’t want to have the elements in a different order than read from the `Scanner`, so this still should be mentioned for future readers, even if it’s ok for you. – Holger Dec 03 '15 at 18:40
  • @Holger please add your solution creating an `ordered` stream for future users. – gonzo Dec 03 '15 at 18:42
  • 1
    @gonzo: as said, it’s unordered by definition (read [the API documentation](http://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#generate-java.util.function.IntSupplier-)) but you won’t notice in this specific use case. Still, it’s *not guaranteed*. See [Ordering](http://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#Ordering) – Holger Dec 03 '15 at 18:51
  • 2
    @gonzo I added an ordered version :) – Jean Logeart Dec 03 '15 at 18:52
3

Due to the way, the current implementation handles limit(…)s, it’s more efficient to create a stream using:

IntStream.range(0, N).map(i -> scan.nextInt())

which will be more efficient when using toArray() as well as for parallel processing despite creating an otherwise unused value with the range operation. Further, this creates an ordered stream which maintains the element order.

So when using,

int[] numbers = IntStream.range(0, N).map(i -> scan.nextInt()).toArray();

it benefits from knowing the array size in advance.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • The range does the job of the fill basically from my example. Thanks for the clarification. – gonzo Dec 03 '15 at 18:58
  • Please do come back and post here if you come up with a way without creating the unused values with the range operation. – gonzo Dec 03 '15 at 19:01
  • 2
    Actually, every solution has to use such a value as it has to count somehow to limit the result to the desired number. You can only find solutions hiding it, e.g. the implementation of `limit` must have a counter somewhere. If you like hiding it, you can use `Collections.nCopies(N, scan).stream().mapToInt(Scanner::nextInt)`… – Holger Dec 03 '15 at 19:06
  • Right but there is a difference between setting the size of the array and creating a stream with n amount of sequential numbers. There should be a way to do this with using only one number (N) not N numbers. – gonzo Dec 03 '15 at 19:13
  • I do like the `Collections` solution. Thanks for that as well. – gonzo Dec 03 '15 at 19:17
  • 2
    There is no technical difference as streams do not materialize. `IntStream.range` does not “create” the numbers, it just performs the specified downstream action `N` times (or less in case of short circuiting operations) passing the current value of its counter. `IntStream.generate(…).limit(N)` will do the same but won’t pass the the counter to the action (and report different characteristics). – Holger Dec 03 '15 at 19:23
  • Would there be a way to do this with `Scanner#nextLine` and returning a `String[]` from the stream? – gonzo Dec 10 '15 at 16:48
  • 1
    `String[] array=IntStream.range(0, N) .mapToObj(i -> scan.nextLine()) .toArray(String[]::new);` – Holger Dec 11 '15 at 09:17