3

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
            });

}
shmosel
  • 49,289
  • 6
  • 73
  • 138
rossb83
  • 1,694
  • 4
  • 21
  • 40

2 Answers2

2

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
Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • Suppose the outer collection class doesn't allow index based access and instead implements iterable, is this still possible? – rossb83 Oct 21 '17 at 17:25
  • 1
    Yes but it would be a complete mess, slower, and you'd also lose the ability to deal with the dimensions separately. Instead you'd need `Collector`s that deal with entire rows at a time. – Paul Boddington Oct 21 '17 at 17:32
  • Can you give a quick example of that? – rossb83 Oct 21 '17 at 20:00
  • @rossb83 My comment above is incorrect - you can deal with the dimensions separately. I've edited my answer – Paul Boddington Oct 22 '17 at 08:56
  • Instead of `Iterable vals=Arrays.asList(new int[]{2, 4, 8}, new int[]{1, 5, 7}, new int[]{3, 9, 6});` you can simply write `Iterable vals=Arrays.asList(new int[][] {{2, 4, 8}, {1, 5, 7}, {3, 9, 6}});`… – Holger Oct 23 '17 at 09:42
  • @Holger That's a really good point. Thank you. I'll update the answer. – Paul Boddington Oct 23 '17 at 18:22
1

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)
SEY_91
  • 1,615
  • 15
  • 26
  • edit question, outer class does not have index based selection, instead it implements iterable – rossb83 Oct 22 '17 at 02:57
  • I based my answer on your simple input `[[2,4,8],[1,5,7],[3,9,6]]` just like @Paul Boddington answer, please add the implementation of the `iterator()` method maybe I could help you. – SEY_91 Oct 22 '17 at 08:14