How to convert Comparator to compare? - java

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]

Related

How to collect element of the stream into groups of fixed size in accordance with their order

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]

Stateful filter for ordered stream

I have a problem and I wonder if there is a solution using Streams.
Imagine you have an ordered stream of Objects; let's assume a stream of Integers.
Stream<Integer> stream = Stream.of(2,20,18,17,4,11,13,6,3,19,4,10,13....)
Now I want to filter all values where the difference of a value and the previous number before this value is greater than n.
stream.filter(magicalVoodoo(5))
// 2, 20, 4, 11, 3, 19, 4, 10 ...
I there any possibility to do this?
Yes, this is possible, but you will need a stateful predicate that keeps track of the previous value for doing the comparison. This does mean it can only be used for sequential streams: with parallel streams you'd run into race conditions.
Luckily, most streams default to sequential, but if you need to do this on streams from an unknown source, you may want to check using isParallel() and either throw an exception, or convert it to a sequential stream using sequential().
An example:
public class DistanceFilter implements IntPredicate {
private final int distance;
private int previousValue;
public DistanceFilter(int distance) {
this(distance, 0);
}
public DistanceFilter(int distance, int startValue) {
this.distance = distance;
this.previousValue = startValue;
}
#Override
public boolean test(int value) {
if (Math.abs(previousValue - value) > distance) {
previousValue = value;
return true;
}
return false;
}
// Just for simple demonstration
public static void main(String[] args) {
int[] ints = IntStream.of(2, 20, 18, 17, 4, 11, 13, 6, 3, 19, 4, 10, 13)
.filter(new DistanceFilter(5))
.toArray();
System.out.println(Arrays.toString(ints));
}
}
I used IntStream here, because it is a better type for this, but the concept would be similar for Stream<Integer> (or other object types).
Streams are not designed for this kind of task. I would use a different way to accomplish this, which doesn't use streams. But, if you really must use streams, the solution has to circumvent certain restrictions due to the design of streams and lambdas, and therefore looks quite hacky:
int[] previous = new int[1];
previous[0] = firstElement;
... = stream.filter(n -> {
boolean isAllowed = (abs(n - previous[0]) > 5);
if (isAllowed)
previous[0] = n;
return isAllowed;})
Notice that the variable previous is a one-element array. That's a hack due to the fact that the lambda is not allowed to modify variables (it can modify an element of an array, but not the array itself).

Merge sets when two elements in common

This is the follow up of compare sets
I have
Set<Set<Node>> NestedSet = new HashSet<Set<Node>>();
[[Node[0], Node[1], Node[2]], [Node[0], Node[2], Node[6]], [Node[3], Node[4], Node[5]] [Node[2], Node[6], Node[7]] ]
I want to merge the sets when there are two elements in common. For example 0,1,2 and 0,2,6 has two elements in common so merging them to form [0,1,2,6].
Again [0,1,2,6] and [2,6,7] has 2 and 6 common. so merging them and getting [0,1,2,6,7].
The final output should be :
[ [Node[0], Node[1], Node[2], Node[6], Node[7]], [Node[3], Node[4], Node[5]] ]
I tried like this :
for (Set<Node> s1 : NestedSet ) {
Optional<Set<Node>> findFirst = result.stream().filter(p -> { HashSet<Node> temp = new HashSet<>(s1);
temp.retainAll(p);
return temp.size() == 2; }).findFirst();
if (findFirst.isPresent()){
findFirst.get().addAll(s1);
}
else {
result.add(s1);
}
}
But the result I got was :
[[Node[0], Node[1], Node[2], Node[6], Node[7]], [Node[3], Node[4], Node[5]], [Node[0], Node[2], Node[6], Node[7]]]
Any idea ? Is there any way to get the desired output?
Some considerations:
Each time you apply a merge, you have to restart the procedure and iterate over the modified collection. Because of this, the iteration order of the input set is important, if you want your code to be deterministic you may want to use collections that give guarantees over their iteration order (e.g. use LinkedHashSet (not HashSet) or List.
Your current code has side effects as it modifies the supplied sets when merging. In general I think it helps to abstain from creating side effects whenever possible.
The following code does what you want:
static <T> List<Set<T>> mergeSets(Collection<? extends Set<T>> unmergedSets) {
final List<Set<T>> mergedSets = new ArrayList<>(unmergedSets);
List<Integer> mergeCandidate = Collections.emptyList();
do {
mergeCandidate = findMergeCandidate(mergedSets);
// apply the merge
if (!mergeCandidate.isEmpty()) {
// gather the sets to merge
final Set<T> mergedSet = Sets.union(
mergedSets.get(mergeCandidate.get(0)),
mergedSets.get(mergeCandidate.get(1)));
// removes both sets using their index, starts with the highest index
mergedSets.remove(mergeCandidate.get(0).intValue());
mergedSets.remove(mergeCandidate.get(1).intValue());
// add the mergedSet
mergedSets.add(mergedSet);
}
} while (!mergeCandidate.isEmpty());
return mergedSets;
}
// O(n^2/2)
static <T> List<Integer> findMergeCandidate(List<Set<T>> sets) {
for (int i = 0; i < sets.size(); i++) {
for (int j = i + 1; j < sets.size(); j++) {
if (Sets.intersection(sets.get(i), sets.get(j)).size() == 2) {
return Arrays.asList(j, i);
}
}
}
return Collections.emptyList();
}
For testing this method I created two helper methods:
static Set<Integer> set(int... ints) {
return new LinkedHashSet<>(Ints.asList(ints));
}
#SafeVarargs
static <T> Set<Set<T>> sets(Set<T>... sets) {
return new LinkedHashSet<>(Arrays.asList(sets));
}
These helper methods allow to write very readable tests, for example (using the numbers from the question):
public static void main(String[] args) {
// prints [[2, 6, 7, 0, 1]]
System.out.println(mergeSets(sets(set(0, 1, 2, 6), set(2, 6, 7))));
// prints [[3, 4, 5], [0, 2, 6, 1, 7]]
System.out.println(
mergeSets(sets(set(0, 1, 2), set(0, 2, 6), set(3, 4, 5), set(2, 6, 7))));
}
I'm not sure why you are getting that result, but I do see another problem with this code: It is order-dependent. For example, even if the code worked as intended, it would matter whether [Node[0], Node[1], Node[2]] is compared first to [Node[0], Node[2], Node[6]] or [Node[2], Node[6], Node[7]]. But Sets don't have a defined order, so the result is either non-deterministic or implementation-dependent, depending on how you look at it.
If you really want deterministic order-dependent operations here, you should be using List<Set<Node>>, rather than Set<Set<Node>>.
Here's a clean approach using recursion:
public static <T> Set<Set<T>> mergeIntersectingSets(Collection<? extends Set<T>> unmergedSets) {
boolean edited = false;
Set<Set<T>> mergedSets = new HashSet<>();
for (Set<T> subset1 : unmergedSets) {
boolean merged = false;
// if at least one element is contained in another subset, then merge the subsets
for (Set<T> subset2 : mergedSets) {
if (!Collections.disjoint(subset1, subset2)) {
subset2.addAll(subset1);
merged = true;
edited = true;
}
}
// otherwise, add the current subset as a new subset
if (!merged) mergedSets.add(subset1);
}
if (edited) return mergeIntersectingSets(mergedSets); // continue merging until we reach a fixpoint
else return mergedSets;
}

Combine values with Java8 stream

If I have a list with integers, is there a way to construct another list, where integers are summed if the difference to the head of the new list is below a threashold? I would like to solve this using Java 8 streams. It should work similar to the Scan operator of RxJava.
Example: 5, 2, 2, 5, 13
Threashold: 2
Result: 5, 9, 13
Intermediate results:
5
5, 2
5, 4 (2 and 2 summed)
5, 9 (4 and 5 summed)
5, 9, 13
Sequential Stream solution may look like this:
List<Integer> result = Stream.of(5, 2, 2, 5, 13).collect(ArrayList::new, (list, n) -> {
if(!list.isEmpty() && Math.abs(list.get(list.size()-1)-n) < 2)
list.set(list.size()-1, list.get(list.size()-1)+n);
else
list.add(n);
}, (l1, l2) -> {throw new UnsupportedOperationException();});
System.out.println(result);
Though it looks not much better as good old solution:
List<Integer> input = Arrays.asList(5, 2, 2, 5, 13);
List<Integer> list = new ArrayList<>();
for(Integer n : input) {
if(!list.isEmpty() && Math.abs(list.get(list.size()-1)-n) < 2)
list.set(list.size()-1, list.get(list.size()-1)+n);
else
list.add(n);
}
System.out.println(list);
Seems that your problem is not associative, so it cannot be easily parallelized. For example, if you split the input into two groups like this (5, 2), (2, 5, 13), you cannot say whether the first two items of the second group should be merged until the first group is processed. Thus I cannot specify the proper combiner function.
As Tagir Valeev observed, (+1) the combining function is not associative, so reduce() won't work, and it's not possible to write a combiner function for a Collector. Instead, this combining function needs to be applied left-to-right, with the previous partial result being fed into the next operation. This is called a fold-left operation, and unfortunately Java streams don't have such an operation.
(Should they? Let me know.)
It's possible to sort-of write your own fold-left operation using forEachOrdered while capturing and mutating an object to hold partial state. First, let's extract the combining function into its own method:
// extracted from Tagir Valeev's answer
void combine(List<Integer> list, int n) {
if (!list.isEmpty() && Math.abs(list.get(list.size()-1)-n) < 2)
list.set(list.size()-1, list.get(list.size()-1)+n);
else
list.add(n);
}
Then, create the initial result list and call the combining function from within forEachOrdered:
List<Integer> result = new ArrayList<>();
IntStream.of(5, 2, 2, 5, 13)
.forEachOrdered(n -> combine(result, n));
This gives the desired result of
[5, 9, 13]
In principle this can be done on a parallel stream, but performance will probably degrade to sequential given the semantics of forEachOrdered. Also note that the forEachOrdered operations are performed one at a time, so we needn't worry about thread safety of the data we're mutating.
I know that the Stream's masters "Tagir Valeev" and "Stuart Marks" already pointed out that reduce() will not work because the combining function is not associative, and I'm risking a couple of downvotes here. Anyway:
What about if we force the stream to be sequential? Wouldn't we be able then to use reduce? Isn't the associativity property only needed when using parallelism?
Stream<Integer> s = Stream.of(5, 2, 2, 5, 13);
LinkedList<Integer> result = s.sequential().reduce(new LinkedList<Integer>(),
(list, el) -> {
if (list.isEmpty() || Math.abs(list.getLast() - el) >= 2) {
list.add(el);
} else {
list.set(list.size() - 1, list.getLast() + el);
}
return list;
}, (list1, list2) -> {
//don't really needed, as we are sequential
list1.addAll(list2); return list1;
});
Java 8 way is define custom IntSpliterator class:
static class IntThreasholdSpliterator extends Spliterators.AbstractIntSpliterator {
private PrimitiveIterator.OfInt it;
private int threashold;
private int sum;
public IntThreasholdSpliterator(int threashold, IntStream stream, long est) {
super(est, ORDERED );
this.it = stream.iterator();
this.threashold = threashold;
}
#Override
public boolean tryAdvance(IntConsumer action) {
if(!it.hasNext()){
return false;
}
int next = it.nextInt();
if(next<threashold){
sum += next;
}else {
action.accept(next + sum);
sum = 0;
}
return true;
}
}
public static void main( String[] args )
{
IntThreasholdSpliterator s = new IntThreasholdSpliterator(3, IntStream.of(5, 2, 2, 5, 13), 5);
List<Integer> rs= StreamSupport.intStream(s, false).mapToObj(Integer::valueOf).collect(toList());
System.out.println(rs);
}
Also you can hack it as
List<Integer> list = Arrays.asList(5, 2, 2, 5, 13);
int[] sum = {0};
list = list.stream().filter(s -> {
if(s<=2) sum[0]+=s;
return s>2;
}).map(s -> {
int rs = s + sum[0];
sum[0] = 0;
return rs;
}).collect(toList());
System.out.println(list);
But I am not sure that this hack is good idea for production code.

Java - A TreeSet containing Lists of Integers?

I need a Set that contains a List of Integers. Each List contains 2 int values (for x and y coordinates) and I want to be able to implement some sort of compare method so that the Set is sorted depending on the first value (X value) for the List in ascending order so that Lists with the first value being very large will appear at the tail of the Set.
So that instead of printing out:
[2, 1]
[1, 2]
[3, 3]
[2, 3]
It prints out:
[1, 2]
[2, 1]
[2, 3]
[3, 3]
The problem is that I get an error:
Exception in thread "main" java.lang.ClassCastException:
java.util.Arrays$ArrayList cannot be cast to java.lang.Comparable
I honestly do not have much experience with sorting like this as I am a beginner. Any help would be brilliant, thanks!
Code so far:
public class Shape implements Comparator<List<Integer>> {
private SortedSet<List<Integer>> coords;
#Override
public int compare(List<Integer> l1, List<Integer> l2) {
if (l1.get(0) > l2.get(0))
return 1;
else
return -1;
}
public Shape(int shapeID) {
coords = new TreeSet<List<Integer>>();
switch (shapeID) {
case 1:
coords.add(Arrays.asList(0, 2));
coords.add(Arrays.asList(1, 2));
coords.add(Arrays.asList(3, 2));
break;
case 2:
coords.add(Arrays.asList(1, 1));
coords.add(Arrays.asList(1, 2));
coords.add(Arrays.asList(2, 1));
break;
case 3:
coords.add(Arrays.asList(1, 3));
coords.add(Arrays.asList(2, 1));
coords.add(Arrays.asList(2, 3));
break;
}
coords.add(Arrays.asList(2, 2));
}
public void rotate() {
Iterator<List<Integer>> it = coords.iterator();
// should print out a sorted Set:
while (it.hasNext()) {
System.out.println(it.next());
// do more stuff here..
}
}
public static void main(String[] args) {
Shape s = new Shape(2);
s.rotate();
}
}
There are two types of constructor for TreeSets - ones where you specify an explicit Comparator, and ones where you do not. In the latter case, the set will use the natural ordering of its elements, which requires that they implement Comparable. This is why you get the runtime exception, which is relatively clear (once you understand the background).
Since you can't make ArrayLists implement Comparable, you need to provide your own Comparator when you're constructing the TreeSet to assign to coords. This should be straightforward for you to implement (it's just a single function that takes two objects, and decides which one "comes first"), and you can put whatever custom ordering logic in there that you want.

Categories