Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find pre-map element in stream corresponding to post-map minimum

Tags:

java

reduce

I often find myself doing something like this:

list.stream().min(new Comparator<>() {

    @Override
    public int compare(E a, E b) {
        return Double.compare(f(a),f(b));
    }
})

where f is a computation intensive function. This requires twice as many evaluations of f as are actually necessary. I'd prefer to

list.stream().mapToDouble(f).min()

but then I don't know how to get the original element that this minimum corresponds to.

One ugly way around this is

class WithF<E>{
    private final E e;
    private final double fe;
    WithF(E e, double fe){
        this.e = e;
        this.fe = fe;
    }
    public E getE(){
        return e;
    }
    public double getFE(){
        return fe;
    }
}

and then

list.stream().map(e -> new WithF<>(e,f(e))).min(Comparator.comparingDouble(WithF::getFE))

Is there a better, idiomatic way of doing this thing with the stream API?

like image 265
Museful Avatar asked Sep 09 '15 00:09

Museful


2 Answers

This transformation is often called the Schwartzian Transform

I'd actually generalize WithF with a few extra bits, and rename it to make the pipe neater.

class SchwartzianKV<E, SORTKEY implements Comparable<SORTKEY> > 
      implements Comparable<SchwartzianKV<E, SORTKEY>> {

    public final E e;
    public final SORTKEY sortkey;

    SchwartzianKV(E e, SORTKEY sortkey){
        this.e = e;
        this.sortkey = sortkey;
    }

    public static <E, SORTKEY implements Comparable<SORTKEY>> 
    Function<E, SchwartzianKV<E, SORTKEY>> transformer( Function<E, SORTKEY> fn ) {
       return new Function<E, SchwartzianKV<E,SORTKEY>>() {
           @Override SchwartzianKV<E,SORTKEY> apply(E e) {
               return new SchwartzianKV<>(e, fn.apply(e));
           } 
       }
    }

    public int compare(With<E> other) {
       return sortkey.compare(other.sortkey);
    }
}

Now you can write the stream as

 Optional<E> minValue = list.stream()
                            .map( SchwartianKV.transformer( e -> f(e) ) )
                            .min()
                            .map( kv -> kv.e )

Which is pretty concise.

like image 172
Michael Anderson Avatar answered Sep 28 '22 17:09

Michael Anderson


While waiting I'll post what I'm currently considering:

List<Double> fs = list.stream()
    .map(e -> f(e))
    .collect(Collectors.toList())
int i = IntStream.range(0,fs.size()).boxed()
    .min(java.util.Comparator.comparingDouble(fs::get))
    .get();
list.get(i)
like image 24
Museful Avatar answered Sep 28 '22 16:09

Museful