Check if an arraylist contains two strings - java

I have an pojo class like the one below
public CategoryModel {
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
}
I have an arraylist created like the one below.
List<CategoryModel> variantCategoryModelList = new ArrayList<>();
CategoryModel cat1= new CategoryModel();
cat1.setName(TEST1);
CategoryModel cat2= new CategoryModel();
cat2.setName(TEST1);
list.add(cat1);
list.add(cat2);
I have to check, if the value "TEST1" & "TEST2" present in the list and return "true" if both values present in the "list" and I tried something like the one below, though my "list" has both the values, its returning false.Could you please help me check what I am doing wrong btw I am using JDK 11.
final Optional<CategoryModel> optionalData = variantCategoryModelList.stream().
filter(valueData -> TEST1.equalsIgnoreCase(valueData.getName())
&& TEST2.equalsIgnoreCase(valueData.getName())).findFirst();
if(optionalData.isPresent()){
return true;
}

You could map your CategoryModel to name and collect to list of strings and call List.containsAll :
return variantCategoryModelList.stream()
.map(CategoryModel::getName)
.collect(Collectors.toList())
.containsAll(Arrays.asList("TEST1","TEST2"));

Set would be a more natural (and faster) data structure:
return variantCategoryModelList.stream()
.map(CategoryModel::getName)
.collect(Collectors.toSet())
.containsAll(Set.of("TEST1", "TEST2"));
Your problem was and (&&) instead of or.
So:
Set<String> soughtNames = Set.of("TEST1", "TEST2");
return variantCategoryModelList.stream()
.filter(cm -> soughtNames.contains(cm.getName()))
.distinct()
.count() == 2L;
As #fps commented, distinct() is needed on a list to prevent ["Test1", "Test1"] to be accepted, or ["Test1", "Test1", "Test2"] failing.
This is obviously inefficient as it will - having found 2 entries -, still walk to the end.
You want:
Set<String> soughtNames = Set.of("TEST1", "TEST2");
return soughtNames.stream()
.allMatch(soughtName ->
variantCategoryModelList.stream()
.anyMatch(cm -> soughtName.equals(cm.getName()));
Or a bit retro-style:
return
variantCategoryModelList.stream()
.anyMatch(cm -> "TEST1".equals(cm.getName())) &&
variantCategoryModelList.stream()
.anyMatch(cm -> "TEST2".equals(cm.getName()));

Here's a way to do it:
Set<String> set = Set.of("TEST1", "TEST2");
boolean result = list.stream()
.filter(cat -> set.contains(cat.getName().toUpperCase())
.distinct()
.limit(2)
.count() == 2L;
This streams the list of categories, then keeps only those categories whose name is either TEST1 or TEST2. We then remove duplicates and stop after we've found two (already distinct) category names. This ensures short-circuiting. Finally, we check if we have exactly two elements at the end.

Related

How to filter list based on custom criteria?

I have a java list consisting of objects. Most of the objects have common fields and I need to keep just the one object from the list of candidates that have a specific field set. How can I achieve this? Example
class A{
String field1;
String field2;
String field3;
LocalDate dateField;
}
With the following values;
A first = new A("field1","field2","field3",null);
A second = new A("field1","field2","field3",LocalDate.now());
A third= new A("field1","field2","field3",LocalDate.now().plusMonths(3));
A forth= new A("4","5","6",LocalDate.now().plusMonths(3));
A fifth = new A("7","8","9",LocalDate.now().plusMonths(3));
I need to write a method that returns a list consisting of second, forth and fifth. So if field1 field2 and field3 are identical, I need to keep the minimum localdate field. How to achieve this?
I understand you have a List<A> of objects of type A.
It seems want to filter the list, querying it for items A that match some requirement. What kinds of things you want to search when filtering that list isn't very clear.
List<A> items = ...; // setup the items
List<A> items_filtered = items.stream()
.filter( x -> x.field1.equals("value") )
.collect(Collectors.toList());
List<A> items_filtered_2 = items.stream()
.filter( x -> !x.field2.equals("other_value") )
.collect(Collectors.toList());
These filters can be applied to any list, including list that is the result of a previous filter, or you can combine two checks in the same filter.
List<A> fitlered_both = items.stream()
.filter( x -> x.field1.equals("value") && !x.field2.equals("other_value") )
.collect(Collectors.toList());
You can try this
public static void main(String[] args) {
A first = new A("field1","field2","field3",null);
A second = new A("field1","field2","field3",LocalDate.now());
A third= new A("field1","field2","field3",LocalDate.now().plusMonths(3));
A forth= new A("4","5","6",LocalDate.now().plusMonths(3));
A fifth = new A("7","8","9",LocalDate.now().plusMonths(3));
List<A> collect = Stream.of(first, second, third, forth, fifth)
.collect(
Collectors.groupingBy(a -> Objects.hash(a.field1, a.field2, a.field3),
Collectors.minBy((o1, o2) -> {
if(o1 == null || o2 == null || o1.dateField == null || o2.dateField == null){
return 1;
}
return o1.dateField.compareTo(o2.dateField);
})))
.values().stream().map(Optional::get).collect(Collectors.toList());
System.out.println(collect);
}
You want to group the objects by similar objects first, Objects.hash(field1, field2, field3) will group objects based on field1, field2, and field3.
Next you want to sort the grouping using Localdate.
Next, we collect the first elements of each group, which is our answer.
I would suggest to write a comparator and sort the list using it. Then you will be able to retrieve all the desired objects within one pass

Java 8 Filter List of POJO based on a condition

I need to extract a sublist where one of the attribute in list of POJO matches with the input at first occurrence.
class POJO {
String name;
#Nullable
String location;
}
Given a list of POJO, I need to get the List where location matches to input location. Once we find the first occurrence of the location, We need to extract the list from that point to end of list.
List<POJO> ans = Lists.newArrayList();
Iterator<POJO> iterator = input
.iterator();
while (iterator.hasNext()) {
POJO pojo = iterator.next();
if (Objects.nonNull(pojo.location)) {
String location = pojo.location
//Got the first location matching with input, From here, Get all the elements from List
if (inputLocation.equals(location) {
ans.add(pojo);
break;
}
}
}
while (iterator.hasNext()) {
POJO pojo = iterator.next();
if (Objects.nonNull(pojo.location)) {
ans.add(pojo);
}
}
Can anyone suggest any better apporach? (if possible using Streams)?
If you're on Java 9+, you can use dropWhile:
List<POJO> ans = input.stream().dropWhile(pojo -> pojo.location == null
|| !inputLocation.equals(pojo.location))
.filter(loc -> loc.location != null)
.collect(Collectors.toList());
On Java 8, you'd have to do it in two steps: find the index of your element and then pull the sub-list. This combines the two in one statement:
List<POJO> ans = input.subList(IntStream.range(0, input.size())
.filter(i -> input.get(i).location != null &&
input.get(i).location.equals("inputLocation"))
.findFirst()
.orElseGet(input::size), input.size());
Just be aware of subList's behavior. If you want to break the link from input, you may want to construct a new list from the result.
You can get the index of element using IntStream like:
int index = IntStream.range(0, answers.size())
.filter(i -> inputLocation.equals(answers.get(i).getLocation())).findFirst().orElse(-1);
Then extract a sub list using above index like below if index is greater than -1 :
answers.subList(index, answers.size())
You are looking for filter and dropWhile operations:
List<Pojo> list = List.of(new Pojo("a","USA"),new Pojo("b","UK"),
new Pojo("c","USA"), new Pojo("d","UK"));
List<Pojo> remaining = list.stream()
.filter(p->p.location()!=null)
.dropWhile(p1->!p1.location().equals("UK"))
.collect(Collectors.toList());
System.out.println(remaining);
Output:
[Pojo[name=b, location=UK], Pojo[name=c, location=USA], Pojo[name=d, location=UK]]
In Java9 dropWhile was added in streams that will drop elements until the condition is true, and then return the subsequent elements
https://docs.oracle.com/javase/9/docs/api/java/util/stream/Stream.html#dropWhile-java.util.function.Predicate-
input.stream().dropWhile(item -> ...).collect(...);
This is not an elegant solution compared to the rest of the answer but just a different flavor
Picked input data from this answer
Logic is once we encounter the first matching condition, set the flag, and use it in the stream filter stage.
Since we need variable to be effective final in lambda, final Boolean[] isFirstEncounter = {false}; one of the bad workaround.
Also following code should be use in sequential stream and not in parallel stream.
final Boolean[] isFirstEncounter = {false};
List<POJO> input = List.of(new POJO("a", "USA"), new POJO("b", "UK"), new POJO("c", "USA"), new POJO("d", "UK"));
List<POJO> ans = input.stream()
.filter(Objects::nonNull)
.filter(p -> {
if (!isFirstEncounter[0] && "UK".equals(p.location) {
isFirstEncounter[0] = true; // true means accept all the elements
}
return isFirstEncounter[0]; // false means filter all the elements
}
).collect(Collectors.toList());
System.out.println(ans);
//output
// [Solution.POJO(name=b, location=UK), Solution.POJO(name=c, location=USA), Solution.POJO(name=d, location=UK)]

Updating a subsection of a list with an "id" field

I am trying to learn how to use the lambda functions for sleeker code but struggling to make this work.
I have two lists. The "old" list is always shorter or the same length as the "updated list".
I want to take the objects from the "updated list" and overwrite the "stale objects" in the shorter "old list".
The lists have a unique field for each object.
For example, it is a bit like updating books in a library with new editions. The UUID (title+author) remains the same but the new object replaces the old on the shelf with a new book/object.
I know I could do it the "long way" and make a HashMap<MyUniqueFieldInMyObject, MyObject> and then take the new List<MyUpdatedObjects> and do the same.
I.e. Have HashMap<UniqueField, MyOldObject> and HashMap<UniqueField, MyUpdatedObject>, then iterate over the old objects with a pseudo "if updated objects have an entry with the same key, overwrite the value with the updated value"...
But...
Is there a "nicer" shorted way to do this with functional lambda statements?
I was thinking along the lines of:
List<MyObject> updatedList;
List<MyObject> oldList;
updatedList.forEach(MyObject -> {
String id = MyObject.getId();
if (oldList.stream().anyMatcher(MyObject ->
MyObject.getId().matches(id)) {
//Do the replacement here? If so...how?
}
}
Which is where I am lost!
Thanks for any guidance.
If you want to update the list in place rather than making a new list, you can use List.replaceAll:
oldList.replaceAll(old ->
updateListe.stream()
.filter(updated -> updated.getId().equals(old.getId())
.findFirst()
.orElse(old)
);
The main problem with this solution is that its complexity is O(size-of-old*size-of-updated). The approach you described as "long way" can protect you from having to iterate over the entire updated list for every entry in the old list:
// note that this will throw if there are multiple entries with the same id
Map<String, MyObject> updatedMap = updatedList.stream()
.collect(toMap(MyObject::getId, x->x));
oldList.replaceAll(old -> updatedMap.getOrDefault(old.getId(), old));
I recommend you to iterate over the oldList - the one you want to update. For each of the object iterated match the equivalent one by its id and replace it using Stream::map. If an object is not found, replace it with self (doesn't change the object) using Optional::orElse.
List<MyObject> newList = oldList
.stream() // Change values with map()
.map(old -> updatedList.stream() // Iterate each to find...
.filter(updated -> old.getId() == updated.getId()) // ...by the same id
.findFirst() // Get new one to replace
.orElse(old)) // Else keep the old one
.collect(Collectors.toList()); // Back to List
List<Foo> updatedList = List.of(new Foo(1L, "new name", "new desc."));
List<Foo> oldList = List.of(new Foo(1L, "old name", "old desc."));
List<Foo> collect = Stream.concat(updatedList.stream(), oldList.stream())
.collect(collectingAndThen(toMap(Foo::getId, identity(), Foo::merge),
map -> new ArrayList(map.values())));
System.out.println(collect);
This will print out:
[Foo{id=1, name='new name', details='old desc.'}]
In Foo::merge you can define which fields need update:
class Foo {
private Long id;
private String name;
private String details;
/*All args constructor*/
/*getters*/
public static Foo merge(Foo newFoo, Foo oldFoo) {
return new Foo(oldFoo.id, newFoo.name, oldFoo.details);
}
}
I think it's best to add the objects to be updated into a new list to avoid changing a list you are streaming on and then you can simply replace the old with the new list
private List<MyObject> update(List<MyObject> updatedList, List<MyObject> oldList) {
List<MyObject> newList = new ArrayList<>();
updatedList.forEach(object -> {
if (oldList.stream().anyMatch(old -> old.getUniqueId().equals(object.getUniqueId()))) {
newList.add(object);
}
}
return newList;
}

Split a List into chunks by an Element

I have a collection of Objects (Pos) with this model :
public class Pos {
private String beforeChangement;
private String type;
private String afterChangement;
}
The list of objects is like this :
[
Pos(beforeChangement=Découvrez, type=VER, afterChangement=découvrir),
Pos(beforeChangement=un, type=DET, afterChangement=un),
Pos(beforeChangement=large, type=ADJ, afterChangement=large),
Pos(beforeChangement=., type=SENT, afterChangement=.),
Pos(beforeChangement=Livraison, type=NOM, afterChangement=livraison),
Pos(beforeChangement=et, type=KON, afterChangement=et),
Pos(beforeChangement=retour, type=NOM, afterChangement=retour),
Pos(beforeChangement=., type=SENT, afterChangement=.),
Pos(beforeChangement=achetez, type=VER, afterChangement=acheter),
Pos(beforeChangement=gratuitement, type=ADV, afterChangement=gratuitement),
Pos(beforeChangement=., type=SENT, afterChangement=.),
Pos(beforeChangement=allez, type=VER, afterChangement=aller),
Pos(beforeChangement=faites, type=VER, afterChangement=faire),
Pos(beforeChangement=vite, type=ADV, afterChangement=vite),
Pos(beforeChangement=chers, type=ADJ, afterChangement=cher),
Pos(beforeChangement=clients, type=NOM, afterChangement=client)]
Pos(beforeChangement=., type=SENT, afterChangement=.)
]
I want to split this List of Objects by the the field of beforeChangement or afterChangement == "." to have this format (A List of List) List<List<SOP>> :
[
[Pos(beforeChangement=Découvrez, type=VER, afterChangement=découvrir),
Pos(beforeChangement=un, type=DET, afterChangement=un),
Pos(beforeChangement=large, type=ADJ, afterChangement=large)],
[Pos(beforeChangement=Livraison, type=NOM, afterChangement=livraison),
Pos(beforeChangement=et, type=KON, afterChangement=et),
Pos(beforeChangement=retour, type=NOM, afterChangement=retour)],
[Pos(beforeChangement=achetez, type=VER, afterChangement=acheter),
Pos(beforeChangement=gratuitement, type=ADV, afterChangement=gratuitement)],
[Pos(beforeChangement=allez, type=VER, afterChangement=aller),
Pos(beforeChangement=faites, type=VER, afterChangement=faire),
Pos(beforeChangement=vite, type=ADV, afterChangement=vite),
Pos(beforeChangement=chers, type=ADJ, afterChangement=cher),
Pos(beforeChangement=clients, type=NOM, afterChangement=client)]
]
Is like performing an inverse flatMap to have a List of Array or List (Chunks) after splitting by a field of object that is the String "."
do you have any idea about how to do it using Streams ?
Thank you guys
hmm, I would like to solve your problem using a simple loop like this :
List<List<Pos>> result = new ArrayList<>();
List<Pos> part = new ArrayList<>();
for(Pos pos : listPos){
if(pos.getBeforeChangement().equals(".") || pos.getAfterChangement().equals(".")){
result.add(part);//If the condition is correct then add the sub list to result list
part = new ArrayList<>();// and reinitialize the sub-list
} else {
part.add(pos);// else just put the Pos object to the sub-list
}
}
//Just in case the listPos not end with "." values then the last part should not be escaped
if(!part.isEmpty()){
result.add(part);
}
Note, the question is not clear enough your Object class is named SOP and the List of Object is Pos which one is correct, In my answer I based to public class Pos{..} instead of public class SOP{..}.
take a look at the Ideone demo
with StreamEx library you can use groupRuns method to split list for list of lists.
For example:
List<List<Pos>> collect = StreamEx.of(originalList.stream())
.groupRuns((p1, p2) -> !(".".equals(p2.beforeChangement) || ".".equals(p2.afterChangement)))
.collect(Collectors.toList());
Method groupRuns returns Stream of lists. In example above it are lists where first element with ..
You can filter out these elements later. For example using map method:
StreamEx.of(originalList.stream())
.groupRuns((p1, p2) -> !(".".equals(p2.beforeChangement) || ".".equals(p2.afterChangement))) // returns Stream of lists with '.' element
.map(l -> l.stream()
.filter(p -> !(".".equals(p.beforeChangement) || ".".equals(p.afterChangement))) //filter out element with '.'
.collect(Collectors.toList()))
.filter(l -> !l.isEmpty()) // filter out empty lists
.collect(Collectors.toList());
Well, I would be conservative here, and I wouldn't use Streams (although it's possible).
The following snippet does what you need:
List<Pos> posList;
List<List<Pos>> result = new ArrayList<>();
boolean startNewSentence = true;
for (Pos pos : posList) {
if (startNewSentence) {
result.add(new ArrayList<>());
}
startNewSentence = isPeriod(pos);
if (!startNewSentence) {
result.get(result.size() - 1).add(pos);
}
}
where:
boolean isPeriod(Pos pos) {
return ".".equals(pos.beforeChangement()) || ".".equals(pos.afterChangement());
}
PS. Note there's no such word as "changement" in English. The noun from verb "change" is also "change".
Collectors.groupingBy() may help you.
Let's say your object name for the list is SOP object is listSOP. Then
List<SOP> listSOP = new ArrayList<>();
.... populate your list.
Map<String,List<SOP>> map = listSOP.stream().collect(Collectors.groupingBy(SOP::getBeforeChangement)
This should return a Map of type <String(BeforeChangement), List<SOP>>.
Here getBeforeChangement is the getter method in your SOP class which should return value of variable beforeChangement

How to get a list output from forEach loop in Java 8 Streams

I have two different lists of same objects but different properties and with a common identifier in those objects. I would like to iterate over the first list and get the corresponding object from the second (which has common properties) and then wrap those objects around and finally add that object to a list using Java Streams.
This is the example I have taken.
private class Person {
private String name;
private boolean isSenior;
private Person(String name, boolean isSenior) {
this.name = name;
this.isSenior = isSenior;
}
public String getName() {
return name;
}
public boolean isSenior() {
return isSenior;
}
#Override
public String toString() {
return name + ": " + isSenior;
}
}
private class PersonWrapper {
private Person jrPerson;
private Person srPerson;
private PersonWrapper(Person jrPerson, Person srPerson) {
this.jrPerson = jrPerson;
this.srPerson = srPerson;
}
public Person getJrPerson() {
return jrPerson;
}
public Person getSrPerson() {
return srPerson;
}
#Override
public String toString() {
return jrPerson.toString() + "-" + srPerson.toString();
}
}
Now, in the main class, I will create two list instances like this
List<Person> jrPersons = new ArrayList<>();
List<Person> srPersons = new ArrayList<>();
and add the objects in the following manner
jrList.add(new Person("John", false));
jrList.add(new Person("Paul", false));
jrList.add(new Person("Mike", false));
seniorList.add(new Person("John", true));
seniorList.add(new Person("Paul", true));
seniorList.add(new Person("Mike", true));
Now, I want to iterate over the jrList and find the corresponding Person object in the srList (same name). Then I would wrap these objects as PersonWrapper and that object to a list.
So far, this is what I have been doing
List<PersonWrapper> wrapperList = new ArrayList<>();
jrList.forEach(jr -> seniorList.stream().filter(sr -> jr.getName().equals(sr.getName())).map(sr -> new PersonWrapper(jr, sr)).collect(Collectors.toList()));
Now, I would like to know how the Collectors.toList() can be substituted by wrapperList or how the output from Collectors.toList() be added to wrapperList.
Please help me in achieving this.
Instead of using a forEach just use streams from the beginning:
List<PersonWrapper> wrapperList = jrList.stream()
.flatMap(jr -> seniorList.stream()
.filter(sr -> jr.getName().equals(sr.getName()))
.map(sr -> new PersonWrapper(jr, sr))
)
.collect(Collectors.toList());
By using flatMap you can flatten a stream of streams (Stream<Stream<PersonWrapper>>) into a single stream (Stream<PersonWrapper>)
If you can't instantiate wrapperList by yourself or really need to append to it. You can alter above snippet to following:
List<PersonWrapper> wrapperList = new ArrayList<>();
jrList.stream()
.flatMap(jr -> seniorList.stream()
.filter(sr -> jr.getName().equals(sr.getName()))
.map(sr -> new PersonWrapper(jr, sr))
)
.forEach(wrapperList::add);
While Lino's answer is certainly correct. I would argue that if a given person object in jrList can only ever have one corresponding match in seniorList maximum, in other words, if it's a 1-1 relationship then you can improve upon the solution given by Lino by finding the first match as follows:
List<PersonWrapper> resultSet = jrList.stream()
.map(p -> seniorList.stream()
.filter(sr -> p.getName().equals(sr.getName()))
.findFirst()
.map(q -> new PersonWrapper(p, q))
.get())
.collect(Collectors.toList());
or if there is no guarantee that each person in jrList will have a corresponding match in seniorList then change the above query to:
List<PersonWrapper> resultSet = jrList.stream()
.map(p -> seniorList.stream()
.filter(sr -> p.getName().equals(sr.getName()))
.findFirst()
.map(q -> new PersonWrapper(p, q))
.orElse(null))
.filter(Objects::nonNull)
.collect(Collectors.toList());
The difference is that now instead of calling get() on the result of findFirst() we provide a default with orElse in case findFirst cannot find the corresponding value and then we filter the null values out in the subsequent intermediate operation as they are not needed.
Replace your looping logic with below code.
jrList.forEach(jr -> seniorList.stream().filter(sr -> jr.getName().equals(sr.getName()))
.map(sr -> wrapperList.add(new PersonWrapper(jr, sr))).collect(Collectors.toList()));

Categories

Resources