Java Stream API storing lambda expression as variable - java

This title sounds stupid even to me, but there must be at least somewhat clever way to achieve such effect and I don't know how else to explain it. I need to sort array using sorted in stream API. Here is my stream so far:
Arrays.stream(sequence.split(" "))
.mapToInt(Integer::parseInt)
.boxed()
.sorted((a, b) -> a.compareTo(b))
.forEach(a -> System.out.print(a + " "));
Now I have two different sorts of course - ascending and descending and the sort I need to use is specified in the user input. So what I want to do is having something like switch with 2 cases: "ascending" and "descending" and a variable to store the lambda expression respectively:
switch(command) {
case "ascending": var = a.compareTo(b);
case "descending": var = b.compareTo(a);
}
Then I my sorted looks like:
.sorted((a, b) -> var)
I got the idea in a python course I attended. There it was available to store an object in variable, thus making the variable "executable". I realize that this lambda is not an object, but an expression, but I'm asking is there any clever way that can achieve such result, or should I just have
if(var)
and two diferent streams for each sort order.

The question is not stupid at all. Answering it in a broader sense: Unfortunately, there is no generic solution for that. This is due to the type inference, which determines one particular type for the lambda expression, based on the target type. (The section about type inference may be helpful here, but does not cover all details regarding lambdas).
Particularly, a lambda like x -> y does not have any type. So there is no way of writing
GenericLambdaTypefunction = x -> y;
and later use function as a drop-in replacement for the actual lambda x -> y.
For example, when you have two functions like
static void useF(Function<Integer, Boolean> f) { ... }
static void useP(Predicate<Integer> p) { ... }
you can call them both with the same lambda
useF(x -> true);
useP(x -> true);
but there is no way of "storing" the x -> true lambda in a way so that it later may be passed to both functions - you can only store it in a reference with the type that it will be needed in later:
Function<Integer, Boolean> f = x -> true;
Predicate<Integer> p = x -> true;
useF(f);
useP(p);
For your particular case, the answer by Konstantin Yovkov already showed the solution: You have to store it as a Comparator<Integer> (ignoring the fact that you wouldn't have needed a lambda here in the first place...)

You can switch between using Comparator.reverseOrder() and Comparator.naturalOrder:
Comparator<Integer> comparator = youWantToHaveItReversed ? Comparator.reverseOrder(): Comparator.naturalOrder();
Arrays.stream(sequence.split(" "))
.map(Integer::valueOf)
.sorted(comparator)
.forEach(a -> System.out.print(a + " "));

In Lambdas you can use a functionblock
(a,b) -> { if(anything) return 0; else return -1;}

Related

How to translate following comparator into Comparator.comparing?

My question may be unclear so let me clear it out by example:
Arrays.sort(arr, new Comparator<String>(){
public int compare(String a, String b){
return (b + a).compareTo(a + b);
}
});
I want to use the Comparator.comparing. I tried out the following:
Arrays.sort(arr, Comparator.comparing((a, b) -> (b + a).compareTo((String)a + b)));
I get an error - bad return type in lamdba expression. How to fix this ?
Comparator.comparing method expects a keyExtractor of type Function. You just need a lambda to implement the Comparator<String> interface here:
Arrays.sort(arr, (a, b) -> (b + a).compareTo(a + b));
It is impossible to turn that into a call to comparing because it's not a valid Comparator: it doesn't satisfy the Comparator contract, and you should never use it as a Comparator.
To prove it, that Comparator will compare every string as equal to "", but not every string will be equal to each other. That violates the transitivity property.
As already answered, your anonymous class implementation can be shortened into a lambda expression:
Arrays.sort(arr, (a, b) -> (b + a).compareTo(a + b));
If you insist on using Comparator.comparing(), remember it has specific parameters that don't fit your sorting problem.
The Comparator.comparing(keyExtractor) returns a specified Comparator for certain key based on the natural way of comparison (Comparator.naturalOrder). Your method doesn't say what is compared, but how it is.
The Comparator.comparing(keyExtractor, keyComparator) looks a bit better because you can specify how the specified keys are compared using keyComparator. You can use your logics of comparing and you conclude to:
Arrays.sort(arr, Comparator.comparing(
Function.identity(), // keyExtractor, WHAT is compared
(a, b) -> (b + a).compareTo(a + b))); // keyComparator, HOW is it compared
This is a solution using Comparator.comparing that uses a keyExtractor the Function.identity() returning the input back (the same like str -> str lambda expression) since you want still compare the Strings but in a different way specified with a custom Comparator, therefore the only correct way to sort the array as you need is the simplified version omitting the keyExtractor:
Arrays.sort(arr, (a, b) -> (b + a).compareTo(a + b));
... which is finally where we started at.

How to make this Java summation stream more compact?

I have this stream from a Map that sums the integers from a method call on an object based on a certain condition.
I would like to know how to make it more compact and efficient.
int total = myMap.values().stream()
.filter(obj -> obj.countsInt())
.mapToInt(MyObject::getInt)
.sum();
You can use a method reference for obj.countsInt() and also use Collectors.summingInt() for your reducing method
int total = myMap.values()
.stream()
.filter(MyObject::countsInt)
.collect(summingInt(MyObject::getInt));
Compactness:
Use method references:
obj -> obj.countsInt()
can be replace by
MyObject::countsInt
Ignoring that, you could go straight to ints replacing:
.filter(obj -> obj.countsInt()).mapToInt(MyObject::getInt)
with
.mapToInt(obj -> obj.countsInt() ? 0 : obj.getInt())
But if you want efficiency, don't use streams.

Identity for BinaryOperator

I see in Java8 in UnaryOperator Interface following piece of code which does nothing on parameter and returns same value.
static <T> UnaryOperator<T> identity() {
return t -> t;
}
Is there anything for BinaryOperator which accepts two parameters of samekind and returns one value
static <T> BinaryOperator<T> identity() {
return (t,t) -> t;
}
why I am asking this question is for below requirement,
List<String> list = Arrays.asList("Abcd","Abcd");
Map<String,Integer> map = list.stream().collect(Collectors.toMap(str->str,
str->(Integer)str.length(),(t1,t2)->t1));
System.out.println(map.size());
in above code I don't want to do anything for two values of same key, I just wanted return one value, because in my case for sure values will be same.
As am not using t2 value Sonar throwing error, So I am finding out is there any thing like UnaryOperator.identity() for BinaryOpertor also in java8
Your question doesn't really make sense. If you were to paste your proposed BinaryOperator.identity method into an IDE, you would immediately see that it would complain that the identifier t is declared twice.
To fix this, we need a different identifier for each parameter:
return (t, u) -> t;
Now we can clearly see that this is not an identity function. It's a method which takes two arguments and returns the first one. Therefore the best name for this would be something like getFirst.
To answer your question about whether there's anything like this in the JDK: no. Using an identity function is a common use case, so defining a method for that is useful. Arbitrarily returning the first argument of two is not a common use case, and it's not useful to have a method to do that.
T means they have the same types, not the same values, that is not an identity per-se.
It just means that BinaryOperator will be used for the same types, but providing an identity for different values... this somehow sounds like foldLeft or foldRight or foldLeftIdentity/foldRightIdentity, which java does not have.
Your code seemingly can be improved as
List<String> list = Arrays.asList("Abcd", "Abcd");
Map<String, Integer> map = list.stream()
.collect(Collectors.toMap(Function.identity(), String::length, (a, b) -> a));
System.out.println(map.size());
Or possibly for your use case I don't want to do anything for two values of same key, I just wanted return one value, you may just choose to randomly return any value in using an implementation as following:
private static <T> BinaryOperator<T> any() {
return Math.random() < 0.5 ? ((x, y) -> x) : ((x, y) -> y);
}
and then in your code use it as
Map<String, Integer> map = list.stream()
.collect(Collectors.toMap(Function.identity(), String::length, any()));
Thanks to the suggestions from Holger, Eugene, and Federico, there are other efficient implementations of the any method that can actually involve using :
private static <T> BinaryOperator<T> any() {
// suggested by Holger
return ThreadLocalRandom.current().nextBoolean() ? ((x, y) -> x) : ((x, y) -> y);
// suggested by Eugene
long nt = System.nanoTime();
((nt >>> 32) ^ nt) > 0 ? ((x, y) -> x) : ((x, y) -> y);
}

Equivalent of Scala's foldLeft in Java 8

What is the equivalent of of Scala's great foldLeft in Java 8?
I was tempted to think it was reduce, but reduce has to return something of identical type to what it reduces on.
Example:
import java.util.List;
public class Foo {
// this method works pretty well
public int sum(List<Integer> numbers) {
return numbers.stream()
.reduce(0, (acc, n) -> (acc + n));
}
// this method makes the file not compile
public String concatenate(List<Character> chars) {
return chars.stream()
.reduce(new StringBuilder(""), (acc, c) -> acc.append(c)).toString();
}
}
The problem in the code above is the accumulator: new StringBuilder("")
Thus, could anyone point me to the proper equivalent of the foldLeft/fix my code?
There is no equivalent of foldLeft in Java 8's Stream API. As others noted, reduce(identity, accumulator, combiner) comes close, but it's not equivalent with foldLeft because it requires the resulting type B to combine with itself and be associative (in other terms, be monoid-like), a property that not every type has.
There is also an enhancement request for this: add Stream.foldLeft() terminal operation
To see why reduce won't work, consider the following code, where you intend to execute a series of arithmetic operations starting with given number:
val arithOps = List(('+', 1), ('*', 4), ('-', 2), ('/', 5))
val fun: (Int, (Char, Int)) => Int = {
case (x, ('+', y)) => x + y
case (x, ('-', y)) => x - y
case (x, ('*', y)) => x * y
case (x, ('/', y)) => x / y
}
val number = 2
arithOps.foldLeft(number)(fun) // ((2 + 1) * 4 - 2) / 5
If you tried writing reduce(2, fun, combine), what combiner function could you pass that combines two numbers? Adding the two numbers together clearly does not solve it. Also, the value 2 is clearly not an identity element.
Note that no operation that requires a sequential execution can be expressed in terms of reduce. foldLeft is actually more generic than reduce: you can implement reduce with foldLeft but you cannot implement foldLeft with reduce.
Update:
Here is initial attempt to get your code fixed:
public static String concatenate(List<Character> chars) {
return chars
.stream()
.reduce(new StringBuilder(),
StringBuilder::append,
StringBuilder::append).toString();
}
It uses the following reduce method:
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
It may sound confusing but if you look at the javadocs there is a nice explanation that may help you quickly grasp the details. The reduction is equivalent to the following code:
U result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
For a more in-depth explanation please check this source.
This usage is not correct though because it violates the contract of reduce which states that the accumulator should be an associative, non-interfering, stateless function for incorporating an additional element into a result. In other words since the identity is mutable the result will be broken in case of parallel execution.
As pointed in the comments below a correct option is using the reduction as follows:
return chars.stream().collect(
StringBuilder::new,
StringBuilder::append,
StringBuilder::append).toString();
The supplier StringBuilder::new will be used to create reusable containers which will be later combined.
The method you are looking for is java.util.Stream.reduce, particularly the overload with three parameters, identity, accumulator, and binary function. That is the correct equivalent to Scala's foldLeft.
However, you are not allowed to use Java's reduce that way, and also not Scala's foldLeft for that matter. Use collect instead.
It can be done by using Collectors:
public static <A, B> Collector<A, ?, B> foldLeft(final B init, final BiFunction<? super B, ? super A, ? extends B> f) {
return Collectors.collectingAndThen(
Collectors.reducing(Function.<B>identity(), a -> b -> f.apply(b, a), Function::andThen),
endo -> endo.apply(init)
);
}
Usage example:
IntStream.rangeClosed(1, 100).boxed().collect(foldLeft(50, (a, b) -> a - b)); // Output = -5000
For your question, this does what you wanted:
public String concatenate(List<Character> chars) {
return chars.stream()
.collect(foldLeft(new StringBuilder(), StringBuilder::append)).toString();
}
Others are correct there's no equivalent though. Here's a util that comes close-
<U, T> U foldLeft(Collection<T> sequence, U identity, BiFunction<U, ? super T, U> accumulator) {
U result = identity;
for (T element : sequence)
result = accumulator.apply(result, element);
return result;
}
your case using the above method would look like-
public String concatenate(List<Character> chars) {
return foldLeft(chars, new StringBuilder(""), StringBuilder::append).toString();
}
Or without the lambda method ref sugar,
public String concatenate(List<Character> chars) {
return foldLeft(chars, new StringBuilder(""), (stringBuilder, character) -> stringBuilder.append(character)).toString();
}

How to do additional processing in Java Stream API near the "::" method reference operator

Using the Java Stream API, is there a way to do additional processing to adjust the value of whatever is passed to a method reference?
I'll give two examples.
Example 1.
In the first example, I start with a Stream<Path>, and I want to return a Map<String, Path> in which the keys in the map are processed version of the filename using another function that takes a String filename (not a Path). Specifically:
public Map<String, Path> createMap(Path sourceFolder, PathMatcher filter) {
return stream.filter(filter::matches)
.collect(Collectors.toMap(FilenameHelper::parseFilename, Function.identity()));
parseFilename(String filename) takes a String filename, but of course the method reference gets a Path. I'd like to say something like, FilenameHelper::parseFilename(((Path)Function.identity()).toFile().getName()) but that doesn't work (Eclipse says: "The left-hand side of an assignment must be a variable"). I can work around it by creating a new method that takes a Path and just does return parseFilename(path.toFile().toName()) but that's not cool.
Example 2.
In the second example, I have rows, a List<List<String>>> that represents a data table (rows, then columns). I have a method that should return a List<String> consisting of a specific column in that table for every nth row. I want to do something like:
public List<String> getDataFromColumn(String columnName, int nth) {
/// Without a clause at ???, this returns a List<List<String>>
return IntStream.range(0, rows.size())
.filter(n -> n % nth == 0) // Get every nth row
.mapToObj(rows::get)
.???
.collect(Collectors.toList());
}
Where "???" should be something like map(ArrayList::get(headers.indexOf(columnName))) (where headers is a List<String> containing the column headers) but if I put that in, I get an AssignmentOperator syntax error in the get part of this clause. Replacing map with forEach doesn't help here. In other words, I don't want rows.get(n), I want rows.get(n).get(headers.indexOf(columnName).
Question
In both of these examples, I want to do something additional to the value that is being passed to the method pointed to with the method reference operator (::). Is there a "Java Stream-ic" way to do additional processing to the thing being passed to the method reference?
Method references are essentially a convenient substitute for lambdas where the function signature is an exact match to the method signature. In your case you can just use regular lambdas:
public Map<String, Path> createMap(Path sourceFolder, PathMatcher filter) {
return stream.filter(filter::matches)
.collect(Collectors.toMap(path -> FilenameHelper.parseFilename(path.toFile().getName()), Function.identity()));
}
public List<String> getDataFromColumn(String columnName, int nth) {
return IntStream.range(0, rows.size())
.filter(n -> n % nth == 0)
.mapToObj(rows::get)
.map(row -> row.get(headers.indexOf(columnName)))
.collect(Collectors.toList());
}
How about Function.compose? Of course you cannot use FilenameHelper::parseFilename.compose, but you can easily write a static helper method to work around it:
static <T, V, R> Function<T, R> compose(Function<T, V> f, Function<V, R> g) {
return g.compose(f);
}
Now we can compose method references:
return stream.filter(filter::matches)
.collect(Collectors.toMap(
compose(
compose(Path::getFileName, Path::toString),
FilenameHelper::parseFilename),
Function.identity()));
This is actually not very readable but an alternative to writing a full lambda.
No, this functionality is currently not provided.
The usual way would be to just not use a method reference and instead call the method the "usual" way using a lambda expression:
stream.filter(filter::matches)
.collect(Collectors.toMap(p -> FilenameHelper.parseFilename(p.getFileName()), Function.identity()));
No, there is not. There is no syntax to do that.
And if you wanted such a thing then lambda expression is what you want.
Method reference or lambda, under the hood you are still going to get a class that actually implements the Predicate/Function so it does not matter.
And that argument but that's not cool, to me under the conditions that there is no syntax for that, it's the best option you have.
Underneath the actual calls that you there is a MethodHandle (introduced in jdk-7) and MethodHandles do not have a way to achieve what you want. I think the same restriction exists in C++ with method pointers.

Categories

Resources