For a Java project I'm working on, I need to make a sort-by method, which sorts a list using a mapping function. The most obvious solution is to use the built-in Collections.sort() method:
static <D, R extends Comparable> void sortBy(List<D> list, Function<D, R> function) {
Collections.sort(list, new Comparator<D>() {
#Override
public int compare(D d1, D d2) {
return function.apply(d1).compareTo(function.apply(d2));
}
});
}
The problem is that this calls the function on each element many times (I think about 2 log N). Furthermore, the function is likely to be slow, with each invocation taking at least a few milliseconds, possibly much longer. I'd like a more efficient algorithm which calls the function as few times as possible.
I've considered applying each function at the beginning, and then sorting the mapped list, but I don't see how to get back to the original list:
static <D, R extends Comparable> void sortBy(List<D> list, Function<D, R> function) {
List<R> newList = new ArrayList<>();
for (D d : list){
newList.add(function.apply(d));
}
Collections.sort(newList);
// now what?
}
(Note that the function is pure, i.e. each input yields the same output, with no side effects.)
As proposed in comments, you can sort a list of some kind of tuple that will hold the original value and the computed one. Then you build a new list by extracting the original values in sorted order.
This solution creates temporary objects (the tuples), but should be efficient if the mapping function is expensive. Of course, this needs to be measured...
static <D, R extends Comparable> List<D> sortBy(List<D> list, Function<D, R> function) {
// Build the list of pairs
List<Pair<D,R>> newList = list.stream()
.map(d -> new Pair<>(d, function.apply(d)))
.collect(Collectors.toList());
// Sort the list of pairs on second member, which is the computed one
Collections.sort(newList, new Comparator<Pair<D,R>>() {
#Override
public int compare(Pair<D, R> p1, Pair<D, R> p2) {
return p1.second.compareTo(p2.second);
}
});
// extract the first member of pair, which is the original value
return newList.stream().map(p -> p.first).collect(Collectors.toList());
}
Given a simple class Pair<U, V> like:
public final class Pair<U,V> {
public final U first;
public final V second;
public Pair(U u, V v) {
this.first = u;
this.second = v;
}
public String toString() {
return "["+first+","+second+"]";
}
}
Then:
List<String> data = Arrays.asList("blah", "foo", "bar", "hello world", "bye bye", "fizz", "buzz");
List<String> sortedDataByLength = sortBy(data, new Function<String, Integer>() {
#Override
public Integer apply(String t) {
return t.length();
}});
System.out.println(sortedDataByLength);
Yields:
[foo, bar, blah, fizz, buzz, bye bye, hello world]
Instead of R simply containing the result, implement it so that it is a composite object that contains a reference to its corresponding D as well. Then you can sort by R and extract D out of each R element in the sorted list.
You can implement simple memoization using the new Java 8 Map#computeIfAbsent(...) method:
static <D, R extends Comparable<? super R>> void sortBy(List<D> list, Function<D, R> function) {
Map<D, R> memo = new HashMap<>();
Collections.sort(list, new Comparator<D>() {
#Override
public int compare(D d1, D d2) {
R r1 = memo.computeIfAbsent(d1, function);
R r2 = memo.computeIfAbsent(d2, function);
return r1.compareTo(r2);
}
});
}
Related
I need help figuring out how to pass this BiFunction
BiFunction<Integer, List<Integer>, Integer> func = (a, b) -> {
int result = 0;
int temp = 0;
for(Integer ele : b) {
temp = b.get(ele);
result += temp;
}
return a + result;
};
I am using this junit test
void testFoldLeft() {
LinkedList<Integer> l = new LinkedList<>();
for(int i = 0; i < 10; i++) l.addFirst(i+1);
Integer u = fp.foldLeft(0, l, func(0,l));
}
I am attempting to pass the BiFunction func through foldLeft, which looks like this
static <U,V> V foldLeft(V e, Iterable<U>l, BiFunction<V,U,V> f){
return null;
}
What func is supposed to do is to take a list, in this case b, sum up all the elements in b then add that number to a and return the result. However, Eclipse gives me an error stating that func is undefined. I'm new to BiFunctions so I'm kind of stuck here. I would appreciate any help.
There are two issues in your code:
func(0,l) is a syntax error, just pass func variable as the BiFunction. No need for providing arguments:
Integer u = foldLeft(0, l, func);
The generics signature of foldLeft does not match the BiFunction. Given the current way it is written, Iterable<U> l makes the compiler infer U as Integer, hence the compiler expects the third argument to be a BiFunction<Integer, Integer, Integer> instead of BiFunction<Integer, List<Integer>, Integer>. To make U match the LinkedList<Integer>, declare it as follows:
static <U extends Iterable<V>, V> V foldLeft(V e, U l, BiFunction<V, U, V> f){
return null;
}
Side note: I wonder what the function code is doing:
for(Integer ele : b) {
temp = b.get(ele);
result += temp;
}
This is looping over elements of b and treating them as indices. I doubt this is what you want.
I think the mismatch is caused by an incorrect declaration of the second parameter of foldLeft. If, in your example, Integer is the Iterable's argument, then your method should be declared like this:
static <U, V> V foldLeft(V e, Iterable<V> l, BiFunction<V, U, V> f) {
return null;
}
V is the element type of the iterable, not U (which, in this case, would be equivalent to Iterable<V>).
Further, your invocation should look like this:
Integer u = foldLeft(0, l, func);
You can't invoke an expression in Java. Unless func is declared as a method in visible scope, func() will always be invalid.
Note that you can simplify your generics by getting rid of U (I don't see a specific need for the type variable for the list type here, but I may be missing some use cases):
static <V> V foldLeft(V e, Iterable<V> l, BiFunction<V, Iterable<V>, V> f) {
return null;
}
You do not show your implementation of foldLeft, but I'm sure it's going to need to iterate over the list, or at least pass it to f.apply. So I suppose you can make the function take a List<V>:
static <V> V foldLeft(V e, List<V> l, BiFunction<V, List<V>, V> f) {
return f.apply(e, l);
}
Otherwise, calling f.apply(e, l) would fail as Function<Integer, LinkedList<Integer>, Integer> and Function<Integer, Iterable<Integer>, Integer> are incompatible.
How can I write this method?
public static <T> void adds(List<T> k,int i)
{
T y;
List<T> g = new ArrayList<T>();
for(i=0;i<5;i++) {
y+= k.get(i));}
}
}
What should I use for the sum?
I have tried declaring a type T as a variable to place the sum within it.
You can use a generic method that takes a BinaryOperator<T> of the type to be summed and calls it to add in a reduce operation:
public static <T> T sum(List<T> list, BinaryOperator<T> adder) {
return list.stream().reduce(adder).get();
}
And you can use it like this:
List<String> s = Arrays.asList("1", "2", "3");
sum(s, (s1, s2) -> String.valueOf(Double.parseDouble(s1) + Double.parseDouble(s2))); //"6.0"
sum(Arrays.asList(1, 2, 3, 4), (a, b) -> a + b); //10
This allows the method to be free of type-specific "addition" logic (so String list can be concatenated, numbers added, etc.
Let's say I've got some lambda expressions as below:
Function<String, List<String>> flines = fileName -> {
//Puts all lines from file into List and returns a list
};
Function<List<String>, String> join = listOfLines -> {
//Concatenates all lines from list and returns them as String
};
Function<String, List<Integer>> collectInts = str -> {
//Returns all Integer occurences from given String
};
And I want to create a function, let's say resultType convertBy(Function<T,S>...args)
So I can combine arguments and return cerain result:
List<String> lines = fileConv.convertBy(flines) //returns list of lines
String text = fileConv.convertBy(flines, join); //returns s String from concatenated Strings
List<Integer> ints = fileConv.convertBy(flines, join, collectInts); //returns list of Integers from string
Integer sumints = fileConv.convertBy(flines, join, collectInts, sum); ////returns list of Integers from string and sums it
Is it somehow possible to do in Java?
EDIT: I CAN'T use overloading
When using generics, you need to declare the type variables involved. Since defining a method which chains calls using a variable number of generic functions (with varargs) would require a variable number of type variables, that's not possible to do.
It would not be possible, at compile time, to guarantee that each of functions given with varargs would use types so that they are compatible when chaining the calls.
You can do it, but not in a type-safe way. Any mismatch on the input/output types of the functions will result in a ClassCastException at runtime.
private static <T, U> U convertBy(T arg, Function... functions) {
Object result = arg;
for (Function f : functions) {
result = f.apply(result);
}
return (U) result;
}
#Test
public void test() {
Function<String, Integer> f1 = s -> s.length();
Function<Integer, Double> f2 = i -> i*2.0;
Double d = convertBy("test", f1, f2);
assertThat(d).isEqualTo(8.0);
}
You can, however, manually define variants of that method that does the chaining by overloading it:
private static <T, U> U convertBy(T arg, Function<T, U> func1) {
return func1.apply(arg);
}
private static <T, U, V> V convertBy(T arg, Function<T, U> func1, Function<U, V> func2) {
return func2.apply(func1.apply(arg));
}
private static <T, U, V, X> X convertBy(T arg, Function<T, U> func1, Function<U, V> func2, Function<V, X> func3) {
return func3.apply(func2.apply(func1.apply(arg)));
}
I'm trying Java 8, I want to iterate over 2 collections and call a parameter function for each pair of values.
In abstract, I want to apply a foo(tuple, i) function for each iteration
[ v1, v2, v3, v4, v5, v6 ] (first collection)
[ w1, w2, w3, w4, w5, w6 ] (second collection)
---------------------------
foo(<v1,w1>, 0)
foo(<v2,w2>, 1)
...
foo(<v6,w6>, 5)
Now what I got so far (java and pseudo code)
// Type of f?
private <S,U> void iterateSimultaneously(Collection<S> c1, Collection<U> c2, Function f) {
int i = 0
Iterator<S> it1 = c1.iterator()
Iterator<U> it2 = c2.iterator()
while(it1.hasNext() && it2.hasNext()) {
Tuple<S, U> tuple = new Tuple<>(it1.next(), it2.next())
// call somehow f(tuple, i)
i++
}
}
// ........................
// pseudo code, is this posible in Java?
iterateSimultaneously(c1, c2, (e1, e2, i) -> {
// play with those items and the i value
})
Is something like this what you're looking for?
private <S,U> void iterateSimultaneously(Collection<S> c1, Collection<U> c2, BiConsumer<Tuple<S, U>, Integer> f) {
int i = 0
Iterator<S> it1 = c1.iterator()
Iterator<U> it2 = c2.iterator()
while(it1.hasNext() && it2.hasNext()) {
Tuple<S, U> tuple = new Tuple<>(it1.next(), it2.next())
f.accept(tuple, i);
i++
}
}
iterateSimultaneously(c1, c2, (t, i) -> {
//stuff
})
What type is the function f supposed to return? If nothing, change it to a consumer instead. If you want it to accept a tuple you most clarify it like I have done here. Is this what you're looking for?
You are probably looking for a BiConsumer:
private <S,U> void iterateSimultaneously(Collection<S> c1, Collection<U> c2,
BiConsumer<Tuple<S, U>, Integer> f) {
f.accept(tuple, i);
}
and call it with:
iterateSimultaneously(c1, c2, (tuple, i) -> doSomethingWith(tuple, i));
The signature of doSomethingWith would look like:
private <S, U> void doSomethingWith(Tuple<S, U> tuple, int i) {
}
You can find an detailed implementation using Stream API of Java 8 of what you are looking for just here (the method zip()) :
https://github.com/JosePaumard/streams-utils/blob/master/src/main/java/org/paumard/streams/StreamsUtils.java#L398
Take a look at Guava's utilities for streams, particularly Streams.zip and Streams.mapWithIndex. You might use them both to achieve what you want:
Collection<Double> numbers = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
Collection<String> letters = Arrays.asList("a", "b", "c", "d", "e");
Stream<Tuple<Double, String>> zipped = Streams.zip(
numbers.stream(),
letters.stream(),
Tuple::new);
Stream<String> withIndex = Streams.mapWithIndex(
zipped,
(tuple, index) -> index + ": " + tuple.u + "/" + tuple.v);
withIndex.forEach(System.out::println);
This produces the following output:
0: 1.1/a
1: 2.2/b
2: 3.3/c
3: 4.4/d
4: 5.5/e
This works by first zipping streams for c1 and c2 collections into one zipped stream of tuples and then mapping this zipped stream with a function that receives both each tuple and its corresponding index.
Note that Streams.mapWithIndex must receive a BiFunction, which means that it must return a value. If you want to consume both the tuples and the indices instead, I'm afraid you will need to create a new tuple containing the original tuple and the index:
Stream<Tuple<Tuple<Double, String>, Long>> withIndex = Streams.mapWithIndex(
zipped,
Tuple::new);
withIndex.forEach(tuple -> someMethod(tuple.u, tuple.v));
Where someMethod has the following signature:
<U, V> void method(Tuple<U, V> tuple, long index)
Note 1: this example assumes the following Tuple class is used:
public class Tuple<U, V> {
private final U u;
private final V v;
Tuple(U u, V v) {
this.u = u;
this.v = v;
}
// TODO: getters and setters, hashCode and equals
}
Note 2: while you can achieve the same with iterators, the main advantage of these utilities is that they also work efficiently on parallel streams.
Note 3: this functionality is available since Guava 21.0.
I want to write a type-safe map method in Java that returns a Collection of the same type as the argument passed (i.e. ArrayList, LinkedList, TreeSet, etc.) but with a different generic type (that between the angled brackets), determined by the generic type of another parameter (the resulting type of the generic mapping function).
So the code would be used as:
public interface TransformFunctor<S, R> {
public R apply(S sourceObject);
}
TransformFunctor i2s = new TransformFunctor<Integer, String>() {
String apply(Integer i) {return i.toString();};
ArrayList<Integer> ali = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4));
ArrayList<String> als = map(ali, i2s);
TreeSet<Integer> tsi = new TreeSet<Integer>(Arrays.asList(1, 2, 3, 4));
TreeSet<String> tss = map(tsi, i2s);
The idea would be something like:
public static <C extends Collection<?>, S, R>
C<R> map(final C<S> collection, final TransformFunctor<S, R> f)
throws Exception {
//if this casting can be removed, the better
C<R> result = (C<R>) collection.getClass().newInstance();
for (S i : collection) {
result.add(f.apply(i));
}
return result;
}
but that doesn't work because the compiler isn't expecting generic type variables to be further specialised (I think).
Any idea on how to make this work?
AFAIK there is no way to do this in Java so that it's both (a) compile-type-safe and (b) the caller of map does not need to repeat the collection type of source and target. Java does not support higher-order kinds, what you're asking for can be achieved in Scala 2.8, but even there the implementation details are somewhat complex, see this SO question.
It seems that it is not possible to use generic types on generic types. Since you need only a limited number of those you can just enumerate them:
public static <CS extends Collection<S>, CR extends Collection<R>, S, R> CR map(
final CS collection, final TransformFunctor<S, R> f)
throws Exception {
// if this casting can be removed, the better
CR result = (CR) collection.getClass().newInstance();
for (S i : collection) {
result.add(f.apply(i));
}
return result;
}
I used CS as source collection and CR as result collection. I'm afraid you can't remove the cast because you can't use generics at runtime. newInstance() just creates an object of type some collection of Object and the cast to CR is necessary to satisfy the compiler. But it's still something of a cheat. That's why the compiler issues a warning that you have to suppress with #SuppressWarnings("unchecked").
Interesting question btw.
This works:
class Test {
public static void main(String[] args) throws Exception {
Function<Integer, String> i2s = new Function<Integer, String>() {
public String apply(Integer i) {
return i.toString();
}
};
ArrayList<Integer> ali = new ArrayList<Integer>(Arrays.asList(1, 2, 3,
4));
ArrayList<String> als = map(ali, i2s);
TreeSet<Integer> tsi = new TreeSet<Integer>(Arrays.asList(1, 2, 3, 4));
TreeSet<String> tss = map(tsi, i2s);
System.out.println(""+ali+als+tss);
}
static <SC extends Collection<S>, S, T, TC extends Collection<T>> TC map(
SC collection, Function<S, T> func) throws Exception {
// if this casting can be removed, the better
TC result = (TC) collection.getClass().newInstance();
for (S i : collection) {
result.add(func.apply(i));
}
return result;
}
}
interface Function<S, R> {
R apply(S src);
}
What you want to do is not possible. You can, however, specify the generic type of your returned collection.
public static <S, R> Collection<R> map(Collection<S> collection, TransformFunctor<S,R> f) throws Exception {
Collection<R> result = collection.getClass().newInstance();
for (S i : collection) {
result.add(f.apply(i));
}
return result;
}
This does return a collection of the type you specify with your argument, it just doesn't do so explicitly. It is, however, safe to cast the method return to the collection type of your argument.
You should note, however, that the code will fail if the collection type you pass does not have a no-arg constructor.
You can then use it like this:
ArrayList<Integer> ali = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4));
ArrayList<String> als = (ArrayList<String>) map(ali, i2s);
TreeSet<Integer> tsi = new TreeSet<Integer>(Arrays.asList(1, 2, 3, 4));
TreeSet<String> tss = (TreeSet<String>) map(tsi, i2s);