Using Java 8 stream methods to get the last max value
Clash Royale CLAN TAG#URR8PPP
Given a list of items with properties, I am trying to get the last item to appear with a maximum value of said property.
For example, for the following list of objects:
t i
A: 3
D: 7 *
F: 4
C: 5
X: 7 *
M: 6
I can get one of the Things with the highest i
:
Thing t = items.stream()
.max(Comparator.comparingLong(Thing::getI))
.orElse(null);
However, this will get me Thing t = D
. Is there a clean and elegant way of getting the last item, i.e. X
in this case?
One possible solution is using the reduce
function. However, the property is calculated on the fly and it would look more like:
Thing t = items.stream()
.reduce((left, right) ->
long leftValue = valueFunction.apply(left);
long rightValue = valueFunction.apply(right);
return leftValue > rightValue ? left : right;
)
.orElse(null);
The valueFunction
now needs to be called nearly twice as often.
Other obvious roundabout solutions are:
- Store the object in a Tuple with its index
- Store the object in a Tuple with its computed value
- Reverse the list beforehand
- Don't use Streams
java java-8 java-stream
add a comment |
Given a list of items with properties, I am trying to get the last item to appear with a maximum value of said property.
For example, for the following list of objects:
t i
A: 3
D: 7 *
F: 4
C: 5
X: 7 *
M: 6
I can get one of the Things with the highest i
:
Thing t = items.stream()
.max(Comparator.comparingLong(Thing::getI))
.orElse(null);
However, this will get me Thing t = D
. Is there a clean and elegant way of getting the last item, i.e. X
in this case?
One possible solution is using the reduce
function. However, the property is calculated on the fly and it would look more like:
Thing t = items.stream()
.reduce((left, right) ->
long leftValue = valueFunction.apply(left);
long rightValue = valueFunction.apply(right);
return leftValue > rightValue ? left : right;
)
.orElse(null);
The valueFunction
now needs to be called nearly twice as often.
Other obvious roundabout solutions are:
- Store the object in a Tuple with its index
- Store the object in a Tuple with its computed value
- Reverse the list beforehand
- Don't use Streams
java java-8 java-stream
1
"The valueFunction now needs to be called nearly twice as often." Note that even when usingmax
, thegetI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D. How about you just cache the calculated value directly in the Thing instance?
– tobias_k
Feb 5 at 16:14
@tobias_k I realised that after posting my question. My practical solution was to use a standard for each.
– Druckles
Feb 5 at 17:35
2
If your stream goes parallel or async, there can be a complete loss of the idea of "in order". Be sure to think about this and consider if your stream can contain a field or value indicating the order, similar to a tuple of (value, orderIndex) so that the max operation is a simple, parallelizable comparison of the value and orderIndex fields.
– ErikE
Feb 5 at 19:19
@ErikE if the stream has a defined encounter order, thereduce
based solution will work in parallel, as long as the reduction function fulfills the associativity constraint. Which is the case here.
– Holger
Feb 7 at 12:04
add a comment |
Given a list of items with properties, I am trying to get the last item to appear with a maximum value of said property.
For example, for the following list of objects:
t i
A: 3
D: 7 *
F: 4
C: 5
X: 7 *
M: 6
I can get one of the Things with the highest i
:
Thing t = items.stream()
.max(Comparator.comparingLong(Thing::getI))
.orElse(null);
However, this will get me Thing t = D
. Is there a clean and elegant way of getting the last item, i.e. X
in this case?
One possible solution is using the reduce
function. However, the property is calculated on the fly and it would look more like:
Thing t = items.stream()
.reduce((left, right) ->
long leftValue = valueFunction.apply(left);
long rightValue = valueFunction.apply(right);
return leftValue > rightValue ? left : right;
)
.orElse(null);
The valueFunction
now needs to be called nearly twice as often.
Other obvious roundabout solutions are:
- Store the object in a Tuple with its index
- Store the object in a Tuple with its computed value
- Reverse the list beforehand
- Don't use Streams
java java-8 java-stream
Given a list of items with properties, I am trying to get the last item to appear with a maximum value of said property.
For example, for the following list of objects:
t i
A: 3
D: 7 *
F: 4
C: 5
X: 7 *
M: 6
I can get one of the Things with the highest i
:
Thing t = items.stream()
.max(Comparator.comparingLong(Thing::getI))
.orElse(null);
However, this will get me Thing t = D
. Is there a clean and elegant way of getting the last item, i.e. X
in this case?
One possible solution is using the reduce
function. However, the property is calculated on the fly and it would look more like:
Thing t = items.stream()
.reduce((left, right) ->
long leftValue = valueFunction.apply(left);
long rightValue = valueFunction.apply(right);
return leftValue > rightValue ? left : right;
)
.orElse(null);
The valueFunction
now needs to be called nearly twice as often.
Other obvious roundabout solutions are:
- Store the object in a Tuple with its index
- Store the object in a Tuple with its computed value
- Reverse the list beforehand
- Don't use Streams
java java-8 java-stream
java java-8 java-stream
edited Feb 5 at 14:13
nullpointer
43.2k10101200
43.2k10101200
asked Feb 5 at 13:34
DrucklesDruckles
94311033
94311033
1
"The valueFunction now needs to be called nearly twice as often." Note that even when usingmax
, thegetI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D. How about you just cache the calculated value directly in the Thing instance?
– tobias_k
Feb 5 at 16:14
@tobias_k I realised that after posting my question. My practical solution was to use a standard for each.
– Druckles
Feb 5 at 17:35
2
If your stream goes parallel or async, there can be a complete loss of the idea of "in order". Be sure to think about this and consider if your stream can contain a field or value indicating the order, similar to a tuple of (value, orderIndex) so that the max operation is a simple, parallelizable comparison of the value and orderIndex fields.
– ErikE
Feb 5 at 19:19
@ErikE if the stream has a defined encounter order, thereduce
based solution will work in parallel, as long as the reduction function fulfills the associativity constraint. Which is the case here.
– Holger
Feb 7 at 12:04
add a comment |
1
"The valueFunction now needs to be called nearly twice as often." Note that even when usingmax
, thegetI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D. How about you just cache the calculated value directly in the Thing instance?
– tobias_k
Feb 5 at 16:14
@tobias_k I realised that after posting my question. My practical solution was to use a standard for each.
– Druckles
Feb 5 at 17:35
2
If your stream goes parallel or async, there can be a complete loss of the idea of "in order". Be sure to think about this and consider if your stream can contain a field or value indicating the order, similar to a tuple of (value, orderIndex) so that the max operation is a simple, parallelizable comparison of the value and orderIndex fields.
– ErikE
Feb 5 at 19:19
@ErikE if the stream has a defined encounter order, thereduce
based solution will work in parallel, as long as the reduction function fulfills the associativity constraint. Which is the case here.
– Holger
Feb 7 at 12:04
1
1
"The valueFunction now needs to be called nearly twice as often." Note that even when using
max
, the getI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D. How about you just cache the calculated value directly in the Thing instance?– tobias_k
Feb 5 at 16:14
"The valueFunction now needs to be called nearly twice as often." Note that even when using
max
, the getI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D. How about you just cache the calculated value directly in the Thing instance?– tobias_k
Feb 5 at 16:14
@tobias_k I realised that after posting my question. My practical solution was to use a standard for each.
– Druckles
Feb 5 at 17:35
@tobias_k I realised that after posting my question. My practical solution was to use a standard for each.
– Druckles
Feb 5 at 17:35
2
2
If your stream goes parallel or async, there can be a complete loss of the idea of "in order". Be sure to think about this and consider if your stream can contain a field or value indicating the order, similar to a tuple of (value, orderIndex) so that the max operation is a simple, parallelizable comparison of the value and orderIndex fields.
– ErikE
Feb 5 at 19:19
If your stream goes parallel or async, there can be a complete loss of the idea of "in order". Be sure to think about this and consider if your stream can contain a field or value indicating the order, similar to a tuple of (value, orderIndex) so that the max operation is a simple, parallelizable comparison of the value and orderIndex fields.
– ErikE
Feb 5 at 19:19
@ErikE if the stream has a defined encounter order, the
reduce
based solution will work in parallel, as long as the reduction function fulfills the associativity constraint. Which is the case here.– Holger
Feb 7 at 12:04
@ErikE if the stream has a defined encounter order, the
reduce
based solution will work in parallel, as long as the reduction function fulfills the associativity constraint. Which is the case here.– Holger
Feb 7 at 12:04
add a comment |
7 Answers
7
active
oldest
votes
Remove the equals option (don't return 0 if the compared numbers are equal, return -1 instead) from the comparator (ie. write your own comparator that doesn't include an equals option):
Thing t = items.stream()
.max((a, b) -> a.getI() > b.getI() ? 1 : -1)
.orElse(null);
2
I'm not sure that java guarantees the comparator is called "in order", i.e. the later items always is the second argument (thinking about parallel execution). That being said, it makes sense to place a little bit of trust in this implementation detail.
– WorldSEnder
Feb 5 at 17:16
1
I've accepted this because it's the most elegant solution and, as far as I can tell, as efficient as a Comparator (which is what I wanted). If I don't want to compute the value every time I have to settle for a for each or a tuple. Thank you.
– Druckles
Feb 5 at 17:39
2
I do believe this is not guaranteed to work as you expect the moment that the stream goes parallel/asynch.
– ErikE
Feb 5 at 19:16
1
What does this have to do withequals
? The only trick seems to be to invert the tie-breaking from "a unless b is greater" to "b unless a is greater", which is exactly the same as OP'sreduce
did, but IMHO less readable, as you return+/-1
instead of the actual object to retain. Also, this, too, will callgetI
more often than necessary (just as often asreduce
).
– tobias_k
Feb 5 at 22:41
@tobias_k a standard comparator returns +1, 0 or -1, for a being greater than, equal, or less than b. I say 'removing the equals option' meaning my comparator only return +1 or -1, for greater than or not greater than.
– jvdmr
Feb 6 at 8:11
|
show 3 more comments
Conceptually, you seem to be possibly looking for something like thenComparing
using the index
of the elements in the list:
Thing t = items.stream()
.max(Comparator.comparingLong(Thing::getI).thenComparing(items::indexOf))
.orElse(null);
1
Assuming: that for two objects if attributeI
is equal that doesn't mean the objects are equal as well. In which case theindexOf
would return same value always.
– nullpointer
Feb 5 at 14:12
1
This is nice, but the problem I am trying to solve was actually originally caused by theequals
method returning true for different objects ;-)
– Druckles
Feb 5 at 15:08
2
You're right. Unfortunately, I didn't write theequals
.
– Druckles
Feb 5 at 15:17
7
Doesn't this give the whole thing O(n²) complexity? Also, would not work for a pure stream, with not backing list (not a requirement, but anyway). Something like Python'senumerate
would come in handy here.
– tobias_k
Feb 5 at 15:52
1
@Marco13 in the worst case, all elements are "new maximum" values in respect to the previous elements, i.e. when the input elements happen to be in ascending order. So O(n²) is the correct time complexity of this solution.
– Holger
Feb 7 at 11:55
|
show 2 more comments
To avoid the multiple applications of valueFunction in your reduce solution, simply explicitly calculate the result and put it in a tuple:
Item lastMax = items.stream()
.map(item -> new AbstractMap.SimpleEntry<Item, Long>(item, valueFunction.apply(item)))
.reduce((l, r) -> l.getValue() > r.getValue() ? l : r )
.map(Map.Entry::getKey)
.orElse(null);
add a comment |
Stream is not necessary bad if you do things in two steps :
1) Find the i
value that has more occurrences in the Iterable
(as you did)
2) Search the last element for this i
value by starting from the end of items:
Thing t =
items.stream()
.max(Comparator.comparingLong(Thing::getI))
.mapping(firstMaxThing ->
return
IntStream.rangeClosed(1, items.size())
.mapToObj(i -> items.get(items.size()-i))
.filter(item -> item.getI() == firstMaxThing.getI())
.findFirst().get();
// here get() cannot fail as *max()* returned something.
)
.orElse(null)
add a comment |
The valueFunction now needs to be called nearly twice as often.
Note that even when using max
, the getI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D, and for longer lists, too, it seems to be called on average twice per element.
How about you just cache the calculated value directly in the Thing
instance? If this is not possible, you could use an external Map
and use calculateIfAbsent
to calculate the value only once for each Thing
and then use your approach using reduce
.
Map<Thing, Long> cache = new HashMap<>();
Thing x = items.stream()
.reduce((left, right) ->
long leftValue = cache.computeIfAbsent(left, Thing::getI);
long rightValue = cache.computeIfAbsent(right, Thing::getI);
return leftValue > rightValue ? left : right;
)
.orElse(null);
Or a bit cleaner, calculating all the values beforehand:
Map<Thing, Long> cache = items.stream()
.collect(Collectors.toMap(x -> x, Thing::getI));
Thing x = items.stream()
.reduce((left, right) -> cache.get(left) > cache.get(right) ? left : right)
.orElse(null);
1
BTW, you can easily check how often the comparator callback is called by adding some print statement or incrementing a counter ingetI
or in the callback itself.
– tobias_k
Feb 5 at 22:45
add a comment |
You can still use the reduction to get this thing done. If t1 is larger, then only it will keep t1. In all the other cases it will keep t2. If either t2 is larger or t1 and t2 are the same, then it will eventually return t2 adhering to your requirement.
Thing t = items.stream().
reduce((t1, t2) -> t1.getI() > t2.getI() ? t1 : t2)
.orElse(null);
This is the same solution as I presented in my question.
– Druckles
Feb 5 at 17:37
1
@Druckles Technically, the answer you accepted is also the same as your solution, just less obviously so.
– tobias_k
Feb 5 at 22:43
@tobias_k "Less obvious" is everything in programming.
– Druckles
Feb 6 at 8:53
1
@Druckles Am I getting you correctly? Your programming goal is to write as obfuscated code as possible?
– Holger
Feb 7 at 11:53
1
@Druckles I don't see any way, how to interpret your sentence "'Less obvious' is everything in programming" in a different way than that you like non-obvious things, like the accepted answer, which only makes it less obvious that it still does the same as the question's code, including having the same problems, plus adding another one, using a formally incorrect comparator, which makes it less obvious why it still happens to do the desired thing. "less obvious" is obfuscation. Actually, "is everything" does not only imply that you like it but that it is the most important thing to you.
– Holger
Feb 7 at 13:19
|
show 3 more comments
Your current implementation using reduce
looks good, unless your value-extractor function is costly.
Considering later you may want to reuse the logic for different object types & fields, I would extract the logic itself in separate generic method/methods:
public static <T, K, V> Function<T, Map.Entry<K, V>> toEntry(Function<T, K> keyFunc, Function<T, V> valueFunc)
return t -> new AbstractMap.SimpleEntry<>(keyFunc.apply(t), valueFunc.apply(t));
public static <ITEM, FIELD extends Comparable<FIELD>> Optional<ITEM> maxBy(Function<ITEM, FIELD> extractor, Collection<ITEM> items)
return items.stream()
.map(toEntry(identity(), extractor))
.max(comparing(Map.Entry::getValue))
.map(Map.Entry::getKey);
The code snippets above can be used like this:
Thing maxThing = maxBy(Thing::getField, things).orElse(null);
AnotherThing maxAnotherThing = maxBy(AnotherThing::getAnotherField, anotherThings).orElse(null);
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54535632%2fusing-java-8-stream-methods-to-get-the-last-max-value%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
7 Answers
7
active
oldest
votes
7 Answers
7
active
oldest
votes
active
oldest
votes
active
oldest
votes
Remove the equals option (don't return 0 if the compared numbers are equal, return -1 instead) from the comparator (ie. write your own comparator that doesn't include an equals option):
Thing t = items.stream()
.max((a, b) -> a.getI() > b.getI() ? 1 : -1)
.orElse(null);
2
I'm not sure that java guarantees the comparator is called "in order", i.e. the later items always is the second argument (thinking about parallel execution). That being said, it makes sense to place a little bit of trust in this implementation detail.
– WorldSEnder
Feb 5 at 17:16
1
I've accepted this because it's the most elegant solution and, as far as I can tell, as efficient as a Comparator (which is what I wanted). If I don't want to compute the value every time I have to settle for a for each or a tuple. Thank you.
– Druckles
Feb 5 at 17:39
2
I do believe this is not guaranteed to work as you expect the moment that the stream goes parallel/asynch.
– ErikE
Feb 5 at 19:16
1
What does this have to do withequals
? The only trick seems to be to invert the tie-breaking from "a unless b is greater" to "b unless a is greater", which is exactly the same as OP'sreduce
did, but IMHO less readable, as you return+/-1
instead of the actual object to retain. Also, this, too, will callgetI
more often than necessary (just as often asreduce
).
– tobias_k
Feb 5 at 22:41
@tobias_k a standard comparator returns +1, 0 or -1, for a being greater than, equal, or less than b. I say 'removing the equals option' meaning my comparator only return +1 or -1, for greater than or not greater than.
– jvdmr
Feb 6 at 8:11
|
show 3 more comments
Remove the equals option (don't return 0 if the compared numbers are equal, return -1 instead) from the comparator (ie. write your own comparator that doesn't include an equals option):
Thing t = items.stream()
.max((a, b) -> a.getI() > b.getI() ? 1 : -1)
.orElse(null);
2
I'm not sure that java guarantees the comparator is called "in order", i.e. the later items always is the second argument (thinking about parallel execution). That being said, it makes sense to place a little bit of trust in this implementation detail.
– WorldSEnder
Feb 5 at 17:16
1
I've accepted this because it's the most elegant solution and, as far as I can tell, as efficient as a Comparator (which is what I wanted). If I don't want to compute the value every time I have to settle for a for each or a tuple. Thank you.
– Druckles
Feb 5 at 17:39
2
I do believe this is not guaranteed to work as you expect the moment that the stream goes parallel/asynch.
– ErikE
Feb 5 at 19:16
1
What does this have to do withequals
? The only trick seems to be to invert the tie-breaking from "a unless b is greater" to "b unless a is greater", which is exactly the same as OP'sreduce
did, but IMHO less readable, as you return+/-1
instead of the actual object to retain. Also, this, too, will callgetI
more often than necessary (just as often asreduce
).
– tobias_k
Feb 5 at 22:41
@tobias_k a standard comparator returns +1, 0 or -1, for a being greater than, equal, or less than b. I say 'removing the equals option' meaning my comparator only return +1 or -1, for greater than or not greater than.
– jvdmr
Feb 6 at 8:11
|
show 3 more comments
Remove the equals option (don't return 0 if the compared numbers are equal, return -1 instead) from the comparator (ie. write your own comparator that doesn't include an equals option):
Thing t = items.stream()
.max((a, b) -> a.getI() > b.getI() ? 1 : -1)
.orElse(null);
Remove the equals option (don't return 0 if the compared numbers are equal, return -1 instead) from the comparator (ie. write your own comparator that doesn't include an equals option):
Thing t = items.stream()
.max((a, b) -> a.getI() > b.getI() ? 1 : -1)
.orElse(null);
edited Feb 6 at 9:25
answered Feb 5 at 13:58
jvdmrjvdmr
52128
52128
2
I'm not sure that java guarantees the comparator is called "in order", i.e. the later items always is the second argument (thinking about parallel execution). That being said, it makes sense to place a little bit of trust in this implementation detail.
– WorldSEnder
Feb 5 at 17:16
1
I've accepted this because it's the most elegant solution and, as far as I can tell, as efficient as a Comparator (which is what I wanted). If I don't want to compute the value every time I have to settle for a for each or a tuple. Thank you.
– Druckles
Feb 5 at 17:39
2
I do believe this is not guaranteed to work as you expect the moment that the stream goes parallel/asynch.
– ErikE
Feb 5 at 19:16
1
What does this have to do withequals
? The only trick seems to be to invert the tie-breaking from "a unless b is greater" to "b unless a is greater", which is exactly the same as OP'sreduce
did, but IMHO less readable, as you return+/-1
instead of the actual object to retain. Also, this, too, will callgetI
more often than necessary (just as often asreduce
).
– tobias_k
Feb 5 at 22:41
@tobias_k a standard comparator returns +1, 0 or -1, for a being greater than, equal, or less than b. I say 'removing the equals option' meaning my comparator only return +1 or -1, for greater than or not greater than.
– jvdmr
Feb 6 at 8:11
|
show 3 more comments
2
I'm not sure that java guarantees the comparator is called "in order", i.e. the later items always is the second argument (thinking about parallel execution). That being said, it makes sense to place a little bit of trust in this implementation detail.
– WorldSEnder
Feb 5 at 17:16
1
I've accepted this because it's the most elegant solution and, as far as I can tell, as efficient as a Comparator (which is what I wanted). If I don't want to compute the value every time I have to settle for a for each or a tuple. Thank you.
– Druckles
Feb 5 at 17:39
2
I do believe this is not guaranteed to work as you expect the moment that the stream goes parallel/asynch.
– ErikE
Feb 5 at 19:16
1
What does this have to do withequals
? The only trick seems to be to invert the tie-breaking from "a unless b is greater" to "b unless a is greater", which is exactly the same as OP'sreduce
did, but IMHO less readable, as you return+/-1
instead of the actual object to retain. Also, this, too, will callgetI
more often than necessary (just as often asreduce
).
– tobias_k
Feb 5 at 22:41
@tobias_k a standard comparator returns +1, 0 or -1, for a being greater than, equal, or less than b. I say 'removing the equals option' meaning my comparator only return +1 or -1, for greater than or not greater than.
– jvdmr
Feb 6 at 8:11
2
2
I'm not sure that java guarantees the comparator is called "in order", i.e. the later items always is the second argument (thinking about parallel execution). That being said, it makes sense to place a little bit of trust in this implementation detail.
– WorldSEnder
Feb 5 at 17:16
I'm not sure that java guarantees the comparator is called "in order", i.e. the later items always is the second argument (thinking about parallel execution). That being said, it makes sense to place a little bit of trust in this implementation detail.
– WorldSEnder
Feb 5 at 17:16
1
1
I've accepted this because it's the most elegant solution and, as far as I can tell, as efficient as a Comparator (which is what I wanted). If I don't want to compute the value every time I have to settle for a for each or a tuple. Thank you.
– Druckles
Feb 5 at 17:39
I've accepted this because it's the most elegant solution and, as far as I can tell, as efficient as a Comparator (which is what I wanted). If I don't want to compute the value every time I have to settle for a for each or a tuple. Thank you.
– Druckles
Feb 5 at 17:39
2
2
I do believe this is not guaranteed to work as you expect the moment that the stream goes parallel/asynch.
– ErikE
Feb 5 at 19:16
I do believe this is not guaranteed to work as you expect the moment that the stream goes parallel/asynch.
– ErikE
Feb 5 at 19:16
1
1
What does this have to do with
equals
? The only trick seems to be to invert the tie-breaking from "a unless b is greater" to "b unless a is greater", which is exactly the same as OP's reduce
did, but IMHO less readable, as you return +/-1
instead of the actual object to retain. Also, this, too, will call getI
more often than necessary (just as often as reduce
).– tobias_k
Feb 5 at 22:41
What does this have to do with
equals
? The only trick seems to be to invert the tie-breaking from "a unless b is greater" to "b unless a is greater", which is exactly the same as OP's reduce
did, but IMHO less readable, as you return +/-1
instead of the actual object to retain. Also, this, too, will call getI
more often than necessary (just as often as reduce
).– tobias_k
Feb 5 at 22:41
@tobias_k a standard comparator returns +1, 0 or -1, for a being greater than, equal, or less than b. I say 'removing the equals option' meaning my comparator only return +1 or -1, for greater than or not greater than.
– jvdmr
Feb 6 at 8:11
@tobias_k a standard comparator returns +1, 0 or -1, for a being greater than, equal, or less than b. I say 'removing the equals option' meaning my comparator only return +1 or -1, for greater than or not greater than.
– jvdmr
Feb 6 at 8:11
|
show 3 more comments
Conceptually, you seem to be possibly looking for something like thenComparing
using the index
of the elements in the list:
Thing t = items.stream()
.max(Comparator.comparingLong(Thing::getI).thenComparing(items::indexOf))
.orElse(null);
1
Assuming: that for two objects if attributeI
is equal that doesn't mean the objects are equal as well. In which case theindexOf
would return same value always.
– nullpointer
Feb 5 at 14:12
1
This is nice, but the problem I am trying to solve was actually originally caused by theequals
method returning true for different objects ;-)
– Druckles
Feb 5 at 15:08
2
You're right. Unfortunately, I didn't write theequals
.
– Druckles
Feb 5 at 15:17
7
Doesn't this give the whole thing O(n²) complexity? Also, would not work for a pure stream, with not backing list (not a requirement, but anyway). Something like Python'senumerate
would come in handy here.
– tobias_k
Feb 5 at 15:52
1
@Marco13 in the worst case, all elements are "new maximum" values in respect to the previous elements, i.e. when the input elements happen to be in ascending order. So O(n²) is the correct time complexity of this solution.
– Holger
Feb 7 at 11:55
|
show 2 more comments
Conceptually, you seem to be possibly looking for something like thenComparing
using the index
of the elements in the list:
Thing t = items.stream()
.max(Comparator.comparingLong(Thing::getI).thenComparing(items::indexOf))
.orElse(null);
1
Assuming: that for two objects if attributeI
is equal that doesn't mean the objects are equal as well. In which case theindexOf
would return same value always.
– nullpointer
Feb 5 at 14:12
1
This is nice, but the problem I am trying to solve was actually originally caused by theequals
method returning true for different objects ;-)
– Druckles
Feb 5 at 15:08
2
You're right. Unfortunately, I didn't write theequals
.
– Druckles
Feb 5 at 15:17
7
Doesn't this give the whole thing O(n²) complexity? Also, would not work for a pure stream, with not backing list (not a requirement, but anyway). Something like Python'senumerate
would come in handy here.
– tobias_k
Feb 5 at 15:52
1
@Marco13 in the worst case, all elements are "new maximum" values in respect to the previous elements, i.e. when the input elements happen to be in ascending order. So O(n²) is the correct time complexity of this solution.
– Holger
Feb 7 at 11:55
|
show 2 more comments
Conceptually, you seem to be possibly looking for something like thenComparing
using the index
of the elements in the list:
Thing t = items.stream()
.max(Comparator.comparingLong(Thing::getI).thenComparing(items::indexOf))
.orElse(null);
Conceptually, you seem to be possibly looking for something like thenComparing
using the index
of the elements in the list:
Thing t = items.stream()
.max(Comparator.comparingLong(Thing::getI).thenComparing(items::indexOf))
.orElse(null);
edited Feb 5 at 14:32
answered Feb 5 at 14:09
nullpointernullpointer
43.2k10101200
43.2k10101200
1
Assuming: that for two objects if attributeI
is equal that doesn't mean the objects are equal as well. In which case theindexOf
would return same value always.
– nullpointer
Feb 5 at 14:12
1
This is nice, but the problem I am trying to solve was actually originally caused by theequals
method returning true for different objects ;-)
– Druckles
Feb 5 at 15:08
2
You're right. Unfortunately, I didn't write theequals
.
– Druckles
Feb 5 at 15:17
7
Doesn't this give the whole thing O(n²) complexity? Also, would not work for a pure stream, with not backing list (not a requirement, but anyway). Something like Python'senumerate
would come in handy here.
– tobias_k
Feb 5 at 15:52
1
@Marco13 in the worst case, all elements are "new maximum" values in respect to the previous elements, i.e. when the input elements happen to be in ascending order. So O(n²) is the correct time complexity of this solution.
– Holger
Feb 7 at 11:55
|
show 2 more comments
1
Assuming: that for two objects if attributeI
is equal that doesn't mean the objects are equal as well. In which case theindexOf
would return same value always.
– nullpointer
Feb 5 at 14:12
1
This is nice, but the problem I am trying to solve was actually originally caused by theequals
method returning true for different objects ;-)
– Druckles
Feb 5 at 15:08
2
You're right. Unfortunately, I didn't write theequals
.
– Druckles
Feb 5 at 15:17
7
Doesn't this give the whole thing O(n²) complexity? Also, would not work for a pure stream, with not backing list (not a requirement, but anyway). Something like Python'senumerate
would come in handy here.
– tobias_k
Feb 5 at 15:52
1
@Marco13 in the worst case, all elements are "new maximum" values in respect to the previous elements, i.e. when the input elements happen to be in ascending order. So O(n²) is the correct time complexity of this solution.
– Holger
Feb 7 at 11:55
1
1
Assuming: that for two objects if attribute
I
is equal that doesn't mean the objects are equal as well. In which case the indexOf
would return same value always.– nullpointer
Feb 5 at 14:12
Assuming: that for two objects if attribute
I
is equal that doesn't mean the objects are equal as well. In which case the indexOf
would return same value always.– nullpointer
Feb 5 at 14:12
1
1
This is nice, but the problem I am trying to solve was actually originally caused by the
equals
method returning true for different objects ;-)– Druckles
Feb 5 at 15:08
This is nice, but the problem I am trying to solve was actually originally caused by the
equals
method returning true for different objects ;-)– Druckles
Feb 5 at 15:08
2
2
You're right. Unfortunately, I didn't write the
equals
.– Druckles
Feb 5 at 15:17
You're right. Unfortunately, I didn't write the
equals
.– Druckles
Feb 5 at 15:17
7
7
Doesn't this give the whole thing O(n²) complexity? Also, would not work for a pure stream, with not backing list (not a requirement, but anyway). Something like Python's
enumerate
would come in handy here.– tobias_k
Feb 5 at 15:52
Doesn't this give the whole thing O(n²) complexity? Also, would not work for a pure stream, with not backing list (not a requirement, but anyway). Something like Python's
enumerate
would come in handy here.– tobias_k
Feb 5 at 15:52
1
1
@Marco13 in the worst case, all elements are "new maximum" values in respect to the previous elements, i.e. when the input elements happen to be in ascending order. So O(n²) is the correct time complexity of this solution.
– Holger
Feb 7 at 11:55
@Marco13 in the worst case, all elements are "new maximum" values in respect to the previous elements, i.e. when the input elements happen to be in ascending order. So O(n²) is the correct time complexity of this solution.
– Holger
Feb 7 at 11:55
|
show 2 more comments
To avoid the multiple applications of valueFunction in your reduce solution, simply explicitly calculate the result and put it in a tuple:
Item lastMax = items.stream()
.map(item -> new AbstractMap.SimpleEntry<Item, Long>(item, valueFunction.apply(item)))
.reduce((l, r) -> l.getValue() > r.getValue() ? l : r )
.map(Map.Entry::getKey)
.orElse(null);
add a comment |
To avoid the multiple applications of valueFunction in your reduce solution, simply explicitly calculate the result and put it in a tuple:
Item lastMax = items.stream()
.map(item -> new AbstractMap.SimpleEntry<Item, Long>(item, valueFunction.apply(item)))
.reduce((l, r) -> l.getValue() > r.getValue() ? l : r )
.map(Map.Entry::getKey)
.orElse(null);
add a comment |
To avoid the multiple applications of valueFunction in your reduce solution, simply explicitly calculate the result and put it in a tuple:
Item lastMax = items.stream()
.map(item -> new AbstractMap.SimpleEntry<Item, Long>(item, valueFunction.apply(item)))
.reduce((l, r) -> l.getValue() > r.getValue() ? l : r )
.map(Map.Entry::getKey)
.orElse(null);
To avoid the multiple applications of valueFunction in your reduce solution, simply explicitly calculate the result and put it in a tuple:
Item lastMax = items.stream()
.map(item -> new AbstractMap.SimpleEntry<Item, Long>(item, valueFunction.apply(item)))
.reduce((l, r) -> l.getValue() > r.getValue() ? l : r )
.map(Map.Entry::getKey)
.orElse(null);
answered Feb 5 at 14:53
MikeFHayMikeFHay
3,14732035
3,14732035
add a comment |
add a comment |
Stream is not necessary bad if you do things in two steps :
1) Find the i
value that has more occurrences in the Iterable
(as you did)
2) Search the last element for this i
value by starting from the end of items:
Thing t =
items.stream()
.max(Comparator.comparingLong(Thing::getI))
.mapping(firstMaxThing ->
return
IntStream.rangeClosed(1, items.size())
.mapToObj(i -> items.get(items.size()-i))
.filter(item -> item.getI() == firstMaxThing.getI())
.findFirst().get();
// here get() cannot fail as *max()* returned something.
)
.orElse(null)
add a comment |
Stream is not necessary bad if you do things in two steps :
1) Find the i
value that has more occurrences in the Iterable
(as you did)
2) Search the last element for this i
value by starting from the end of items:
Thing t =
items.stream()
.max(Comparator.comparingLong(Thing::getI))
.mapping(firstMaxThing ->
return
IntStream.rangeClosed(1, items.size())
.mapToObj(i -> items.get(items.size()-i))
.filter(item -> item.getI() == firstMaxThing.getI())
.findFirst().get();
// here get() cannot fail as *max()* returned something.
)
.orElse(null)
add a comment |
Stream is not necessary bad if you do things in two steps :
1) Find the i
value that has more occurrences in the Iterable
(as you did)
2) Search the last element for this i
value by starting from the end of items:
Thing t =
items.stream()
.max(Comparator.comparingLong(Thing::getI))
.mapping(firstMaxThing ->
return
IntStream.rangeClosed(1, items.size())
.mapToObj(i -> items.get(items.size()-i))
.filter(item -> item.getI() == firstMaxThing.getI())
.findFirst().get();
// here get() cannot fail as *max()* returned something.
)
.orElse(null)
Stream is not necessary bad if you do things in two steps :
1) Find the i
value that has more occurrences in the Iterable
(as you did)
2) Search the last element for this i
value by starting from the end of items:
Thing t =
items.stream()
.max(Comparator.comparingLong(Thing::getI))
.mapping(firstMaxThing ->
return
IntStream.rangeClosed(1, items.size())
.mapToObj(i -> items.get(items.size()-i))
.filter(item -> item.getI() == firstMaxThing.getI())
.findFirst().get();
// here get() cannot fail as *max()* returned something.
)
.orElse(null)
edited Feb 5 at 15:14
answered Feb 5 at 15:00
davidxxxdavidxxx
67.1k67095
67.1k67095
add a comment |
add a comment |
The valueFunction now needs to be called nearly twice as often.
Note that even when using max
, the getI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D, and for longer lists, too, it seems to be called on average twice per element.
How about you just cache the calculated value directly in the Thing
instance? If this is not possible, you could use an external Map
and use calculateIfAbsent
to calculate the value only once for each Thing
and then use your approach using reduce
.
Map<Thing, Long> cache = new HashMap<>();
Thing x = items.stream()
.reduce((left, right) ->
long leftValue = cache.computeIfAbsent(left, Thing::getI);
long rightValue = cache.computeIfAbsent(right, Thing::getI);
return leftValue > rightValue ? left : right;
)
.orElse(null);
Or a bit cleaner, calculating all the values beforehand:
Map<Thing, Long> cache = items.stream()
.collect(Collectors.toMap(x -> x, Thing::getI));
Thing x = items.stream()
.reduce((left, right) -> cache.get(left) > cache.get(right) ? left : right)
.orElse(null);
1
BTW, you can easily check how often the comparator callback is called by adding some print statement or incrementing a counter ingetI
or in the callback itself.
– tobias_k
Feb 5 at 22:45
add a comment |
The valueFunction now needs to be called nearly twice as often.
Note that even when using max
, the getI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D, and for longer lists, too, it seems to be called on average twice per element.
How about you just cache the calculated value directly in the Thing
instance? If this is not possible, you could use an external Map
and use calculateIfAbsent
to calculate the value only once for each Thing
and then use your approach using reduce
.
Map<Thing, Long> cache = new HashMap<>();
Thing x = items.stream()
.reduce((left, right) ->
long leftValue = cache.computeIfAbsent(left, Thing::getI);
long rightValue = cache.computeIfAbsent(right, Thing::getI);
return leftValue > rightValue ? left : right;
)
.orElse(null);
Or a bit cleaner, calculating all the values beforehand:
Map<Thing, Long> cache = items.stream()
.collect(Collectors.toMap(x -> x, Thing::getI));
Thing x = items.stream()
.reduce((left, right) -> cache.get(left) > cache.get(right) ? left : right)
.orElse(null);
1
BTW, you can easily check how often the comparator callback is called by adding some print statement or incrementing a counter ingetI
or in the callback itself.
– tobias_k
Feb 5 at 22:45
add a comment |
The valueFunction now needs to be called nearly twice as often.
Note that even when using max
, the getI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D, and for longer lists, too, it seems to be called on average twice per element.
How about you just cache the calculated value directly in the Thing
instance? If this is not possible, you could use an external Map
and use calculateIfAbsent
to calculate the value only once for each Thing
and then use your approach using reduce
.
Map<Thing, Long> cache = new HashMap<>();
Thing x = items.stream()
.reduce((left, right) ->
long leftValue = cache.computeIfAbsent(left, Thing::getI);
long rightValue = cache.computeIfAbsent(right, Thing::getI);
return leftValue > rightValue ? left : right;
)
.orElse(null);
Or a bit cleaner, calculating all the values beforehand:
Map<Thing, Long> cache = items.stream()
.collect(Collectors.toMap(x -> x, Thing::getI));
Thing x = items.stream()
.reduce((left, right) -> cache.get(left) > cache.get(right) ? left : right)
.orElse(null);
The valueFunction now needs to be called nearly twice as often.
Note that even when using max
, the getI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D, and for longer lists, too, it seems to be called on average twice per element.
How about you just cache the calculated value directly in the Thing
instance? If this is not possible, you could use an external Map
and use calculateIfAbsent
to calculate the value only once for each Thing
and then use your approach using reduce
.
Map<Thing, Long> cache = new HashMap<>();
Thing x = items.stream()
.reduce((left, right) ->
long leftValue = cache.computeIfAbsent(left, Thing::getI);
long rightValue = cache.computeIfAbsent(right, Thing::getI);
return leftValue > rightValue ? left : right;
)
.orElse(null);
Or a bit cleaner, calculating all the values beforehand:
Map<Thing, Long> cache = items.stream()
.collect(Collectors.toMap(x -> x, Thing::getI));
Thing x = items.stream()
.reduce((left, right) -> cache.get(left) > cache.get(right) ? left : right)
.orElse(null);
answered Feb 5 at 16:40
tobias_ktobias_k
58.7k969107
58.7k969107
1
BTW, you can easily check how often the comparator callback is called by adding some print statement or incrementing a counter ingetI
or in the callback itself.
– tobias_k
Feb 5 at 22:45
add a comment |
1
BTW, you can easily check how often the comparator callback is called by adding some print statement or incrementing a counter ingetI
or in the callback itself.
– tobias_k
Feb 5 at 22:45
1
1
BTW, you can easily check how often the comparator callback is called by adding some print statement or incrementing a counter in
getI
or in the callback itself.– tobias_k
Feb 5 at 22:45
BTW, you can easily check how often the comparator callback is called by adding some print statement or incrementing a counter in
getI
or in the callback itself.– tobias_k
Feb 5 at 22:45
add a comment |
You can still use the reduction to get this thing done. If t1 is larger, then only it will keep t1. In all the other cases it will keep t2. If either t2 is larger or t1 and t2 are the same, then it will eventually return t2 adhering to your requirement.
Thing t = items.stream().
reduce((t1, t2) -> t1.getI() > t2.getI() ? t1 : t2)
.orElse(null);
This is the same solution as I presented in my question.
– Druckles
Feb 5 at 17:37
1
@Druckles Technically, the answer you accepted is also the same as your solution, just less obviously so.
– tobias_k
Feb 5 at 22:43
@tobias_k "Less obvious" is everything in programming.
– Druckles
Feb 6 at 8:53
1
@Druckles Am I getting you correctly? Your programming goal is to write as obfuscated code as possible?
– Holger
Feb 7 at 11:53
1
@Druckles I don't see any way, how to interpret your sentence "'Less obvious' is everything in programming" in a different way than that you like non-obvious things, like the accepted answer, which only makes it less obvious that it still does the same as the question's code, including having the same problems, plus adding another one, using a formally incorrect comparator, which makes it less obvious why it still happens to do the desired thing. "less obvious" is obfuscation. Actually, "is everything" does not only imply that you like it but that it is the most important thing to you.
– Holger
Feb 7 at 13:19
|
show 3 more comments
You can still use the reduction to get this thing done. If t1 is larger, then only it will keep t1. In all the other cases it will keep t2. If either t2 is larger or t1 and t2 are the same, then it will eventually return t2 adhering to your requirement.
Thing t = items.stream().
reduce((t1, t2) -> t1.getI() > t2.getI() ? t1 : t2)
.orElse(null);
This is the same solution as I presented in my question.
– Druckles
Feb 5 at 17:37
1
@Druckles Technically, the answer you accepted is also the same as your solution, just less obviously so.
– tobias_k
Feb 5 at 22:43
@tobias_k "Less obvious" is everything in programming.
– Druckles
Feb 6 at 8:53
1
@Druckles Am I getting you correctly? Your programming goal is to write as obfuscated code as possible?
– Holger
Feb 7 at 11:53
1
@Druckles I don't see any way, how to interpret your sentence "'Less obvious' is everything in programming" in a different way than that you like non-obvious things, like the accepted answer, which only makes it less obvious that it still does the same as the question's code, including having the same problems, plus adding another one, using a formally incorrect comparator, which makes it less obvious why it still happens to do the desired thing. "less obvious" is obfuscation. Actually, "is everything" does not only imply that you like it but that it is the most important thing to you.
– Holger
Feb 7 at 13:19
|
show 3 more comments
You can still use the reduction to get this thing done. If t1 is larger, then only it will keep t1. In all the other cases it will keep t2. If either t2 is larger or t1 and t2 are the same, then it will eventually return t2 adhering to your requirement.
Thing t = items.stream().
reduce((t1, t2) -> t1.getI() > t2.getI() ? t1 : t2)
.orElse(null);
You can still use the reduction to get this thing done. If t1 is larger, then only it will keep t1. In all the other cases it will keep t2. If either t2 is larger or t1 and t2 are the same, then it will eventually return t2 adhering to your requirement.
Thing t = items.stream().
reduce((t1, t2) -> t1.getI() > t2.getI() ? t1 : t2)
.orElse(null);
edited Feb 5 at 14:30
answered Feb 5 at 13:39
Ravindra RanwalaRavindra Ranwala
10.6k42040
10.6k42040
This is the same solution as I presented in my question.
– Druckles
Feb 5 at 17:37
1
@Druckles Technically, the answer you accepted is also the same as your solution, just less obviously so.
– tobias_k
Feb 5 at 22:43
@tobias_k "Less obvious" is everything in programming.
– Druckles
Feb 6 at 8:53
1
@Druckles Am I getting you correctly? Your programming goal is to write as obfuscated code as possible?
– Holger
Feb 7 at 11:53
1
@Druckles I don't see any way, how to interpret your sentence "'Less obvious' is everything in programming" in a different way than that you like non-obvious things, like the accepted answer, which only makes it less obvious that it still does the same as the question's code, including having the same problems, plus adding another one, using a formally incorrect comparator, which makes it less obvious why it still happens to do the desired thing. "less obvious" is obfuscation. Actually, "is everything" does not only imply that you like it but that it is the most important thing to you.
– Holger
Feb 7 at 13:19
|
show 3 more comments
This is the same solution as I presented in my question.
– Druckles
Feb 5 at 17:37
1
@Druckles Technically, the answer you accepted is also the same as your solution, just less obviously so.
– tobias_k
Feb 5 at 22:43
@tobias_k "Less obvious" is everything in programming.
– Druckles
Feb 6 at 8:53
1
@Druckles Am I getting you correctly? Your programming goal is to write as obfuscated code as possible?
– Holger
Feb 7 at 11:53
1
@Druckles I don't see any way, how to interpret your sentence "'Less obvious' is everything in programming" in a different way than that you like non-obvious things, like the accepted answer, which only makes it less obvious that it still does the same as the question's code, including having the same problems, plus adding another one, using a formally incorrect comparator, which makes it less obvious why it still happens to do the desired thing. "less obvious" is obfuscation. Actually, "is everything" does not only imply that you like it but that it is the most important thing to you.
– Holger
Feb 7 at 13:19
This is the same solution as I presented in my question.
– Druckles
Feb 5 at 17:37
This is the same solution as I presented in my question.
– Druckles
Feb 5 at 17:37
1
1
@Druckles Technically, the answer you accepted is also the same as your solution, just less obviously so.
– tobias_k
Feb 5 at 22:43
@Druckles Technically, the answer you accepted is also the same as your solution, just less obviously so.
– tobias_k
Feb 5 at 22:43
@tobias_k "Less obvious" is everything in programming.
– Druckles
Feb 6 at 8:53
@tobias_k "Less obvious" is everything in programming.
– Druckles
Feb 6 at 8:53
1
1
@Druckles Am I getting you correctly? Your programming goal is to write as obfuscated code as possible?
– Holger
Feb 7 at 11:53
@Druckles Am I getting you correctly? Your programming goal is to write as obfuscated code as possible?
– Holger
Feb 7 at 11:53
1
1
@Druckles I don't see any way, how to interpret your sentence "'Less obvious' is everything in programming" in a different way than that you like non-obvious things, like the accepted answer, which only makes it less obvious that it still does the same as the question's code, including having the same problems, plus adding another one, using a formally incorrect comparator, which makes it less obvious why it still happens to do the desired thing. "less obvious" is obfuscation. Actually, "is everything" does not only imply that you like it but that it is the most important thing to you.
– Holger
Feb 7 at 13:19
@Druckles I don't see any way, how to interpret your sentence "'Less obvious' is everything in programming" in a different way than that you like non-obvious things, like the accepted answer, which only makes it less obvious that it still does the same as the question's code, including having the same problems, plus adding another one, using a formally incorrect comparator, which makes it less obvious why it still happens to do the desired thing. "less obvious" is obfuscation. Actually, "is everything" does not only imply that you like it but that it is the most important thing to you.
– Holger
Feb 7 at 13:19
|
show 3 more comments
Your current implementation using reduce
looks good, unless your value-extractor function is costly.
Considering later you may want to reuse the logic for different object types & fields, I would extract the logic itself in separate generic method/methods:
public static <T, K, V> Function<T, Map.Entry<K, V>> toEntry(Function<T, K> keyFunc, Function<T, V> valueFunc)
return t -> new AbstractMap.SimpleEntry<>(keyFunc.apply(t), valueFunc.apply(t));
public static <ITEM, FIELD extends Comparable<FIELD>> Optional<ITEM> maxBy(Function<ITEM, FIELD> extractor, Collection<ITEM> items)
return items.stream()
.map(toEntry(identity(), extractor))
.max(comparing(Map.Entry::getValue))
.map(Map.Entry::getKey);
The code snippets above can be used like this:
Thing maxThing = maxBy(Thing::getField, things).orElse(null);
AnotherThing maxAnotherThing = maxBy(AnotherThing::getAnotherField, anotherThings).orElse(null);
add a comment |
Your current implementation using reduce
looks good, unless your value-extractor function is costly.
Considering later you may want to reuse the logic for different object types & fields, I would extract the logic itself in separate generic method/methods:
public static <T, K, V> Function<T, Map.Entry<K, V>> toEntry(Function<T, K> keyFunc, Function<T, V> valueFunc)
return t -> new AbstractMap.SimpleEntry<>(keyFunc.apply(t), valueFunc.apply(t));
public static <ITEM, FIELD extends Comparable<FIELD>> Optional<ITEM> maxBy(Function<ITEM, FIELD> extractor, Collection<ITEM> items)
return items.stream()
.map(toEntry(identity(), extractor))
.max(comparing(Map.Entry::getValue))
.map(Map.Entry::getKey);
The code snippets above can be used like this:
Thing maxThing = maxBy(Thing::getField, things).orElse(null);
AnotherThing maxAnotherThing = maxBy(AnotherThing::getAnotherField, anotherThings).orElse(null);
add a comment |
Your current implementation using reduce
looks good, unless your value-extractor function is costly.
Considering later you may want to reuse the logic for different object types & fields, I would extract the logic itself in separate generic method/methods:
public static <T, K, V> Function<T, Map.Entry<K, V>> toEntry(Function<T, K> keyFunc, Function<T, V> valueFunc)
return t -> new AbstractMap.SimpleEntry<>(keyFunc.apply(t), valueFunc.apply(t));
public static <ITEM, FIELD extends Comparable<FIELD>> Optional<ITEM> maxBy(Function<ITEM, FIELD> extractor, Collection<ITEM> items)
return items.stream()
.map(toEntry(identity(), extractor))
.max(comparing(Map.Entry::getValue))
.map(Map.Entry::getKey);
The code snippets above can be used like this:
Thing maxThing = maxBy(Thing::getField, things).orElse(null);
AnotherThing maxAnotherThing = maxBy(AnotherThing::getAnotherField, anotherThings).orElse(null);
Your current implementation using reduce
looks good, unless your value-extractor function is costly.
Considering later you may want to reuse the logic for different object types & fields, I would extract the logic itself in separate generic method/methods:
public static <T, K, V> Function<T, Map.Entry<K, V>> toEntry(Function<T, K> keyFunc, Function<T, V> valueFunc)
return t -> new AbstractMap.SimpleEntry<>(keyFunc.apply(t), valueFunc.apply(t));
public static <ITEM, FIELD extends Comparable<FIELD>> Optional<ITEM> maxBy(Function<ITEM, FIELD> extractor, Collection<ITEM> items)
return items.stream()
.map(toEntry(identity(), extractor))
.max(comparing(Map.Entry::getValue))
.map(Map.Entry::getKey);
The code snippets above can be used like this:
Thing maxThing = maxBy(Thing::getField, things).orElse(null);
AnotherThing maxAnotherThing = maxBy(AnotherThing::getAnotherField, anotherThings).orElse(null);
edited Feb 5 at 17:01
answered Feb 5 at 14:30
ETOETO
2,6281628
2,6281628
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54535632%2fusing-java-8-stream-methods-to-get-the-last-max-value%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
"The valueFunction now needs to be called nearly twice as often." Note that even when using
max
, thegetI
method will be called again and again for every comparison, not just once per element. In your example, it's called 11 times, including 6 times for D. How about you just cache the calculated value directly in the Thing instance?– tobias_k
Feb 5 at 16:14
@tobias_k I realised that after posting my question. My practical solution was to use a standard for each.
– Druckles
Feb 5 at 17:35
2
If your stream goes parallel or async, there can be a complete loss of the idea of "in order". Be sure to think about this and consider if your stream can contain a field or value indicating the order, similar to a tuple of (value, orderIndex) so that the max operation is a simple, parallelizable comparison of the value and orderIndex fields.
– ErikE
Feb 5 at 19:19
@ErikE if the stream has a defined encounter order, the
reduce
based solution will work in parallel, as long as the reduction function fulfills the associativity constraint. Which is the case here.– Holger
Feb 7 at 12:04