I'm learning Java lambdas for school, and I am stuck for a couple of days now.
Background
I have a list of pumps which I have to sort out on power, last revision, …
I already wrote a Comparator that's returning a List<Pump>:
class PowerComparator implements Comparator<Pomp> {
#Override
public int compare(Pump pump1 , Pump pump2) {
return Double.compare(pump1.getPower(), pump2.getPower());
}
}
I have to write one function that's returning a List<Pump> that can returning a sorted list (power, revision, ...) using a lambda.
The method signature is:
public List<Pump> sortedBy(Function<Pump, Comparable<Pump>> function)
I know that the Function interface is returning a Comparator, but I don't know how to use the function in it.
Below is what I already found (it's not correct). I am really stuck here.
public List<Pump> sortedBy(Function<Pump, Comparable<Pump>> function){
List<Pump> sortBy = new ArrayList<Pump>(pumps);
function.apply((Pump) ->Comparator.comparing(pumps::comparator));
Collections.sort(sortBy, Comparator.comparing(function.apply(pumps);
return sortBy;
}
Additional info (in dutch)
public class Data {
private static List<Pomp> data;
public static List<Pomp> getData() {
data = new ArrayList<>();
data.add(new Pomp("J6706A", 100.0, 2, Aandrijving.TURBINE, LocalDate.of(2022, 1, 10), true, 500.0, "Slurry pomp"));
data.add(new Pomp("J6707A", 55.5, 1, Aandrijving.MOTOR, LocalDate.of(2022, 2, 10), false, 500.0, "Clarified pomp"));
data.add(new Pomp("J6706B", 100.0, 2, Aandrijving.TURBINE, LocalDate.of(2022, 3, 10), true, 500.0, "Slurry pomp"));
data.add(new Pomp("J6706C", 100.0, 2, Aandrijving.TURBINE, LocalDate.of(2022, 4, 10), true, 500.0, "Slurry pomp"));
data.add(new Pomp("J6705A", 62, 1, Aandrijving.MOTOR, LocalDate.of(2022, 5, 10), false, 250, "Voedings pomp"));
data.add(new Pomp("J6705B", 35, 2, Aandrijving.TURBINE, LocalDate.of(2022, 6, 10), false, 150, "Voedings pomp"));
data.add(new Pomp("J6708B", 100.0, 2, Aandrijving.TURBINE, LocalDate.of(2022, 7, 10), false, 300, "HCO circ pomp"));
return data;
}
}
public class Pompen {
private TreeSet<Pomp> pompen = new TreeSet<>();
public void add(Pomp pomp) {
pompen.add(pomp);
}
class VermogenComparator implements Comparator<Pomp> {
#Override
public int compare(Pomp pomp1 , Pomp pomp2) {
return Double.compare(pomp1.getVermogen(), pomp2.getVermogen());
}
}
class RevisieComparator implements Comparator<Pomp> {
#Override
public int compare(Pomp pomp1 , Pomp pomp2) {
return pomp1.getLaatsteRevisie().compareTo(pomp2.getLaatsteRevisie());
}
}
class Zelfontbranding implements Comparator<Pomp> {
#Override
public int compare(Pomp pomp1 , Pomp pomp2) {
return Boolean.compare(pomp1.getBovenZelfOntbranding(), pomp2.getBovenZelfOntbranding());
}
}
Because of your example using sortBy(Pump::getName), I believe that the intent here is to use the Comparator.comparing() factory to create a comparator that extracts a sort key from each object. However, this requires some changes to the generic types used in the prescribed method signature. Working code would look something like this:
public <U extends Comparable<? super U>> List<Pomp> sortedBy(Function<Pomp, ? extends U> toKey) {
List<Pomp> sorted = new ArrayList<>(pompen);
sorted.sort(Comparator.comparing(toKey));
return sorted;
}
This will accept lambdas like Pomp::getNaam as long as the indicated property is Comparable:
System.out.println("Pumps sorted on power:");
pompen.sortedBy(Pomp::getVermogen).forEach(System.out::println);
A better design would be to pass a Comparator; while it's a tiny bit more work for the caller, it gives them full control over the sorting. For example, they can specify a secondary sort key, or reverse the order. Or one could go another step further and simply return a copy of the pumps collection as a list and let the caller do whatever they wish with it.
If permitted, you could change the API to this:
public List<Pomp> sortedBy(Comparator<? super Pomp> order) {
List<Pomp> sorted = new ArrayList<>(pompen);
sorted.sort(order);
return sorted;
}
The caller would then be responsible for creating a Comparator that meets their need:
/* Like this: */
List<Pomp> sortedByName = pompen.sortedBy(Comparator.comparing(Pomp::getNaam));
/* Or this: */
List<Pomp> pumpsDescendingPower =
pompen.sortedBy(Comparator.comparing(Pomp::getVermogen).reversed());
This approach is more idiomatic for Java.
Related
I am trying to do the following with a Stream<BigDecimal> using Java 8 but am stuck at step 2.
Remove null and negative values.
Create groups with a size of 3 elements. Retain groups with an average of less than 30, otherwise discard.
Example. Let's assume the following:
stream<Bigdecimal> input = {4,5,61,3,9,3,1,null,-4,7,2,-8,6,-3,null}; //technically its incorrect but just assume.
I was able to solve step 1 as below:
Stream<BigDecimal> newInList = input.filter(bd -> (bd != null && bd.signum() > 0));
I'm not able to do the step 2 - create groups of 3 elements.
The expected result for step2: {4,5,6},{61,3,9},{3,1,7}.
I'm looking for a solution with Java 8 streams.
So you need to extract groups with the size of 3 elements from the stream in accordance with their order.
It can be done using Stream API by implementing a custom collector that implements the Collector interface.
While initializing the GroupCollector size of the group has to be provided (it's was done to make the collector more flexible and avoid hard-coding the value of 3 inside the class).
Deque<List<T>> is used as a mutable container because the Deque interface provides convenient access to the last element.
combiner() method provides the logic of how to combine results of the execution obtained by different threads. Parallel stream provides a guarantee for the collect() operation that the initial order of the stream will be preserved and results from the different threads will be joined with respect to the order they were assigned with their tasks. Therefore this solution can be parallelized.
The logic of combining the two queues produced by different treads entails the following concerns:
make sure that all groups (except for one that should be the last) have exactly 3 elements. Therefore we can't simply add all the contents of the second deque to the first deque. Instead, every group of the second deque has to be processed one by one.
lists that are already created should be reused.
finisher() function will discard the last list in the deque if its size is less than the groupSize (requirement provided by the PO in the comment).
As an example, I've used the sequence of numbers from the question.
public static void main(String[] args) {
Stream<BigDecimal> source =
IntStream.of(4, 5, 6, 61, 3, 9, 3, 1, 7, 2, 6)
.mapToObj(BigDecimal::valueOf);
System.out.println(createGroups(source)
.flatMap(List::stream)
.collect(Collectors.toList())); // collecting to list for demonstration purposes
}
Method createGroups()
public static Stream<List<BigDecimal>> createGroups(Stream<BigDecimal> source) {
return source
.collect(new GroupCollector<BigDecimal>(3))
.stream()
.filter(list -> averageIsLessThen(list, 30));
}
Collector
public class GroupCollector<T> implements Collector<T, Deque<List<T>>, Deque<List<T>>> {
private final int groupSize;
public GroupCollector(int groupSize) {
this.groupSize = groupSize;
}
#Override
public Supplier<Deque<List<T>>> supplier() {
return ArrayDeque::new;
}
#Override
public BiConsumer<Deque<List<T>>, T> accumulator() {
return (deque, next) -> {
if (deque.isEmpty() || deque.getLast().size() == groupSize) {
List<T> group = new ArrayList<>();
group.add(next);
deque.addLast(group);
} else {
deque.getLast().add(next);
}
};
}
#Override
public BinaryOperator<Deque<List<T>>> combiner() {
return (deque1, deque2) -> {
if (deque1.isEmpty()) {
return deque2;
} else if (deque1.getLast().size() == groupSize) {
deque1.addAll(deque2);
return deque1;
}
// last group in the deque1 has a size less than groupSize
List<T> curGroup = deque1.pollLast();
List<T> nextGroup;
for (List<T> nextItem: deque2) {
nextGroup = nextItem;
Iterator<T> iter = nextItem.iterator();
while (iter.hasNext() && curGroup.size() < groupSize) {
curGroup.add(iter.next());
iter.remove();
}
deque1.add(curGroup);
curGroup = nextGroup;
}
if (curGroup.size() != 0) {
deque1.add(curGroup);
}
return deque1;
};
}
#Override
public Function<Deque<List<T>>, Deque<List<T>>> finisher() {
return deque -> {
if (deque.peekLast() != null && deque.peekLast().size() < groupSize) {
deque.pollLast();
}
return deque;
};
}
#Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
The auxiliary method that is used to validate a group of elements based on its average value (in case you are wondering what RoundingMode is meant for, then read this answer).
private static boolean averageIsLessThen(List<BigDecimal> list, double target) {
BigDecimal average = list.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(BigDecimal.valueOf(list.size()), RoundingMode.HALF_UP);
return average.compareTo(BigDecimal.valueOf(target)) < 0;
}
output (expected result: { 4, 5, 6, 61, 3, 9, 3, 1, 7 }, provided by the PO)
[4, 5, 6, 61, 3, 9, 3, 1, 7]
I have two list as shown below
List<Test> fisrtList= Arrays.asList(
new Test(1, 1L),
new Test(2, 3L),
new Test(2, 4L)
);
List<Long> secondList=Arrays.asList(3L, 5L);
//Find value of second list that are not in first list
Expected answer from the comparision should be 5L as it is not in firstList.
Here is my Test class
public class Test {
public Test(int id, Long idNo) {
this.id=id;
this.idNo=idNo;
}
private int id;
private Long idNo;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Long getIdNo() {
return idNo;
}
public void setIdNo(Long idNo) {
this.idNo = idNo;
}
}
How can I find the Long values from secondList that are not in the firstList?
If you want to use streams for this purpose the most efficient approach will look like this:
public static List<Long> removeIntersection(List<Long> source, List<Test> valuesToRemove) {
Set<Long> toRemove = toSet(valuesToRemove);
return source.stream()
.filter(num -> !toRemove.contains(num))
.collect(Collectors.toList());
}
private static Set<Long> toSet(List<Test> valuesToRemove) {
return valuesToRemove.stream()
.map(Test::getIdNo)
.collect(Collectors.toSet());
}
The same result could be achieved by utilizing removeAll() and retainAll() methods from the Collection interface. These methods are optimized for ArrayList and perform in a linear time.
You just have to coerce your list of test objects to a list of Long and make a copy of the second list which gonna be mutated.
To demonstrate how these methods work let's consider the following example with lists of Integer values.
removeAll() will remove all elements contained in the given collection from this collection
retainAll() will retain only elements contained in both collections
public static void main(String[] args) {
List<Integer> source = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9));
List<Integer> valuesToRemove = new ArrayList<>(List.of(1, 2, 3, 4));
source.removeAll(valuesToRemove);
System.out.println("result of removeAll: " + source);
source = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9));
List<Integer> valuesToRetain = new ArrayList<>(List.of(5, 6, 7, 8, 9));
source.retainAll(valuesToRetain);
System.out.println("result of retainAll: " + source);
}
output
result of removeAll: [5, 6, 7, 8, 9]
result of retainAll: [5, 6, 7, 8, 9]
This will do it
secondList.removeAll(firstList);
If you need secondList to not be modified you must make a deep copy first and use the deep copy instead of secondList.
Here's the code that I'm trying to get.
public static final Comparator<Youku> AscDurRevCreationDate =
Comparator.comparing(Youku::getDuration)
.reversed()
.thenComparing(Youku::getDateCreation)
.reversed();
And the code below is the one I'm trying to convert it to. However, I'm getting a little different result from this code below. Btw, I'm using the Duration object in here.
#Override
public int compare(Youku obj1, Youku obj2) {
Integer duration = obj1.getDuration().compareTo(obj2.getDuration());
Integer dateCreation = obj2.getDateCreation().compareTo(obj1.getDateCreation());
return duration.compareTo(dateCreation );
}
Explanation
Looking at your Comparator:
public static final Comparator<Youku> AscDurRevCreationDate =
Comparator.comparing(Youku::getDuration)
.reversed()
.thenComparing(Youku::getDateCreation)
.reversed();
You want to compare Youkus by their duration (getDuration), descending and not ascending (reversed()) and if two durations are equal, break the ties by the creation date (getDateCreation), descending.
The correct Comparable implementation for that looks like:
#Override
public int compareTo(Youku other) {
int durationResult = Integer.compare(getDuration(), other.getDuration());
durationResult *= -1; // for reversed
if (durationResult != 0) { // different durations
return durationResult;
}
// break ties using creation date
int creationDateResult = Integer.compare(getDateCreation(), other.getDateCreation());
creationDateResult *= -1;
return creationDateResult;
}
or in compact:
int durationResult = -1 * Integer.compare(getDuration(), other.getDuration());
return durationResult != 0
? durationResult
: -1 * Integer.compare(getDateCreation(), other.getDateCreation());
Based on Comparator
Alternatively you can also implement the method based on the Comparator you already have:
public static final Comparator<Youku> comparator =
Comparator.comparing(Youku::getDuration)
.reversed()
.thenComparing(Youku::getDateCreation)
.reversed();
...
#Override
public int compareTo(Youku other) {
return comparator.compare(this, other);
}
Notes
Your code attempt does not show a Comparable implementation but a manual implementation for a Comparator. I suppose you confused something there.
Looking at your original requirement I feel compelled to point out that specifying reversed() reverses the desired order of all the previously specified sorting orders. So assuming a default ascending sorting order for all, the following:
public static final Comparator<Youku> comparator =
Comparator.comparing(Youku::getDuration)
.reversed()
.thenComparing(Youku::getDateCreation)
.reversed();
Sorts first in ascending order of duration and then in descending order of creation since the second reversed() reverses the previous orders. So to sort both in descending, remove the first reversed().
Say you wanted to sort by duration in ascending order but by creation in descending order. Then you could do it this way.
public static final Comparator<Youku> comparator =
Comparator.comparing(Youku::getDuration)
.thenComparing(Youku::getDateCreation, Comparator.reversedOrder());
The Comparator.reverseOrder() only affects the current mode of sorting.
If you want to see an easy example of this, check out the following. You can adjust the comparator to see the different results.
int[][] vals = { { 30, 6 }, { 40, 7 }, { 40, 8 }, { 10, 1 },
{ 10, 2 }, { 20, 3 }, { 20, 4 }, { 30, 5 }, { 50, 9 },
{ 50, 10 }, { 60, 11 }, { 60, 12 } };
// ascending first, descending second
Comparator<int[]> comp =
Comparator.comparing((int[] a) -> a[0])
.reversed()
.thenComparing(a -> a[1])
.reversed();
Arrays.sort(vals,comp);
for(int[] v : vals) {
System.out.println(Arrays.toString(v));
}
Prints
[10, 2]
[10, 1]
[20, 4]
[20, 3]
[30, 6]
[30, 5]
[40, 8]
[40, 7]
[50, 10]
[50, 9]
[60, 12]
[60, 11]
I have a class that represents a tree-like structure, the essential bits look like this:
public Node<T> placeAll(Collection<T> elements){
for (T e : elements)
addElement(e);
// LOG/DEBUG etc
return root;
}
public void addElement(T el) {
Node<T> node = new Node<T>(el);
addElement(root, node);
}
private void addElement(Node<T> parent, Node<T> child) {
// .... PLACE THE NODE
}
Now this works perfectly fine when I place the nodes one by one in a test case:
public void test() {
List<Integer> s1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
// 13 more lists
List<Integer> s15 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 221, 251);
Hypergraph<Object> hg = new Hypergraph<>(...);
hg.addElement(s1);
System.out.println(hg.getRoot().toStringTree());
System.out.println();
.
.
.
hg.addElement(s15);
System.out.println(hg.getRoot().toStringTree());
System.out.println();
}
If I add the following line
hg.placeAll(Arrays.asList(s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15));
to my test case, I get an error regarding the use of generics:
The method placeAll(Collection<Object>) in the type Hypergraph<Object> is not applicable for the arguments (List<List<Integer>>)
I don't quite understand this... If addElement(T el) works fine when I call it with T resolved to List<Integer>, why does List<List<Integer>> comply to placeAll(Collection<T> c)? Considering that List<T> is a Collection<T> I can't make sense out of this..
The problem is that the method expects a Collection<Object> (as T seems to be Object in your example), but you are passing a Collection<List<Integer>>. And while a List<Integer> is an Object, a Collection<List<Integer>> is not a subclass of a Collection<Object>.
Change the method signature to accept a Collection<? extends T>, then it should work.
public Node<T> placeAll(Collection<? extends T> elements) {
I've created the following structure which maps unique double values to one or more pairs of integers:
#SuppressWarnings("boxing")
private static final HashMap<Double, Integer[][]> rules =
new HashMap<Double, Integer[][]>() {
private static final long serialVersionUID = 1L;
{
put(-0.6, new Integer[][] { { 1, 3 } });
put(-0.3, new Integer[][] { { 2, 2 } });
put(0.0, new Integer[][] { { 2, 4 }, { 3, 3 }, { 4, 2 } });
put(0.3, new Integer[][] { { 4, 4 } });
put(0.6, new Integer[][] { { 5, 3 } });
}
};
Can I rewrite this so it's simpler - i.e not have to deal with warnings (serialVersionUID, boxing), and it being so verbose?
Using a class for the pairs of integers should be the first. Or is this a coincidence, that all arrays containing a bunch of pairs?
The second thing is, that these initialization-data could be read from a configuration-file.
Edit: As I looked again on this code, I realized that Doubles as keys in a Map is somewhat risky. If you produce Doubles as a result of an mathematical operation, it is not clear, if they will be equal for the computer (even if they are equal in a mathematical sense). Floating-point-numbers are represented as approximation in computers. Most likely you want to associate the values with the interval (example 0.0-0.3) and not the value itself. You may avoid trouble, if you always use the same constants as keys in the array. But in this case you could use an enum as well, and no new programmer runs into trouble, if he uses his calculated doubles as keys in the map.
Create another class to hold your pairs of integers, and store them using a list:
Map<Double,List<MyPair>>
Are these to be arbitrary pairs of integers, or will the represent something? If the latter, then name appropriately. New classes are cheap in Java, and good naming will reduce maintenance costs.
Edit: why are you creating an anonymous subclass of HashMap?
Can you wrap a class around Integer[][] called, say Point?
That would make you have a
HashMap<Double, List<Point>>
using a static initializer would be slightly better in my opinion, although it does nothing about the verbosity:
private static final Map<Double, int[][]> rules;
static {
rules = new HashMap<Double, int[][]>();
rules.put(-0.6, new int[][] { { 1, 3 } });
rules.put(-0.3, new int[][] { { 2, 2 } });
rules.put(0.0, new int[][] { { 2, 4 }, { 3, 3 }, { 4, 2 } });
rules.put(0.3, new int[][] { { 4, 4 } });
rules.put(0.6, new int[][] { { 5, 3 } });
}
Another option using a special Pair class and Arrays.asList:
class Pair<A, B> {
A a;
B b;
public Pair(A fst, B snd) {
}
// getters and setters here
}
private static final Map<Double, List<Pair<Integer, Integer>>> rules;
static {
rules = new HashMap<Double, List<Pair<Integer, Integer>>>();
rules.put(-0.6, Arrays.asList(new Pair(1, 3)));
rules.put(-0.3, Arrays.asList(new Pair(2, 2)));
rules.put(0.0, Arrays.asList(new Pair(2, 4), new Pair(3, 3), new Pair(4, 2));
// etc
}
I would start with a MultiValueMap. http://larvalabs.com/collections/.
This way you can do:
private static final MultiValueMap<Double, Integer[]> rules;
static {
MultiValueMap<Double, Integer[]> map = new MultiValueMap <Double, Integer[]>();
map.put(-0.6, new Integer[] { 1, 3 });
map.put(-0.3, new Integer[] { 2, 2 });
map.put(0.0, new Integer[] { 2, 4 }, new Integer[]{ 3, 3 }, new Integer[]{ 4, 2 } );
map.put(0.3, new Integer[] { 4, 4 } );
map.put(0.6, new Integer[] { 5, 3 } );
rules = map;
};
It looks also like you are aways using pairs of integers as the list of Keys. It would probably clean your interface up if you refered to that as a RulePair or some other specified object. Thus 'typing' your Integer array more specificially.
There's not much you can do here. The warnings have to be suppressed; in practice, you never have to worry about serialVersionUID unless you are in fact planning to serialize this object.
The boxing can (and probably should) be removed by using a typed collection as described in other answers here. To remove the boilerplate, you'll have to use a method. For example:
private static void put (double key, int x, int y) {
rules.put(key, new Point(x,y));
}
you could try also with a Builder; Java is not good as other languages for this kind of uses.. but here is FYI:
First shot
class RuleBuilder {
private Map<Double, Integer[][]> rules;
public RuleBuilder() {
rules = new HashMap<Double, Integer[][]>();
}
public RuleBuilder rule(double key, Integer[]... rows) {
rules.put(key, rows);
return this;
}
public Integer[] row(Integer... ints) {
return ints;
}
public Map<Double, Integer[][]> build() {
return rules;
}
}
sample usage:
private static final Map<Double, Integer[][]> rules =
new RuleBuilder() {{
rule(-0.6, row(1, 3));
rule(-0.3, row(2, 2));
rule(0.0, row(2, 4), row(3,3), row(4, 2));
rule(0.3, row(4, 4));
rule(0.6, row(5, 3));
}}.build();
Second shot
In order to elimate the final "build()" call and double brace init you could try with:
class RuleBuilder2 extends HashMap<Double, Integer[][]> {
public RuleBuilder2 rule(double key, Integer[]... rows) {
put(key, rows);
return this;
}
public Integer[] row(Integer... ints) {
return ints;
}
}
in this case the code is a little better:
private static final Map<Double, Integer[][]> rules2 =
new RuleBuilder2().
rule(-0.6, row(1, 3)).
rule(-0.3, row(2, 2)).
rule(0.0, row(2, 4), row(3,3), row(4, 2)).
rule(0.3, row(4, 4)).
rule(0.6, row(5, 3));
EDIT
Probably the names that I've used are not so meaningful; boxed/unboxed conversion is still a problem but this is a problem of Java