Home > Back-end >  Java-Streams - Transforming an Array in such a way so that each Element depends on value of the prev
Java-Streams - Transforming an Array in such a way so that each Element depends on value of the prev

Time:08-22

Let's say I have an accumulative array like this:

[0,1,3,6,10,15,21,28,36]

And I want to make it into [0,1,2,3,4,5,6,7,8] where each element calculated as:

value = [n] - [n-1]

Is there a way I can achieve this using Java stream?

Below, you can see my code attempts.

Attempt1:

confirmData = confirmedReader.readAll().stream()
    .map(cumulativeArr -> Arrays.stream(cumulativeArr).toArray(String[]::new))
    .toList();

Attempt2:

confirmData = confirmedReader.readAll().stream().skip(1)
    .map(a -> {
        IntStream.range(0,a.length).forEach(x -> {
            a[x] = Integer.toString(Integer.parseInt(a[x]) - Integer.parseInt(a[x - 1]));
        });
    })
    .toList();

CodePudding user response:

Using IntStream able to achieve the given result:

int[] arr = { 0, 1, 3, 6, 10, 15, 21, 28, 36 };
IntStream.range(0, arr.length - 1)
       .map(i -> arr[i   1] - arr[i])
       .forEach(System.out::println);

Output:

1
2
3
4
5
6
7
8

CodePudding user response:

Not sure where you get the data from, but assuming you are able to convert it to an int array, you can use Arrays.setAll method:

int[] original = {0,1,3,6,10,15,21,28,36};
int[] result   = new int[original.length];

Arrays.setAll(result, i -> i > 0 ? original[i] - original[i-1] : original[i] );

System.out.println(Arrays.toString(result));

CodePudding user response:

The naive approach would to use Stream.range() or Stream.iterate() in order to mimic traditional index-based for-loop.

But mind that Stream by its nature is an internal iterator, meaning that the core idea behind Streams is that you're not controlling process of internation, you're not retrieving next element by it's index, not checking whether it exists. With stream you need to think about the transformations and accumulation logic but about the process of iteration.

If you choose to dial with indices, then the solution introduced by @Eritrean which utilizes Arrays.setAll() is the cleanest one.

But if you really want to apply Streams to this problem instead of manipulating indices, the task becomes a little tricky.

I come up with the following solution which makes use of a custom collector created via Collector.of().

Deque is used as a type of the mutable container of the collector (List will do fine as well, Deque was chosen because it facilitates convenient access to the last element). At each step of reduction, the accumulator function polls the last element (if exists) and adds into the deque the difference between the polled element and the next element from the stream, then it also stores the next element itself (follow the comments in the code to understand it better).

int[] source = {0, 1, 3, 6, 10, 15, 21, 28, 36};
    
List<Integer> result = Arrays.stream(source).boxed()
    .collect(Collector.of(                                  // Creating a custom Collector
        ArrayDeque::new,                                    // Mutable Container of the Collector
        (Deque<Integer> deque, Integer next) -> {           // Accumulator - adding stream element into the Container
            if (deque.isEmpty()) deque.addLast(next);
            else deque.addLast(next - deque.pollLast());
            deque.addLast(next);                            // supplementary element which would be used in the else clause of the Accumulator as well as in the Combiner while running parallel and skipped by the Finisher in sequential
        },
        (left, right) -> {                                  // Combiner - merging Container containing intermediate results while executing in parallel (each thread would be given it's own chunk of data and would create its own Deque which will be populated according to the logic represented in the Accumulator)
            left.addLast(right.pollFirst() - left.pollLast()); // supplementary element is being polled from the Left Deque here
            left.addAll(right);
            return left;
        },
        deque -> deque.stream().limit(source.length).toList() // Finisher - transforming Deque into a List
    ));
        
System.out.println(result);

Output:

[0, 1, 2, 3, 4, 5, 6, 7, 8]

CodePudding user response:

With help of Intstream, you can do something like below, considering you have input integer array ready :

int[] original = { 0, 1, 3, 6, 10, 15, 21, 28, 36 };
List<Integer> ls = IntStream.range(0, original.length).collect(ArrayList<Integer>::new, (x, y) -> {
    if (y == 0)
        x.add(original[y]);
    else
        x.add(original[y] - original[y - 1]);
}, List::addAll);  

System.out.println(ls);

Output:

[0, 1, 2, 3, 4, 5, 6, 7, 8]

Please note, this returns list output as I can see based on your question, you are looking for list output.

CodePudding user response:

i used guava package to fine index element by Ints.indexOf(array , target) :

algorithm : value = n - (n-1)

if 0 continue else 1 = 1 - 0 , 2 = 3 - 1 , 3 = 6 - 3 ... etc

    int[] source = {0, 1, 3, 6, 10, 15, 21, 28, 36};
    List<Integer> list = Arrays.stream(source).
                       map(s-> s = s != 0 ? s - source[Ints.indexOf(source, s) - 1] : s).
                         boxed().collect(Collectors.toList());
    
    System.out.println(list);

print :

[0, 1, 2, 3, 4, 5, 6, 7, 8]
  • Related