rossb83
rossb83

Reputation: 1762

java streams operate on all values

edit - second class does not have indexed based access, instead it implements iterable

Suppose a class structure like this:

public class Values
{
    public int getValue(int i)
    {
        return values_[i];
    }

    private int[] values_;
}

and a second class like this

public class ValuesCollection implements Iterable
{
    private Values[] valuesCollection_;
}

Is there a way using java8 streams API to operate statistics along each dimension for instance: sum, mean, min, max, range, variance, std, etc. For example [[2,4,8],[1,5,7],[3,9,6]], for getting min it would return [1,4,6]

The closest I can come up with is something like this:

public int[] getMin(ValuesCollection valuesCollection)
{

    IntStream.range(0, valuesCollection.size()).boxed().collect(Collectors.toList())
            .forEach(i -> {

                List<Integer> vals = valuesCollection.stream()
                        .map(values -> values.getValue(i))
                        .collect(Collectors.toList());

                // operate statistics on vals
                // no way to return the statistics
            });

}

Upvotes: 3

Views: 338

Answers (2)

Paul Boddington
Paul Boddington

Reputation: 37655

You can do it. I've used arrays rather than your wrapper classes. Also, I should probably have included some validation that the array is rectangular, and used orElseThrow rather than getAsInt, but you get the idea.

int[][] vals = {{2, 4, 8}, {1, 5, 7}, {3, 9, 6}};

int[] min = IntStream
               .range(0, vals[0].length)
               .map(j -> IntStream.range(0, vals.length).map(i -> vals[i][j]).min().getAsInt())
               .toArray();

System.out.println(Arrays.toString(min));       // Prints [1, 4, 6] as expected

(Since I used arrays, I could have used this line instead

.map(j -> Arrays.stream(vals).mapToInt(arr -> arr[j]).min().getAsInt())

but I wrote it like I did to closely model your situation where your objects are not arrays but do have indexed based access.)

Doing it for standard deviation is obviously harder, but you can do it by combining my answer with this one.

Edit

If your outer class does not have indexed based access, but instead implements Iterable you can do it by converting the Iterable to a Stream.

Iterable<int[]> vals = Arrays.asList(new int[][] {{2, 4, 8}, {1, 5, 7}, {3, 9, 6}});

int[] min = IntStream
               .range(0, vals.iterator().next().length)
               .map(j -> StreamSupport.stream(vals.spliterator(), false).mapToInt(a -> a[j]).min().getAsInt())
               .toArray();

System.out.println(Arrays.toString(min));       // Prints [1, 4, 6] as expected

Upvotes: 2

SEY_91
SEY_91

Reputation: 1697

You can do it by simply flattens the Integer[][] into Integer[] then operate on it with a cycle equal to the length of the array. The following algorithm perform only min,max,sum!!

   public class Test {
    public static void main(String... strings) {
            Integer[][] vals = {{2, 4, 8}, {1, 5, 7}, {3, 9, 6}};

            BinaryOperator<Integer> minFunction = (x, y) -> x < y? x: y;
            BinaryOperator<Integer> maxFunction = (x, y) -> x > y? x: y;
            BinaryOperator<Integer> sumFunction = (x, y) -> x+y;
    Integer[] performStatistic = performStatistic(vals, minFunction); // 1 4 6
    Integer[] performStatistic2 = performStatistic(vals, maxFunction); // 3 9 8
    Integer[] performStatistic3 = performStatistic(vals, sumFunction); // 6 18 21
            }

public static Integer[] performStatistic(Integer[][] vals, BinaryOperator<Integer> f){

            List<Integer> res = new ArrayList<>(vals.length);
            int[] i = {0};
    Arrays.asList(vals).stream().flatMap((Integer[] x)-> Arrays.asList(x).stream())
            .forEach(x -> {
                if(i[0]<vals.length){
                    res.add(i[0], x);
                }else{
                    int cyclicPos = i[0]%vals.length;
                    res.set(cyclicPos, f.apply(res.get(cyclicPos), x));
                }
                i[0]++;
            });
            return res.toArray(new Integer[res.size()]);
        }

    }

To perform the others operations you can follow the same steps and use BiFunction<Integer,Double,Double> to perform the range, variance ... Maybe the following could help you !!

double avg = 100; // the avg you can get it from the previous algorithm
BiFunction<Integer,Double,Double> varianceFunction = (Integer x, Double y) -> {
            return  Math.pow(new Double(x) - avg, 2)+y;

        };// after getting the result just divided it by the (size of the array -1)

Upvotes: 1

Related Questions