Sorting multiple ArrayList synchronously - java

I have multiple parallel ArrayList, I am sorting one of them (i.e. indexes).
ArrayList<Integer> indexes = {2,3,1};
ArrayList<String> names = {"two","three","one"};
ArrayList<String> upper = {"TWO","THREE","ONE"};
I want to synchronise the sorting of the ArrayList 'indexes' with the others ArrayList.
I am wondering in the Collections.sort(list) would give me a clue?

It sounds like you want to sort one array by its values, and then rearrange two other arrays so that the arrangement of their values matches the sort order of the first array.
An easy way to do this is to sort an array of indexes into the ordering that you want, and then use this to rearrange the other arrays into the same order. Since one of your arrays is already called "indexes" I'll call this new array the "permutation".
First, create the permutation array by generating index values from zero to size-1 and then sorting them. They end up being sorted not according to their own values, but by the values in your index array:
List<Integer> indexes = List.of(2,3,1);
List<String> names = List.of("two","three","one");
List<String> upper = List.of("TWO","THREE","ONE");
List<Integer> permutation = IntStream.range(0, indexes.size())
.boxed()
.sorted(comparing(indexes::get))
.collect(toCollection(ArrayList::new));
(So far, this is similar to the technique from Eritrean's answer.)
Now, we need to rearrange some data array according to the arrangement from the permutation array. Since we're doing this multiple times, here's a function that does that:
static <T> List<T> permute(List<Integer> permutation, List<T> list) {
return IntStream.range(0, permutation.size())
.mapToObj(i -> list.get(permutation.get(i)))
.toList();
}
Now it's a simple matter to apply this to each of the data arrays:
System.out.println(permute(permutation, indexes));
System.out.println(permute(permutation, names));
System.out.println(permute(permutation, upper));
The result is
[1, 2, 3]
[one, two, three]
[ONE, TWO, THREE]
Note that this creates new lists in the desired arrangement. It's possible to permute the data arrays in-place, but it's somewhat more work, though not intractable. (Search for "[java] permute array in place" for ideas.)

Create a priority list from your indexes and use the index of the elemnts as a sorting criteria:
public static void main(String args[]) {
List<Integer> indexes = new ArrayList<>(List.of(2,3,1));
List<String> names = new ArrayList<>(List.of("two","three","one"));
List<String> upper = new ArrayList<>(List.of("TWO","THREE","ONE"));
List<Integer> priority = IntStream.range(0, indexes.size())
.boxed()
.sorted(Comparator.comparingInt(indexes::get))
.collect(Collectors.toList());
names.sort(Comparator.comparingInt(i -> priority.indexOf(names.indexOf(i))));
upper.sort(Comparator.comparingInt(i -> priority.indexOf(upper.indexOf(i))));
indexes.sort(Comparator.comparingInt(i -> priority.indexOf(indexes.indexOf(i))));
System.out.println(indexes);
System.out.println(names);
System.out.println(upper);
}

You can use a container as an intermediary step (though, it may be better to modify your code to simply use the container instead of 3 separate lists):
public class Container {
final int id;
final String name;
final String upper;
... // constructor + getters (or create a record if you're using j14+)
}
public static void main(String args[]) {
List<Container> values = indexes.stream()
.sorted()
.map(index -> new Container(index, names.get(index), upper.get(index)))
.collect(Collectors.toList());
List<String> sortedNames = values.stream()
.map(value -> value.getName())
.collect(Collectors.toList());
List<String> sortedUpper = values.stream()
.map(value -> value.getUpper())
.collect(Collectors.toList());
}

Related

Get indices of unique WebElements.getText() objects in an ArrayList

I have an ArrayList<WebElements> whose elements all have some pieces of text. I'd like to store the indices of those WebElements, which contain unique pieces of text in a new ArrayList<Integer>. E.g. if the texts are (a,b,c,a,b,d) then I'd need (1,2,3,6).
I tried getting the texts to ArrayList<String> piecesOfText with one stream, picking the uniques with another one into ArrayList<String> piecesOfTextUnique, then looping through piecesOfTextUniqueand getting the indices of those pieces of text in piecesOfText via indexOf()
ArrayList<WebElement> webElementsWithText=getWebElements();
ArrayList<String> piecesOfText= new ArrayList<>();
webElementsWithText.stream()
.map(WebElement::getText)
.forEach(piecesOfText::add);
ArrayList<String> piecesOfTextUnique = (ArrayList<String>) piecesOfText.stream()
.distinct()
.collect(Collectors.toList());
ArrayList<Integer> uniqueIndeces=new ArrayList<>();
for (int i=0;i< piecesOfTextUnique.size();i++) {
uniqueIndeces.add(piecesOfText.indexOf(piecesOfTextUnique.get(i)));
}
This one does the job, but can someone suggest a more concise/elegant solution?
You can use collect method instead of Collection#add-ing in forEach.
You can not assume that Collectors.toList returns java.util.ArrayList. It may cause ClassCastException.
piecesOfTextUnique is (semantically) equivalent with piecesOfText.distinct(), so piecesOfText can be inlined.
Last for loop can be replaced with IntStream.
So, final result is:
ArrayList<WebElement> webElementsWithText = getWebElements();
List<String> piecesOfTextUnique = webElementsWithText.stream()
.map(WebElement::getText)
.distinct()
.collect(Collectors.toList());
ArrayList<Integer> uniqueIndeces = IntStream.range(0, piecesOfTextUnique.size())
.mapToObj(i -> piecesOfTextUnique.get(i))
.map(s -> piecesOfText.indexOf(s))
.collect(Collectors.toCollection(ArrayList::new));

Improving list initialization for n-elements using nested lists

I'd like to initialize a nested list without using a for-loop, the root list which is: cakeList will contain another ones eg.(100).
My code:
1. ArrayList<ArrayList<Integer>> cakeList = new ArrayList<>();
2. for (int i=0;i<100;i++) cakeList.add(new ArrayList<>());
I've tried:
import java.util.ArrayList;
public class Solution {
public static void main(String[] args) {
ArrayList<ArrayList<Integer>> cakeList = new ArrayList<>(100);
cakeList.forEach(e-> new ArrayList<>());
System.out.println(cakeList.size());
cakeList.get(0);
}
}
but as you maybe know, it complies, but throws an error al line #7 because cakeList is empty whe I try to cakeList.get(0).
IndexOutOfBoundsException: Index: 0, Size: 0
Thank you in advance!
It is convenient to use Stream::generate and Stream::limit to create predefined number of objects, so for the 2D list this would look as follows:
List<ArrayList<Integer>> list2D = Stream.generate(ArrayList<Integer>::new)
.limit(100) // Stream<ArrayList<Integer>>
.collect(Collectors.toList());
list2D.get(0).add(111); // 111 added to one entry
Also, IntStream with a fixed range may be used:
List<ArrayList<Integer>> list2D = IntStream.range(0, 100)
.mapToObj(i -> new ArrayList<Integer>())
.collect(Collectors.toList());
If it is important to have specific ArrayList<ArrayList> as a result, a collector Collectors.toCollection may be used:
ArrayList<ArrayList<Integer>> list2D = IntStream.range(0, 100)
.mapToObj(i -> new ArrayList<Integer>())
.collect(Collectors.toCollection(ArrayList::new));
There is also a method Collections.nCopies but it creates N copies of the same object which may have a side effect of applying a change in one element to all other "copy" elements in the list.
List<ArrayList<Integer>> cakeList = Collections.nCopies(100, new ArrayList<>());
cakeList.get(0).add(111); // 111 is added to "all" entries in cakeList
Your initialization is fine, but post initialization, your lists are all empty, as you would expect docs (see Constructors) .
Per the methods section of the docs,
cakeList.get(0); // "Returns the element at the specified position in this list."
Since your list is empty, there is no element at the specified position.
The only way to create an ArrayList that is non-empty is to use the constructor (see docs) which takes in a Collection.
Example:
ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
numbers.get(0); //will return 1
Your goal of not wanting to use a for-loop for initialization is understandable, but see this post for the speed comparison of the two methods. Note the method call
cakeList.forEach(e-> new ArrayList<>());
is equivalent to
Iterator<T> iter = cakeList.iterator();
while (iter.hasNext()) {
iter.next().add(new ArrayList<Integer>();
}
A conclusion you will reach is that there is no shortcut to filling arrays, it will take O(n) where n is your array size.

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]
}

remove duplicate list from an arrayList using Set

I have a List that contains duplicate ArrayList.
I'm looking for a solution to remove them.
Here is an example:
listOne = [[1, 0], [0, 1], [3, 2], [2, 3]]
This set contains duplicate List. Normally i want to get :
theListAfterTransformation = [[1, 0],[3, 2]]
Here is my tiny example, i tried to use the Set but it didn't work well.
public class Example {
public static void main( String[] args ) {
ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
ArrayList<Integer> list1 = new ArrayList<>(); list1.add(1); list1.add(0);
ArrayList<Integer> list2 = new ArrayList<>(); list2.add(0); list2.add(1);
ArrayList<Integer> list3 = new ArrayList<>(); list3.add(3); list3.add(2);
ArrayList<Integer> list4 = new ArrayList<>(); list4.add(2); list4.add(3);
lists.add(list1);lists.add(list2);lists.add(list3);lists.add(list4);
System.out.println(getUnduplicateList(lists));
}
public static ArrayList<ArrayList<Integer>> getUnduplicateList( ArrayList<ArrayList<Integer>> lists) {
Iterator iterator = lists.iterator();
Set<ArrayList<Integer>> set = new HashSet<>();
while (iterator.hasNext()){
ArrayList<Integer> list = (ArrayList<Integer>) iterator.next();
set.add(list);
}
return new ArrayList<>(set);
}
}
Note that is a tiny example from my project and it will be very hard to use a solution that change many thing in this implementation.
So take into account that the getUnduplicateList should keep the same signature. the good idea will be to change only the implementation.
This program print the same list as the input. any idea please.
A couple notes on terminology—Set is a distinct data structure from List, where the former is unordered and does not allow duplicates, while the latter is a basic, linear collection, that's generally ordered, and allows duplicates. You seem to be using the terms interchangeably, which may be part of the issue you're having: Set is probably the appropriate data structure here.
That said, it seems that your code is relying on the List API, so we can follow that along. Note that you should, in general, code to the interface (List), rather than the specific class (ArrayList).
Additionally, consider using the Arrays.asList shorthand method for initializing a list (note that this returns an immutable list).
Finally, note that a HashSet eliminates duplicates by checking if both objects have the same hashCode. Lists containing the same elements are still not considered to be the same list unless the elements appear in the same order, and will typically not be treated as duplicates. Sets, however, implement equals and hashCode in such a way that two sets containing exactly the same elements are considered equal (order doesn't matter).
Using your original starting collection, you can convert each inner-list to a set. Then, eliminate duplicates from the outer collection. Finally, convert the inner-collections back to lists, to maintain compatibility with the rest of your code (if needed). This approach will work regardless of the size of the inner-lists.
You can simulate these steps using a Stream, and using method references to convert to and from the Set, as below.
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.stream.Collectors;
public class Example {
public static void main( String[] args ) {
List<Integer> list1 = Arrays.asList(1, 0);
List<Integer> list2 = Arrays.asList(0, 1);
List<Integer> list3 = Arrays.asList(3, 2);
List<Integer> list4 = Arrays.asList(2, 3);
List<List<Integer>> lists = Arrays.asList(list1, list2, list3, list4);
System.out.println(getUnduplicateList(lists));
}
public static List<List<Integer>> getUnduplicateList(List<List<Integer>> lists) {
return lists
.stream()
.map(HashSet::new)
.distinct()
.map(ArrayList::new)
.collect(Collectors.toList());
}
}
You need to convert the inner lists to sets as well.
Another solution is to sort your lists and then run them through distinct Although this is not very efficient and you will also obtain a set of sorted lists:
Set<List<Integer>> collect = set.stream()
.map(list -> {
list.sort(Comparator.comparingInt(Integer::intValue));
return list;
})
.distinct()
.collect(Collectors.toSet());

Sort list with help of index

Can I access the index of the object in the list somehow?
myList.stream().sorted((o1, o2) -> 0).collect(Collectors.toList())
e.g.:
I'd like odd indices to be displayed first and even indices at the end.
I wouldn’t consider index based reordering operations to be actual sorting operations. E.g., no-one would consider implementing an operation like Collections.reverse(List) as a sorting operation.
An efficient method for moving elements at odd positions to the front in-place would be
public static <T> void oddFirst(List<T> list) {
final int size = list.size();
for(int index1=0, index2=list.size()/2|1; index2<size; index1+=2, index2+=2)
Collections.swap(list, index1, index2);
}
Alternatively, you may stream over the indices like in this answer, to generate a new List.
A filter may help:
List<Integer> listEvenIndicesMembers = IntStream.range(0, list.size()).filter(n -> n % 2 == 0).mapToObj(list::get)
.collect(Collectors.toList());
List<Integer> listOddIndicesMembers = IntStream.range(0, list.size()).filter(n -> n % 2 != 0).mapToObj(list::get)
.collect(Collectors.toList());
System.out.println(listEvenIndicesMembers);
System.out.println(listOddIndicesMembers);
[1, 3, 5, 7, 9, 11]
[2, 4, 6, 8, 10]
the problem is now you have 2 lists, appending one after the other will produce the same you want... am still checking the doc maybe I find something more elegant/optimized.
Edit:
Thanks to #Holger for the neat suggestion:
you can concatenate the streams doing:
List<Integer> listOptimal = IntStream
.concat(IntStream.range(0, list.size()).filter(n -> n % 2 == 0),
IntStream.range(0, list.size()).filter(n -> n % 2 != 0))
.mapToObj(list::get).collect(Collectors.toList());
System.out.println(listOptimal);
The accepted answer works, but this works too (as long as there are no duplicates in the list):
// a list to test with
List<String> list = Arrays.asList("abcdefghijklmnopqrstuvwxyz".split(""));
List<String> list2 = list.stream()
.sorted(Comparator.comparing(x -> list.indexOf(x) % 2).thenComparing(x -> list.indexOf(x)))
.collect(Collectors.toList());
list2.forEach(System.out::print);
This prints odd indices first, then the even indices
acegikmoqsuwybdfhjlnprtvxz
Just to illustrate the point Holger made in his comment.
The solution in this answer took my machine 75 ms to run.
The solution in this answer took only 3 ms.
And Holger's own answer ends up with an astonishing < 0 ms.
I think solution of ΦXocę 웃 Пepeúpa ツ should be fine for you, here's just an alternative (remember, it's just an alternative for the sake of learning, this solution should not be used in 'real life', it's just to show the possibility):
public static void main(String arg[]) {
List<String> list = Arrays.asList("A", "B", "C", "D", "E", "F", "G", "H");
List<String> listCopy = new ArrayList<>(list);
list.sort((o1, o2) -> Integer.compare(listCopy.indexOf(o2) % 2, listCopy.indexOf(o1) % 2));
System.out.println(list);
}
Output is: [B, D, F, H, A, C, E, G]
You may use the decorator pattern to store your objects plus extra information for sorting. E.g. if you have a list of strings you want to sort (add getters):
class StringWithIndex {
String text;
int index;
int evenIndex;
StringWithIndex(String text, int index) {
this.text = text;
this.index = index;
this.evenIndex = index % 2 == 0 ? 1 : -1;
}
}
And then you can sort such objects instead of strings:
List<String> strings = Arrays.asList("a", "b", "c", "d");
List<String> sorted = IntStream.range(0, strings.size())
.mapToObj(i -> new StringWithIndex(strings.get(i), i))
.sorted(comparingInt(StringWithIndex::getEvenIndex).thenComparingInt(StringWithIndex::getIndex))
.map(StringWithIndex::getText)
.collect(Collectors.toList());
This adds some overhead to create temporary objects and requires another class. But it can prove very useful as the sorting rules become more complicated.
If all you want to do is move List values around according to their index, then see my other answer.
However, the original wording of your question suggests you want to use sort() or sorted() with a comparator that takes existing position into account as well as other aspects of the value.
This would also be difficult if you just used Collections.sort(), because the Comparator used there doesn't have access to the index either.
You could map your Stream<T> to a Stream<Entry<Integer,T>> and perhaps map it back to Stream<T> when you've finished:
(This is AbstractMap.SimpleEntry -- because it exists in the standard libs -- you could also write your own Pair or use one from a 3rd party -- see A Java collection of value pairs? (tuples?) )
mystream.map(addIndex()).sorted(compareEntry()).map(stripIndex()) ...
private <T> Function<T,Entry<Integer,T>> addIndex() {
final int[] index = new int[] { 0 };
return o -> new SimpleEntry(index[0]++, o);
}
private Comparator<Entry<Integer, T>> compareEntry() {
return a,b -> {
// your comparison code here -- you have access to
// getKey() and getValue() for both parameters
};
}
private <T> Function<Entry<Integer,T>, T> stripIndex() {
return e -> e.getValue();
}
Note that addIndex(), at least, is not parallelisable. I suppose that once all the entries are tagged with indices, downstream from there things could be done in parallel.
Bonus answer - if all you want to do is create a new List containing the odd entries followed by the even entries, then using Stream is adding needless complexity.
for(int i=0; i<inlist.size(); i+=2) {
outlist.add(inlist.get(i));
}
for(int i=1; i<inlist.size(); i+=2) {
outlist.add(inlist.get(i));
}
There's also a pretty simple algorithm what will re-order the list in-place -- which you can write for yourself. Think about it -- there's a simple function to get the new index from the old index, as long as you know the list length.

Categories

Resources