Split a List into chunks by an Element - java

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

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

Check if an arraylist contains two strings

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.

Java - Filter a list based on multiple values

I am trying to filter out a list based on values. I have two List. One is a list of names which i want to remove i.e present in animalList. And another is the main primary list AnimalPrimaryDataPojoFilterList from where i have to remove the object which matches the names from animalList. Now i do have the solution but i think it takes lot of time. Below is the code. I am using Java 8. Can it be optimised?
if(animalList!=null && animalList.size()>0)
{
for(AnimalFilterPojo dtoObject:animalList)
{
if(!dtoObject.getApproved())
{
for(AnimalPrimaryDataPojo mainDtoObject: AnimalPrimaryDataPojoFilterList)
{
if(mainDtoObject.getAnimalName().equalsIgnoreCase(dtoObject.getValue()))
{
AnimalPrimaryDataPojoFilterList.remove(mainDtoObject);
}
}
}
}
Use removeAll() method.
AnimalPrimaryDataPojoFilterList.removeAll(animalList);
It will remove the objects of animalList from AnimalPrimaryDataPojoFilterList
N.B: You need to implement hashCode() and equals() method in AnimalFilterPojo
You can use Java 8 streams to filter the list. In below example Parent is the object which has abc property of type String. We are filtering List<Parent> objs using List<String> names
public class ListFilterDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
List<Parent> objs = new ArrayList<>();
List<Parent> filtersObjs = objs.parallelStream().filter((obj) -> names.contains(obj.getAbc())).collect(Collectors.toList());
}
}
class Parent {
private String abc;
public Parent(String abc) {
this.abc = abc;
}
public String getAbc() {
return this.abc;
}
}
You can try this:
if(animalList!=null && animalList.size()>0)
animalList.removeIf(animal ->
AnimalPrimaryDataPojoFilterList.stream()
.filter(filter -> !filter.getApproved())
.map(AnimalFilter::getValue)
.collect(Collectors.toList()).contains(animal.getAnimalName()));
to explain the code: here we use removeIf() on the List to remove the objects using a Predicate that is a lambda that receives the animal and filters the list by removing the elements by name where name is taken from a list generated as a selection of the AnimalPrimaryDataPojoFilterList of the elments that have the approved flag (the second filter), extracting the value (using the map) and constructing a list out of it using a Collector.
The portion:
AnimalPrimaryDataPojoFilterList.stream()
.filter(filter -> !filter.getApproved())
.map(AnimalFilter::getValue)
.collect(Collectors.toList())
generates the list to be used as a filter
animalList.removeIf(animal ->
<generated list>.contains(animal.getAnimalName()));
uses list generated in place to apply the filter.
Beware that this of course modifies the list you have
Besides, you should not start a variable with a capital letter like you did for AnimalPrimaryDataPojoFilterList.
you can use removeIf then use AnimalPrimaryDataPojoFilterList as the source in which case you'll need to invert your logic within the if block i.e:
if(animalList != null && animalList.size() > 0){
AnimalPrimaryDataPojoFilterList.removeIf(x ->
animalList.parallelStream()
.filter(e -> !e.getApproved() && x.getAnimalName().equalsIgnoreCase(e.getValue())).findAny().orElse(null) != null);
}

Filtering objects with circular reference to itself (ObjectA has property List<ObjectA>)

I have a large json structure with nested values that I have converted into a list of objects to work with. I'd like to filter out all objects that don't contain a specific property value. Problem is though, that so far all I've come up with is a for loop within a for loop within a for loop (and that's given we know the json structure is only three nested levels). I only want to filter out the objects that do contain an integer (if it's null, it could be a parent containing something valid) or parents that are empty). If I try to stream with flattened - I can filter out all my objects and nested objects but won't I lose my structure?
quick eg.
public class ObjectA {
Integer id;
List<ObjectA> sublist;
}
List<ObjectA> fullList;
Set<Integer> keeptheseIntegers;
for (ObjectA obj : fullList) {
if (obj.getId() != null && !keeptheseIntegers.contains(obj.getId()){
fullList.remove(obj);
} else if (obj.getId() == null && obj.getSubList().size() > 0) {
for (ObjectA subObj : obj.getSubList()){
(same thing as above)
}
}
edit - I did realize later that the remove was not working properly and used iterator.remove. still same logical issue though
First: instead of manipulating your original structure (remove unwanted) I would collect the wanted items into an own list during the algorithm.
Second: Iterating through nested structures is a good candidate for the recursive pattern.
Third: Using java 8 I would implement it using streams and lambdas.
Something like this:
public class ObjectA
{
Integer id;
List<ObjectA> sublist;
}
private static final Set<Integer> keeptheseIntegers = new HashSet<>();
static
{
keeptheseIntegers.add( 1 );
}
private List<ObjectA> filter( List<ObjectA> list)
{
List<List<ObjectA>> subLists = new ArrayList<>();
List<ObjectA> result = list.stream()
// get all objects with sublist and add the sublist to interim subLists:
.peek( obj -> {
if ( obj.sublist == null )
{
// assert your assumption that Integer is assigned
if ( obj.id == null )
{
throw new IllegalArgumentException();
}
}
else
{
subLists.add( obj.sublist );
}
} )
// filter for the objects you want in result:
.filter( (obj -> obj.id != null && keeptheseIntegers.contains(obj.id)))
// and convert the resulting stream to a list:
.collect( Collectors.toList());
// call this method recusively for all found sublists:
subLists.forEach( i -> result.addAll(filter( i)) );
return result;
}
and somewher in your main program flow you call it:
...
List<ObjectA> fullList = new ArrayList<>();
List<ObjectA> objWithInt = filter( fullList);
// process the received list. Your original fullList is unchanged.

Categories

Resources