Java remove entries from list where certain attributes are duplicated - java

I'm new into programming and now facing a tricky problem:
I got an Object which contains a list, so the Object looks like the following:
public class SampleClass {
private UUID id;
private List<ValueObject> values;
// getters, constructor, etc
}
The ValueObject contains some Strings like:
public class ValueObject {
private String firstName;
private String lastName;
private String address;
// getters, constructor, etc
}
I have a SampleClass intance which contains a list of multiple ValueObjects. Some of the ValueObjects have the same firstName and lastName.
What I want to archive is that I want to filter out all ValueObject within a SampleClass object having the same firstName and lastName. And I want to keep the last (according to the encounter order) ValueObject out of each group duplicates in the list.
I've tried the following:
SampleClass listWithDuplicates = // intializing SampleClass instance
listWithDuplicates.getValues().stream()
.collect(Collectors.groupingBy(
ValueObject::getLastname,
Collectors.toList()
));
To group it by lastname but how do I find then the matching firstNames, because lastname can be equal but firstname can be different so I want to still keep it in my Object as it not equal. And then how to remove the duplicates?
Thank you for your help
Update:
The order of the list should not get affected by removing the duplicates. And the
listWithDuplicates
holds a SampleClass Object.

You can solve this problem by using four-args version of the Collector toMap(), which expects the following arguments:
keyMapper - a function which generates a Key out of a stream element;
*valueMapper - a function producing Value from a stream element;
mergeFunctino - a function responsible for resolving Values mapped to the same Key;
mapFunctory - allows to specify the required type of Map.
In case if you can't change the implementation of the equals/hashCode in the ValueObject you can introduce an auxiliary type that would serve as a Key.
public record FirstNameLastName(String firstName, String lastName) {
public FirstNameLastName(ValueObject value) {
this(value.getFirstName(), value.getLastName);
}
}
Note: if you're OK with overriding the equals/hashCode contract of the ValueObject on it's firstName and lastName then you don't the auxiliary type shown above. In the code below you can use Function.identity() as both keyMapper and valueMapper of toMap().
And the stream can be implemented like this:
SampleClass listWithDuplicates = // initializing your domain object
List<ValueObject> uniqueValues = listWithDuplicates.getValues().stream()
.collect(Collectors.toMap(
FirstNameLastName::new, // keyMapper - creating Keys
Function.identity(), // valueMapper - generating Values
(left, right) -> right // mergeFunction - resolving duplicates
LinkedHashMap::new // mapFuctory - LinkedHashMap is needed to preserve the encounter order of the elements
))
.values().stream()
.toList();

You can override the equals and hashCode methods in your ValueObject class (not tested):
public class ValueObject {
private String firstName;
private String lastName;
private String adress;
// Constructor...
// Getters and setters...
#Override
public boolean equals(Object obj) {
return obj == this ||(obj instanceof ValueObject
&& ((ValueObject) obj).firstName.equals(this.firstName)
&& ((ValueObject) obj).lastName.equals(this.lastName)
);
}
#Override
public int hashCode() {
return (firstName + lastName).hashCode();
}
}
Then all you need is to use Stream#distinct to remove the duplicates
listWithDuplicates.getValues().stream().distinct()
.collect(Collectors.groupingBy(ValueObject::getLastname, Collectors.toList()));
You can read this answer for a little more information about.

I cannot put it in a stream. But if you indeed create a equals method in the valueObject based on name and first name, you can filter based on the object. Put this in the ValueObject:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ValueObject that = (ValueObject) o;
return Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName);
}
than it should you work fine with a loop like this:
List<ValueObject> listWithoutDuplicates = new ArrayList<>();
for(var vo: listWithDuplicates){
if(!listWithoutDuplicates.contains(vo)){
listWithoutDuplicates.add(vo);
}
}
But in a stream would be nicer, .. But you can work that out if you've implemented the equals method.

I like the already provided answers, and think they give answer for you, but maybe for learning purposes, another approach could be to use a Collector that compares firstName and lastName fields of each ValueObject and then retains only the last instance of duplicates.
List<ValueObject> filteredList = listWithDuplicates.getValues().stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(
vo -> vo.getLastName() + vo.getFirstName(),
vo -> vo,
(vo1, vo2) -> vo2
),
map -> new ArrayList<>(map.values())
));
So you would group the ValueObject instances by lastName and firstName using a COllector that creates a Map and the keys for the map are the concatenated lastName and firstName. The collectingAndThen collector transforms the Map into a List of ValueObjects.

Related

Java aggregate same objects into one

I'm quite new into programming and got a tricky question.
I got an object which has multiple parameters:
public class SampleObject {
private String number;
private String valueOne;
private String valueTwo;
private String valueThree;
// getters, setters, all-args constructor
}
Every object always has non-null number attribute as well as one of three values-field. So for example, if valueOne is not null, the other two value fields valueTwo and valueThree would be null.
So here's my problem:
The SampleObject is referenced in AnotherClass which looks so:
public class AnotherClass {
private UUID id;
private List<SampleObject> sampleObjects;
// getters, setters, all-args constructor
}
I am receiving one object of AnotherClass containing multiple entities of SampleClass in a list.
What I want to do is merge all SampleObjects which got the same number into one object and provide a map, where the number is the key and value are the value parameters. For example:
Sample1(number:"1", valueOne="1", valueTwo=null, valueThree=null)
Sample2(number:"1", valueOne=null, valueTwo="2", valueThree=null)
Sample3(number:"1", valueOne=null, valueTwo=null, valueThree="3")
Sample4(number:"2", valueOne="5", valueTwo=null, valueThree=null)
Desired state:
Sample1Merged(number:"1", valueOne="1", valueTwo="2", valueThree="3")
Sample4(number:"2", valueOne="5", valueTwo=null, valueThree=null)
What I have already done is the following:
final Map<String, SampleObject> mapOfMergedSamples = new LinkedHashMap<>();
anotherClass.getSampleObjects().stream()
.sorted(Comparator.comparing(SampleObject::getNumber))
.forEach(s -> mapOfMergedSamples.put(s.getNumber(), new SampleObject(Stream.of(s.getValueOne(), s.getValueTwo())
.filter(Objects::nonNull)
.collect(Collectors.joining()), s.getValueThree()))
);
return mapOfMergedSamples;
The problem with my current try is that every number gets overwritten because they have the same key in the map (the number in the SampleObject) does someone know how can I archive my desired state?
Based on your usage of Collector.joining() I assume that you want to concatenate all non-null values without any delimiters (anyway it can be easily changed).
In order to combine SampleObject instances having the same number property, you can group them into an intermediate Map where the number would serve as Key and a custom accumulation type (having properties valueOne, valueTwo, valueThree) would be a Value (note: if you don't want to define a new type, you can put the accumulation right into the SampleObject, but I'll go with a separate class because this approach is more flexible).
Here's it might look like (for convenience, I've implemented Consumer interface):
public class SampleObjectAccumulator implements Consumer<SampleObject> {
private StringBuilder valueOne = new StringBuilder();
private StringBuilder valueTwo = new StringBuilder();
private StringBuilder valueThree = new StringBuilder();
#Override
public void accept(SampleObject sampleObject) {
if (sampleObject.getValueOne() != null) valueOne.append(sampleObject.getValueOne());
if (sampleObject.getValueTwo() != null) valueTwo.append(sampleObject.getValueTwo());
if (sampleObject.getValueThree() != null) valueThree.append(sampleObject.getValueThree());
}
public SampleObjectAccumulator merge(SampleObjectAccumulator other) {
valueOne.append(other.valueOne);
valueTwo.append(other.valueTwo);
valueThree.append(other.valueThree);
return this;
}
public SampleObject toSampleObject(String number) {
return new SampleObject(
number,
valueOne.toString(),
valueTwo.toString(),
valueThree.toString()
);
}
// getters
}
To create an intermediate Map we can use Collector groupingBy() and as its downstream Collector, in order to leverage the custom accumulation type, we can provide a custom collector, which can instantiated using factory method Collector.of().
Then we need to create a stream over the entries of the intermediate map in order to transform the Value.
Note that sorting applied in only the second stream.
AnotherClass anotherClass = // initializing the AnotherClass instance
final Map<String, SampleObject> mapOfMergedSamples = anotherClass.getSampleObjects().stream()
.collect(Collectors.groupingBy(
SampleObject::getNumber,
Collector.of(
SampleObjectAccumulator::new,
SampleObjectAccumulator::accept,
SampleObjectAccumulator::merge
)
))
.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().toSampleObject(e.getKey()),
(left, right) -> { throw new AssertionError("All keys are expected to be unique"); },
LinkedHashMap::new
));

Java 9 processing list of objects with nullable fields into another list

I have a list of objects like
#Getter
#Setter
public class Person {
private String name;
private boolean value_bool;
private String value_string;
private Integer value_integer;
private String value_text;
}
The problem is, only ONE of these fields (except for name) is actually initiated (e.g. non-null) for one Person, every other one is null, so for 4 of these values there are 4 Person instances each having a name and one of the values. How do I transform this list of Person objects into a list of value objects, preferably using streamAPI?
The boolean value_bool cannot be null.
To merge this data, first you'd want to group by name, and then collapse a List<Person> (which would represent the separate Person objects all with the same name) into a single merged Person.
To group:
Map<String, List<Person>> grouped = persons.stream()
.collect(Collectors.groupingBy(Person::getName));
Merging a List<Person> into a single Person is not particularly suitable to stream-based code. I would just make a method, it's a little complicated as you have lots of non-obvious choices to make. For example, let's say you have 2 with value_bool is false, and 1 with true. And 2 Person objects both have a non-null value_string, and they aren't the same string; which one wins, or should an exception be thrown, or should the strings be concatenated?
The fact that such questions exist, and have no obvious answers, should strongly indicate that there is no simple one-liner that could possibly do this.
Thus, something like:
public class Person {
// ....
public static Person merge(#NonNull Collection<? extends Person> persons) {
if (persons.isEmpty()) throw new IllegalArgumentException(
"Cannot merge an empty list of persons");
if (persons.size() == 1) return persons.iterator().next();
Person out = null;
for (Person person : persons) {
if (out == null) {
out = person;
continue;
}
// If any part-person is 'valueBool', the output is also.
if (person.isValueBool()) out.setValueBool(true);
// For the rest, the 'last' non-null value 'wins':
if (person.getValueString() != null) out.setValueString(person.getValueString());
// etc
}
return out;
}
}
armed with that method:
persons.stream()
.collect(Collectors.groupingBy(Person::getName))
.values()
.stream()
.map(Person::merge)
.collect(Collectors.toList());
Where that last line can be toList(); if you have JDK16.

Filter objects from a list that have the same member

I have a list of objects. The object looks like this:
public class Slots {
String slotType;
Visits visit;
}
public class Visits {
private long visitCode;
private String agendaCode;
private String scheduledTime;
private String resourceType;
private String resourceDescription;
private String visitTypeCode;
...
}
I need to find the elements that have the same agendaCode, visitTypeCode and scheduledTime and for the life of me I can't get it done.
I tried this:
Set<String> agendas = slotsResponse.getContent().stream()
.map(Slots::getVisit)
.map(Visits::getAgendaCode)
.collect(Collectors.toUnmodifiableSet());
Set<String> visitTypeCode = slotsResponse.getContent().stream()
.map(Slots::getVisit)
.map(Visits::getVisitTypeCode)
.collect(Collectors.toUnmodifiableSet());
Set<String> scheduledTime = slotsResponse.getContent().stream()
.map(Slots::getVisit)
.map(Visits::getScheduledTime)
.collect(Collectors.toUnmodifiableSet());
List<Slots> collect = slotsResponse.getContent().stream()
.filter(c -> agendas.contains(c.getVisit().getAgendaCode()))
.filter(c -> visitTypeCode.contains(c.getVisit().getVisitTypeCode()))
.filter(c -> scheduledTime.contains(c.getVisit().getScheduledTime()))
.collect(Collectors.toList());
But it's not doing what I thought it would. Ideally I would have a list of lists, where each sublist is a list of Slots objects that share the same agendaCode, visitTypeCode and scheduledTime. I struggle with functional programming so any help or pointers would be great!
This is Java 11 and I'm also using vavr.
Since you mentioned you're using vavr, here is the vavr way to solve this question.
Supposed you have your io.vavr.collection.List (or Array or Vector or Stream or similar vavr collection) of visits:
List<Visits> visits = ...;
final Map<Tuple3<String, String, String>, List<Visits>> grouped =
visits.groupBy(visit ->
Tuple.of(
visit.getAgendaCode(),
visit.getVisitTypeCode(),
visit.getScheduledTime()
)
);
Or with a java.util.List of visits:
List<Visits> visits = ...;
Map<Tuple3<String, String, String>, List<Visits>> grouped = visits.stream().collect(
Collectors.groupingBy(
visit ->
Tuple.of(
visit.getAgendaCode(),
visit.getVisitTypeCode(),
visit.getScheduledTime()
)
)
);
The easiest way is to define a new class with necessaries fields (agendaCode, visitTypeCode and scheduledTime). Don't forget about equals/hashcode.
public class Visits {
private long visitCode;
private String resourceType;
private String resourceDescription;
private Code code;
...
}
class Code {
private String agendaCode;
private String scheduledTime;
private String visitTypeCode;
...
#Override
public boolean equals(Object o) {...}
#Override
public int hashCode() {...}
}
Then you can use groupingBy like:
Map<Code, List<Slots>> map = slotsResponse.getContent().stream()
.collect(Collectors.groupingBy(s -> s.getVisit().getCode()));
Also you can just implement equals method inside Visits only for agendaCode, visitTypeCode and scheduledTime. In this case use groupingBy by s.getVisit()
I love Ruslan's idea of using Collectors::groupingBy. Nevertheless, I don't like creating a new class or defining a new equals method. Both of them coerces you to a single Collectors::groupingBy version. What if you want to group by other fields in other methods?
Here is a piece of code that should let you overcome this problem:
slotsResponse.getContent()
.stream()
.collect(Collectors.groupingBy(s -> Arrays.asList(s.getVisit().getAgendaCode(), s.getVisit().getVisitTypeCode(), s.getVisit().getScheduledTime())))
.values();
My idea was to create a new container for every needed field (agendaCode, visitTypeCode, scheludedTime) and compare slots on these newly created containers. I would have liked doing so with a simple Object array, but it doesn't work - arrays should be compared with Arrays.equals which is not the comparison method used by Collectors::groupingBy.
Please note that you should store somewhere or use a method to define which fields you want to group by.
The fields you want to group by are all strings. You can define a function which concatenate those fields values and use that as key for your groups. Example
Function<Slots,String> myFunc = s -> s.getVisit().agendaCode + s.getVisit().visitTypeCode + s.getVisit().scheduledTime;
// or s.getVisit().agendaCode +"-"+ s..getVisit().visitTypeCode +"-"+ s.getVisit().scheduledTime;
And then group as below:
Map<String,List<Slots>> result = slotsResponse.getContent().stream()
.collect(Collectors.groupingBy(myFunc));

Any collection object to hold list of combination of more than 2 elements?

Is there a collection object or a approach to hold a combination of elements?
For instance, I need to create a list that contains the combination of the elements name, age, height and weight.
Creating an object for this is not a good idea in my case. Because the number of fields keep changing.
I need to create this list to pass to a query.
Any solution?
class MyContainer {
String someString;
int someInt;
}
List <MyContainer> myList = new List<>();
Something like that!?
I donĀ“t know exactly, what you mean by "Creating an object for this is not a good idea in my case". You could as an alternative create a List<Object> and put in whatever you have or even a List<List<Object>> if you want to have a List of a number of grouped objects.
The best approach would be to make an Object with all the possible elements in it.
class myObject {
String name;
Integer age;
Float weight;
// Etc
}
Or have a base class then have another class which extends this with additional elements
class myExtendedObject extends myObject{
String streetAddress;
String city;
// etc;
}
Then if you don't have an element set it to null... you could always build your query from the object itself by including a method to return your query, juct check if its null and not include in your query (Assuming you mean an sql style query)
public String buildQuery{
String query = "Select * from blahtable Where ";
query += (name != null)?" name = " + name : "";
// etc, or what ever your query needs to be
return query
}
Other wise you could just have a method which returns a map of your elements then you know what the type of each element is based on the key
public Map<String, Object> getElements{
Map<String, Object> myMap = new HashMap<String, Object>();
if(name != null)
myMap.put("Name", name);
// etc
return myMap
}
What about just using a Map for that and use attribute name as key (e.g. Weight )?
You can use any combination of attributes you want and it would be convenient to pass such collection to the query
Consider Enum map should you require more column names type safety

Java, searching within a list of objects?

I'm a bit lost on the way to make this happen the fastest. I have a large list of objects that have basic variable attributes (with getters / setters) and I need to do a search in this list to find the objects within the list that match a given parameter
I have found how to do a regular list search but I need to, for example search for the value of the result of doing a call getName() for each object in the list and get objects that have a result that matches my input.
Something like below where the third argument is the result of the method call and the second is what I am trying to find.
int index = Collections.binarySearch(myList, "value", getName());
Any advice is appreciated
If you just as a one-off operation need to find the object(s) whose getName() is a particular value, then there's probably not much magic possible: cycle through the list, call getName() on each object, and for those that match, add them to your list of results.
If getName() is an expensive operation and there's some other way of a-priori working out if a given object definitely won't return a matching value, then obviously you can build in this 'filtering' as you cycle through.
If you frequently need to fetch objects for a given getName(), then keep an index (e.g. in a HashMap) of [result of getName()->object -> list of matches]. You'll need to decide how and if you need to keep this "index" in synch with the actual list.
See also the other proposition to use binarySearch() but to keep the list maintained. This way, inserts are more expensive than with a map and unsorted list, but if inserts are infrequent compared to lookups, then it has the advantage of only needing to maintain one structure.
Take a look at the binarySearch that takes a comparator:
public static int binarySearch(List list,
T key,
Comparator c)
So you would do something like:
class FooComparator
implements Comparator<Foo>
{
public int compare(T a, T b)
{
return (a.getName().compareTo(b.getName());
}
}
int index = Collections.binarySearch(myList, "value", new FooComparator());
You will need to first sort the list of course (Collections.sort takes a Comaprator as well...).
I know anonymous inner classes are not fashion anymore, but while Java 8 arrives, you can create something like this:
1.- Create a search method that iterates the collection and pass an object that tells you if your object is to be returned or not.
2.- Invoke that method and create an anonymous inner class with the criteria
3.- Get the new list in separate variable.
Something like this:
result = search( aList, new Matcher(){ public boolean matches( Some some ) {
if( some.name().equals("a")) {
return true;
}
}});
Here's a working demo:
import java.util.*;
class LinearSearchDemo {
public static void main( String ... args ) {
List<Person> list = Arrays.asList(
Person.create("Oscar", 0x20),
Person.create("Reyes", 0x30),
Person.create("Java", 0x10)
);
List<Person> result = searchIn( list,
new Matcher<Person>() {
public boolean matches( Person p ) {
return p.getName().equals("Java");
}});
System.out.println( result );
result = searchIn( list,
new Matcher<Person>() {
public boolean matches( Person p ) {
return p.getAge() > 16;
}});
System.out.println( result );
}
public static <T> List<T> searchIn( List<T> list , Matcher<T> m ) {
List<T> r = new ArrayList<T>();
for( T t : list ) {
if( m.matches( t ) ) {
r.add( t );
}
}
return r;
}
}
class Person {
String name;
int age;
String getName(){
return name;
}
int getAge() {
return age;
}
static Person create( String name, int age ) {
Person p = new Person();
p.name = name;
p.age = age;
return p;
}
public String toString() {
return String.format("Person(%s,%s)", name, age );
}
}
interface Matcher<T> {
public boolean matches( T t );
}
Output:
[Person(Java,16)]
[Person(Oscar,32), Person(Reyes,48)]
To do this in a more scalable way, without simply iterating/filtering objects, see this answer to a similar question: How do you query object collections in Java (Criteria/SQL-like)?
If the objects are immutable (or you at least know their names won't change) you could create an index using a HashMap.
You would have to fill the Map and keep it updated.
Map map = new HashMap();
map.put(myObject.getName(), myObject);
... repeat for each object ...
Then you can use map.get("Some name"); to do lookup using your index.
One library I'm familiar with is Guava -- you can compose its Predicate to pull out items from an Iterable. There's no need for the collection to be pre-sorted. (This means, in turn, that it's O(N), but it's convenient.)

Categories

Resources