Finding multiple min/max using Collections.min/max - java

In an example I was working on, I was trying to find the smallest three elements in a list (without sorting the list) and then add those three elements into a new list.
Because it was an example I could just simple use a for loop, use Collections.min(list) add that element to the new list, then remove that element from the original list. If I had not removed the element, I would get the same element three times. However, by removing the element, I got my desired outcome.
How can I do this without removing the max/min elements from the list?

If you want to find the max/min 3 elements, I would suggest you to use a PriorityQueue:
PriorityQueue<Integer> pq = new PriorityQueue<>(k);
And then in a loop, add elements to this queue.
And then you can add these 3 elements to the list by removing from the queue and simply return from the method.
Even better would be to use 3 seperate variables and directly loop on the main list. Note: this method will not be feasible if you later on update 3 to some other value. Whereas PriorityQueue approach will be flexible.
public static void main (String[] args) throws java.lang.Exception {
Integer arr[] = {2, 6, 5, 3, 7, 9, 12, 35, 1, 3};
List<Integer> incoming = Arrays.asList(arr);
Comparator<Integer> maxFirstComparator = (x, y) -> Integer.compare(y,x);
printList(getMinOrMaxKNumbers(incoming, 3, null));
System.out.println();
printList(getMinOrMaxKNumbers(incoming, 3, maxFirstComparator));
}
/*
* gets the max/min K elements from the List
* #param comparator if null is passed the method uses natural ordering
*
*/
private static List<Integer> getMinOrMaxKNumbers(List<Integer> incoming, int k, Comparator<Integer> comparator) {
int n = incoming.size();
PriorityQueue<Integer> pq = comparator == null ? new PriorityQueue<>(n) : new PriorityQueue<>(n, comparator);
for (int i : incoming) {
pq.add(i);
}
List<Integer> outgoing = new ArrayList<>(k);
for (int i = 0; i < k; i++) {
outgoing.add(pq.poll());
}
return outgoing;
}
private static void printList(List<Integer> list) {
list.stream().forEach(x -> System.out.print(x + " "));
}

I don't think there is any straight forward way to do this using the standard library.
The following code contains a utility method to get the set of a number of maximal elements.
It works by keeping the maximal elements in a TreeSet, to which an element is inserted only if it belongs to the maximal elements. A tree set is a good fit here because it is fast to find the minimal element, and to test if an element is contained in the set.
In this way you get good performance, and can be flexible with the number of maximal elements you want.
public class MultipleMaxElements {
public static void main(String[] args) {
List<Integer> l = List.of(4, 2, 5, 8, 2, 8, 0, 1);
System.out.println(maxElements(l, 3, Comparator.naturalOrder()));
System.out.println(maxElements(l, 3, Comparator.<Integer>naturalOrder().reversed()));
}
public static <T> Set<T> maxElements(Iterable<T> list, int nrElems, Comparator<T> cmp) {
TreeSet<T> maxSet = new TreeSet<>(cmp);
for (T elem : list) {
if (maxSet.size() < nrElems) {
maxSet.add(elem);
} else if (!maxSet.contains(elem) && cmp.compare(elem, maxSet.first()) > 0) {
maxSet.pollFirst();
maxSet.add(elem);
}
}
return maxSet;
}
}
In the question text you don't write anything about how duplicate elements in the input list should be handled. This method returns maximal unique elements.
If duplicates should be preserved then a tree based Guava multi-set could be used instead of a normal TreeSet.

Related

How to efficiently merge two lists in Java?

There are several ways to merge lists in Java
You can call to ArrayList(Collection<? extends E> c)
You can use the stream API, like Stream.concat() or Stream.of(listA, listB).forEach()
and more...
What would be the most memory and performance efficient way to merge two random access lists into a new random access list?
You have not defined what "merge" means in your context. This answer assumes it means "combine into one list".
To reduce the amount of memory and processing used, create a List whose size is exactly right, then add each list in turn to it.
List<E> result = new ArrayList<>(list1.size() + list2.size());
result.addAll(list1);
result.addAll(list2);
This eliminates possible redundant memory allocation and object creation that may occur during list1.addAll(list2).
you can use Apache commons library-
ListUtils.union(listA, listB);
Using parallel Java8 Streams could be is better instead of just streams for large datasets.
Stream.concat(list1.parallelStream(), list1.parallelStream())
.collect(Collectors.toList());
Try this to create an immutable list containing all the elements, by performing a shallow copy. Beware that changes to the source lists will be reflected in the resulting list (so the immutability in reality depends on the immutability / access to the input lists).
public class MergedList<T> extends AbstractList<T> {
private final List<T>[] lists;
private final int size;
#SafeVarargs
MergedList(List<T>... lists) {
this.lists = lists.clone();
this.size = Arrays.stream(lists).mapToInt(list -> list.size()).sum();
}
#Override
public T get(int index) {
for (List<T> list : lists)
if (index < list.size())
return list.get(index);
else
index -= list.size();
throw new IndexOutOfBoundsException("index");
}
#Override
public int size() {
return size;
}
}
and
List<Integer> a = List.of(1, 2, 3, 4);
List<Integer> b = List.of(5, 6, 7);
List<Integer> c = new MergedList<>(a, b);
System.out.println(c);
output
[1, 2, 3, 4, 5, 6, 7]
Considering that the original list is updated, it might be better to remove the field size and do this:
#Override
public int size() {
return Arrays.stream(lists).mapToInt(list -> list.size()).sum();
}

Partially sort an array in descending order using Java Stream API

I need to know how to partially sort an array of primitive unique integers in descending order using Stream API. For example, if there is an array like {1,2,3,4,5}, I want to get {5,4,3, 1,2} - 3 biggest elements first and then the rest. Is it even possible using streams? I checked the docs - there are two methods skip and limit but they change the stream content and work from the beginning of the array.
I can sort the whole array like
Arrays.stream(arr)
.boxed()
.sorted(Collections.reverseOrder())
.mapToInt(Integer::intValue)
.toArray();
but how to make this sorting partial? I said Stream API because I want it to be written nicely.
Also I intuitively feel that concat may have a go here. Another approach I could think about - is to use a custom comparator limiting the number of sorted elements. What do you think?
P.S. I am not a Java expert.
Though the code is longer than the accepted answer, it does a lot less sorting: for big arrays this will matter:
private static int[] partiallySorted(int[] input, int bound) {
int[] result = new int[input.length];
int i = -1;
PriorityQueue<Integer> pq = new PriorityQueue<>(bound, Comparator.naturalOrder());
for (int x : input) {
pq.add(x);
if (pq.size() > bound) {
int el = pq.poll();
result[bound + ++i] = el;
}
}
while (!pq.isEmpty()) {
result[--bound] = pq.poll();
}
return result;
}
Here's an approach using streams.
int[] sortPartially(int[] inputArray, int limit) {
Map<Integer, Long> maxValues = IntStream.of(inputArray)
.boxed()
.sorted(Comparator.reverseOrder())
.limit(limit)
.collect(Collectors.groupingBy(x -> x, LinkedHashMap::new, Collectors.counting()));
IntStream head = maxValues.entrySet()
.stream()
.flatMapToInt(e -> IntStream.iterate(e.getKey(), i -> i)
.limit(e.getValue().intValue()));
IntStream tail = IntStream.of(inputArray)
.filter(x -> {
Long remainingDuplication = maxValues.computeIfPresent(x, (y, count) -> count - 1);
return remainingDuplication == null || remainingDuplication < 0;
});
return IntStream.concat(head, tail).toArray();
}
Above example of course sorts the entire input array, but keeps the order of unsorted elements stable.
Another stream example using priority queue (as others mentioned) reduces the runtime complexity:
Collection<Integer> sortPartially(int[] inputArray, int sortedPartLength) {
Queue<Integer> pq = new PriorityQueue<>(sortedPartLength);
Deque<Integer> result = IntStream.of(inputArray).boxed().map(x -> {
pq.add(x);
return pq.size() > sortedPartLength ? pq.poll() : null;
}).filter(Objects::nonNull).collect(Collectors.toCollection(ArrayDeque::new));
Stream.generate(pq::remove).limit(sortedPartLength).forEach(result::addFirst);
return result;
}
If there are duplicates in the input array, the order of unsorted elements can change.
I need to know how to partially sort an array of primitive integers in descending order using Stream API.
There is no built-in tool that lets you do this in Java. Neither in the Stream API nor in the Collections API. You either need to implement it on your own or change your approach.
I said Stream API because I want it to be written nicely.
Using Java 8 Streams does not mean that your code will be written nicely. Streams are not universal tools. Sometimes they offer enhanced readability and sometimes you have to use something else.
Another approach I could think about - is to use a custom comparator limiting the number of sorted elements.
That can't be done, since Comparator does not know how many elements have been sorted. Simply counting the calls will not give you any meaningful information in this regard.
What I would suggest is implementing something like C++'s std::partial_sort, which is most likely based on the heap approach.
I would save the three largest elements in a set and then define my own comparator.
public static void main(String[] args){
int[] input = {1,2,3,4,5};
Set<Integer> set = Arrays.stream(input).boxed().sorted(Comparator.reverseOrder()).limit(3).collect(Collectors.toSet());
Comparator<Integer> customComp = (a,b) -> {
if(set.contains(a) && set.contains(b)){ return a.compareTo(b);}
else if(set.contains(a)){ return 1;}
else if(set.contains(b)){ return -1;}
else { return 0;}
};
int[] sorted = Arrays.stream(input).boxed().sorted(customComp.reversed()).mapToInt(i->i).toArray();
System.out.println(Arrays.toString(sorted));
}
You won't be able to do this very nicely using streams. Here is one way to do it:
public static void main(String[] args) {
Integer[] arr = {1, 2, 3, 4, 5};
List<Integer> originalValues = new ArrayList<>(Arrays.asList(arr));
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
originalValues.stream().max(Integer::compareTo).ifPresent(v -> {
list.add(v);
originalValues.remove(v);
});
}
list.addAll(originalValues);
System.out.println(list);
// [5, 4, 3, 1, 2]
}

get the index of the min size list in an arrayList using java stream

I have a list of Integer List, like list1=(1,2,3) and list2 = (0,1).
my list of lists contains list1 and list2. It could contains more but for the example i took only two lists.
The question is to get the index of the list with minimum size using java stream.
Here is my program and it work using only the for loop method.
import java.util.ArrayList;
public class Example {
public static void main( String[] args ) {
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);list1.add(2);list1.add(3);
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(0);list2.add(1);
ArrayList<ArrayList<Integer>> listOLists = new ArrayList<>();
listOLists.add(list1);
listOLists.add(list2);
printTheIndexOfTheListWithTheMinSize(listOLists);
}
private static void printTheIndexOfTheListWithTheMinSize( ArrayList<ArrayList<Integer>> listOLists ) {
int minSize = listOLists.get(0).size();
int minIndex = 0;
int i=0;
for ( ArrayList<Integer> list: listOLists ) {
if (list.size()<minSize)
{
minSize = list.size();
minIndex=i;
}
i++;
}
System.out.println(minIndex);
}
}
Could you please give me a hint how to do that using Java stream API.
Note that i'm calling this method many times in a heavy calcul, so the awnser should take that in consideration.
Not really elegant, because it requires boxing and unboxing, but...
Optional<Integer> minIndex =
IntStream.range(0, list.size())
.boxed()
.min(Comparator.comparingInt(i -> list.get(i).size()));
One way to possibly do that would be using indexOf and Collections.min with a comparator as:
int minIndex = listOLists.indexOf(Collections.min(listOLists,
Comparator.comparingInt(List::size)));
Stay away from solutions using indexOf. While they may allow to write rather short code, this indexOf operation bears a content-based linear search operation, invoking equals on the list elements until a match is found.
While it might look like a trivial thing, as all sub-lists differ in size, except for the matching element, most of Java 8’s List implementations do not use the size to short-cut the comparison.
To illustrate the issue,
use the following helper class
class Counter {
int count;
#Override
public boolean equals(Object obj) {
count++;
return super.equals(obj);
}
#Override
public int hashCode() {
return super.hashCode();
}
#Override
public String toString() {
return "equals invoked "+count+" times";
}
}
and
Counter c = new Counter();
List<List<Counter>> list = Arrays.asList(
new ArrayList<>(Collections.nCopies(10, c)),
new ArrayList<>(Collections.nCopies(15, c)),
new ArrayList<>(Collections.nCopies(7, c)),
new ArrayList<>(Collections.nCopies(10, c))
);
Comparator<List<?>> cmp = Comparator.comparingInt(List::size);
System.out.println("using IntStream.range(0, list.size()).boxed()\r\n" +
" .min(Comparator.comparing(list::get, cmp))");
int minIndex =
IntStream.range(0, list.size()).boxed()
.min(Comparator.comparing(list::get, cmp)).orElse(-1);
System.out.println("result "+minIndex+", "+c);
c.count = 0;
System.out.println("\nusing list.indexOf(Collections.min(list, cmp))");
minIndex = list.indexOf(Collections.min(list, cmp));
System.out.println("result "+minIndex+", "+c);
c.count = 0;
System.out.println("\nusing list.indexOf(list.stream().min(cmp).get())");
minIndex = list.indexOf(list.stream().min(cmp).get());
System.out.println("result "+minIndex+", "+c);
it will print
using IntStream.range(0, list.size()).boxed()
.min(Comparator.comparing(list::get, cmp))
result 2, equals invoked 0 times
using list.indexOf(Collections.min(list, cmp))
result 2, equals invoked 14 times
using list.indexOf(list.stream().min(cmp).get())
result 2, equals invoked 14 times
in Java 8, showing that calling equals on any contained element is an unnecessary operation (see the first variant, derived from this answer), but performed multiple times for the other variants. Now imagine what happens if we use larger lists and/or a larger number of lists and have an element type with a rather expensive equality test.
Note that for ArrayList, this has been solved in JDK 11, but there are still list implementations left, like the ones returned by Collections.nCopies or Arrays.asList, which do not short circuit, so it’s generally preferable not to do an entirely obsolete content based linear search operation.
Here's one way to go about it:
int index = listOLists.indexOf(listOLists.stream()
.min(Comparator.comparingInt(List::size))
.orElseGet(ArrayList::new));
or if you want to avoid the creation of an ArrayList when the source is empty then you could do:
int index = listOLists.isEmpty() ? -1 : listOLists.indexOf(listOLists.stream()
.min(Comparator.comparingInt(List::size)).get());
An alternative that creates index/size arrays and finds the min by size:
IntStream.range(0, listOLists.size())
.mapToObj(i -> new int[] { i, listOLists.get(i).size() })
.min(Comparator.comparingInt(arr -> arr[1]))
.map(arr -> arr[0])
.ifPresent(System.out::println);
This will print the index of min-sized list in listOLists

Shuffle list except for certain sequences

I want to shuffle an ArrayList but based on some custom conditions:
if my array list was something like [1, 4, 5, 6, 9, 45, 67],
I want to shuffle it but make sure 5, 6, 9 always appear together.
Is there any method available in Collections class to do this?
I have tried doing this, but it throws ConcurrentModificationException
List<Integer> y= new ArrayList<>();
y.add(1);
y.add(4);
y.add(5);
y.add(6);
y.add(9);
y.add(45);
y.add(67);
List<Integer> z = y.subList(2, 5);
y.removeAll(z);
Collections.shuffle(y);
int index = ThreadLocalRandom.current()
.nextInt(0, y.size() + 1);
y.addAll(index,z);
It sounds like your data should really be a list of lists, especially since its likely that you will have more than 1 group that needs to stay together.
You can always flatten it when you need.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<List<Integer>> y = new ArrayList<List<Integer>>();
y.add(new ArrayList<Integer>(Arrays.asList(1)));
y.add(new ArrayList<Integer>(Arrays.asList(4)));
y.add(new ArrayList<Integer>(Arrays.asList(5, 6, 9)));
y.add(new ArrayList<Integer>(Arrays.asList(45)));
y.add(new ArrayList<Integer>(Arrays.asList(67)));
Collections.shuffle(y);
List<Integer> flatList = new ArrayList<>();
y.forEach(flatList::addAll);
}
}
A simple way of doing this is to store your target elements in a separate List:
List<Integer> target = new ArrayList<>();
target.add(5);
target.add(6);
target.add(9);
Then shuffle your main list:
Collections.shuffle(y);
Then get a random number from 0 -> y.size().
Random ran = new Random();
int pos = ran.nextInt(y.size());
And insert your target list into your original list:
y.addAll(pos, target);
Note: this assumes your original list has the target 3 numbers removed already.
Without seeing your code, I'd think that the ConcurrentModificationExceptions are thrown because you try to remove the group elements from the list or to add them back in while iterating it. Changing a collection while iterating it leads to those exceptions: Iterating through a Collection, avoiding ConcurrentModificationException when removing in loop.
It will get a lot easier if you do not treat the groups as the exception, but as the norm. What I mean by this is that you should convert your List<?> into a List<List<?>> where each sub list contains either one element or one of the groups. You can then shuffle that list easily with Collections.shuffle() and flatten it again.
Look at this rough implementation:
List<Integer> ints = new ArrayList<>(asList(2, 3, 5, 4, 8, 7, 11, 55));
List<List<Integer>> groups = asList(asList(5, 4), asList(7, 11));
// remove all the grouped elements from the list
groups.forEach(ints::removeAll);
// wrap the single elements into list and join them with the groups
List<List<Integer>> wrapped = Stream.concat(ints.stream().map(Arrays::asList),
groups.stream())
.collect(Collectors.toList());
Collections.shuffle(wrapped);
// flatten the list into single elements again
List<Integer> shuffled = wrapped.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(shuffled); // e.g. [55, 3, 7, 11, 2, 8, 5, 4]
// ----- ----
Note that while this is quite readable, it is probably not the most efficient or error proof solution. But it should give you an idea how to tackle the problem.
Edit after comment from Gonen I. Here is a helper method to only remove the exact sequences and not random parts of them all over the list:
private static <T> void removeSequence(List<T> list, List<T> sequence)
{
int indexOfSubList = Collections.lastIndexOfSubList(list, sequence);
while (indexOfSubList != -1)
{
for (int j = 0; j < sequence.size(); j++)
{
list.remove(indexOfSubList);
}
indexOfSubList = Collections.lastIndexOfSubList(list, sequence);
}
}
Use it by replacing groups.forEach(ints::removeAll); by groups.forEach(group -> removeSequence(ints, group));

removing elements from an array list

consider I have an array list like : [2,5,1,8,6]
and I want to remove all elements from 1 till the end .and the arraylist will be like :[2,5]
how can i do this?
thanks
It is worth noting that you cannot add/remove from an Arrays.asList(), but you can do.
List<Integer> list = Arrays.asList(2, 5, 1, 8, 6);
int idx = list.indexOf(1);
if (idx>=0) list = list.subList(0, idx);
List<Integer> list = Arrays.asList(2, 5, 1, 8, 6);
boolean remove = false;
Iterator<Integer> it = list.iterator();
while (it.hasNext() {
if (!remove && it.next() == 1) {
remove = true;
}
if (remove) {
it.remove();
}
}
list = list.subList(0, 1);
The most efficient way to remove elements from ArrayList is to remove them from the end of the list. Each element you remove from the middle of the list will result in all the latter elements being moved to the left. If the list is large, this can result in a significant performance issue.
However, in your case, you might be better off just creating a new sublist with the remaining two elements.
Another way of doing it would be to simply pop items off the end of the List until we have popped the starting element. Each item can be popped in O(1) time, so the entire operation is O(n).
class Main{
private static List<Integer> inputs = new ArrayList<Integer>();
public static void main(String args[]){
for (int x: new int[]{2,5,1,8,6})
inputs.add(x);
System.out.println(inputs);
int start=inputs.indexOf(1);
if (start>=0){ //check if there is a 1 in input
while (inputs.size()>start)
inputs.remove(inputs.size()-1);
}
System.out.println(inputs);
}
}

Categories

Resources