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

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));

Related

Sorting multiple ArrayList synchronously

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());
}

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.

Java 8 Streams find element and add it to the start of the new List

I am wondering if this is possible to solve this problem with one line using Java Streams : i need to find a String in a list of strings and create another list where the search string (if found) will be the first string on the new List and then the rest of the String values
For Eg:
List<String> oldList = Arrays.asList("Avacado", "Apple", "Orange", "Chocolate");
So now if if search using say a filter "Chocolate" its should return a new list which will contain the elements in order "Chocolate", "Avacado",
"Apple", "Orange".
List<String> newList = oldList.stream().filter(i -> i.equals("Chocolate")).collect(Collectors.toList()) ???
You're asking for a solution using Stream. This is one:
List<String> sorted = oldList.stream()
.sorted(Comparator.comparing("Chocolate"::equals).reversed())
.collect(Collectors.toList());
System.out.println(sorted);
And it gives:
[Chocolate, Avacado, Apple, Orange]
So only "Chocolate" was moved at first index.
But actually yon don't need a Stream. The list itself is enough:
oldList.sort(Comparator.comparing("Chocolate"::equals).reversed());
System.out.println(oldList);
The result is
[Chocolate, Avacado, Apple, Orange]
... the same.
EDIT:
I've updated my answer as #Holger pointed out that it violated the contract of Comparator.compare(o1,o2). I'm using the solution Holger suggested in his comment. So he should be upvoted as I struggled two times to get a correct solution.
This approach maps each string to a boolean value which states whether the string is equal to "Chocolate". In case of equality Boolean.compare(x,y) will return 1 for x and -1 for y if the other string is not equal to "Chocolate". If x and y are equal to "Chocolate" the result is 0.
As we want to move "Chocolate" to the first index it's index has to decrease. Thus a reveresed comparator (1 => -1 and -1 => 1) is needed.
Your requerement is basically about sorting the list so you might use the sort method:
List<String> newList =
oldList.stream()
.sorted((i1,i2) -> i1.equals("Chocolate")?
-1: // untested, so may be the other way around...
i2.equals("Chocolate")?
1:
i1.compareTo(i2))
.collect(Collectors.toList())
[edit:] Thanks to #Holger we can somplify to:
List<String> newList =
oldList.stream()
.sorted(Comparator.comparing("Chocolate"::equals).reversed())
.collect(Collectors.toList())
You can do it after collecting list without targetElement and then add first postion of new list.
List<String> newList = oldList.stream().filter(i -> !i.equals("Chocolate")).collect(Collectors.toList());
if(oldList.size() != newList.size()){
newList.add(0,"Chocolate");
}
I personally don't think that Streams are suitable for this task. A procedural way to do this is more readable in my mind.
Here's my solution. The commented code is the procedural way of doing so. I used integers for convenience but strings are the same.
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// int index = list.indexOf(3);
// ArrayList<Integer> resultList = new ArrayList<>();
// resultList.add(list.get(index));
// resultList.addAll(list.subList(0, index));
// resultList.addAll(list.subList(index + 1, list.size()));
List<Integer> resultList = Stream.concat(Stream.of(3), list.stream().filter(x -> x == 3)).collect(Collectors.toList());
System.out.println(resultList);

String to ArrayList<Long>

Currently i have:
String a = "123.5950,555,5973.1,6321.905,6411.810000000001,6591.855"
I can turn it into an array list of Strings then into array list of Longs:
ArrayList<String> vals = new ArrayList<String>(Arrays.asList(a.split(","));
ArrayList<Long> longs = new ArrayList<>();
for(String ks : vals){
longs.add(Long.parseLong(ks));
}
I tried to do this with Stream to make this more 'fun' but cant seem to be successful with this:
ArrayList<Long> longs = a.stream().map(Long::parseLong).collect(Collectors.toList());
I dont think the for loop is very elegant, how can i do it with Stream?
Edit: copied to original string wrong
You need to create a stream from the result of String.split:
final List<Long> longs = Arrays
.stream(a.split(","))
.map(Long::parseLong)
.collect(Collectors.toList());
Also, Collectors.toList() will return the List interface, not the concrete implementation ArrayList.
If you really need an array list, you'll need to copy it:
new ArrayList<>(longs);
Edit:
As #shmosel pointed out, you can collect directly to an array list with Collectors.toCollection(ArrayList::new)
You can't stream a String without splitting it up. Two ways to split to stream:
Arrays.stream(a.split(","))
or
Pattern.compile(",").splitAsStream(a)
Aside from that, collect(Collectors.toList()) returns List, not ArrayList. And I'm not sure why you expect parseLong() to work on those strings.
String a = "123.5950.555,5973.1,6321.905,6411.810000000001,6591.855";
List<Double> doubleList = Arrays.stream(a.split(","))
.map(Doubles::tryParse)
.collect(Collectors.toList());
System.out.println(doubleList);
Note: this uses Doubles#tryParse from Guava.

Comparing Two ArrayLists to Get Unique and Duplicate Values

I have two ArrayLists as shown - pinklist and normallist. I am comparing both of them and finding the unique and duplicate values from both as shown below in code:
List<String> pinklist = t2.getList();
List<String> normallist = t.getList();
ArrayList<String> duplicatevalues = new ArrayList<String>();
ArrayList<String> uniquevalues = new ArrayList<String>();
for (String finalval : pinklist) {
if (pinklist.contains(normallist)) {
duplicatevalues.add(finalval);
} else if (!normallist.contains(pinklist)) {
uniquevalues.add(finalval);
}
}
I am getting the duplicateValues properly, but I am not getting the unique values.
this should do:
List<String> pinklist = t2.getList();
List<String> normallist = t.getList();
ArrayList<String> duplicates = new ArrayList<String>(normallist);
duplicates.retainAll(pinklist);
ArrayList<String> uniques = new ArrayList<String>(normallist);
uniques.removeAll(pinklist);
Explaination:
Every List can take another list as a constructor parameter, and copy it's values.
retainAll(list2) will remove all entries, that does not exist in list2.
removeAll(list2) will remove all entries, that does exist in list2.
We don't want to remove/retain on the original lists, because this will modify it, so we copy them, in the constructor.
You're ignoring finalval in your conditions, instead asking whether one list contains the other list.
You could do it like this:
// Variable names edited for readability
for (String item : pinkList) {
if (normalList.contains(item)) {
duplicateList.add(item);
} else {
uniqueList.add(item);
}
}
I wouldn't really call these "unique" or "duplicate" items though - those are usually about items within one collection. This is just testing whether each item from one list is in another. It's more like "existing" and "new" in this case, I'd say.
Note that as you're treating these in a set-based way, I'd suggest using a set implementation such as HashSet<E> instead of lists. The Sets class in Guava provides useful methods for working with sets.
Try ListUtils https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/ListUtils.html
To get duplicate values use ListUtils.intersection(list1, list2)
To get unique values you could use ListUtils.sum(list1, list2) and then subtract the duplicates list
Do it this way -
for (String finalval : pinklist)
{
if(normallist.contains(finalval))
{
// finalval is both in pinklist and in
// normallist. Add it as a duplicate.
duplicatevalues.add(finalval); // this will get you the duplicate values
}
else {
// finalval is in pinklist but not in
// normallist. Add it as unique.
uniquevalues.add(finalval); // this will get you the values which are in
// pinklist but not in normallist
}
}
// This will give you the values which are
// in normallist but not in pinklist.
for(String value : normallist) {
if(!pinklist.contains(value)) {
uniquevalues.add(value);
}
}
Using Java8 Stream API we can filter lists and get expected results.
List<String> listOne = // Your list1
List<String> listTwo = // Your list2
List<String> uniqueElementsFromBothList = new ArrayList<>();
List<String> commonElementsFromBothList = new ArrayList<>();
// Duplicate/Common elements from both lists
commonElementsFromBothList.addAll(
listOne.stream()
.filter(str -> listTwo.contains(str))
.collect(Collectors.toList()));
// Unique element from listOne
uniqueElementsFromBothList.addAll(
listOne.stream()
.filter(str -> !listTwo.contains(str))
.collect(Collectors.toList()));
// Unique element from listOne and listTwo
// Here adding unique elements of listTwo in existing unique elements list (i.e. unique from listOne)
uniqueElementsFromBothList.addAll(
listTwo.stream()
.filter(str -> !listOne.contains(str))
.collect(Collectors.toList()));
Here's my solution to the problem.
We can create a set containing elements from both the lists.
For the unique elements, using the Stream API, we can filter out the elements based on the predicates returning XOR of contains method. it will return true only for true ^ false OR false ^ true, ensuring only one of them contains it.
For the distinct elements, simply change the XOR to &&, and it'll check if both lists have the objects or not.
Code:
private static void uniqueAndDuplicateElements(List<String> a, List<String> b) {
Set<String> containsAll = new HashSet<String>();
containsAll.addAll(a);
containsAll.addAll(b);
List<String> uniquevalues = containsAll.stream()
.filter(str -> a.contains(str) ^ b.contains(str))
.collect(Collectors.toList());
List<String> duplicatevalues = containsAll.stream()
.filter(str -> a.contains(str) && b.contains(str))
.collect(Collectors.toList());
System.out.println("Unique elements from both lists: " + uniquevalues);
System.out.println("Elements present in both lists: " + duplicatevalues);
}
Why are you passing entire list to the contains method? You should pass finalval rather.

Categories

Resources