How to filter object lists and create another filtered lists from it - java-8

I receive a List of MediaDTO and this Object has two attributes:
String sizeType and String URL.
In 'sizeType' comes the image´s size: small, medium, large, and thumbnail.
So I have to filter the sizeType of these objects and create 4 new lists based on them.
This is how I get the List<MediaDTO> mediaDTO:
medias=[MediaDTO(sizeType=THUMBNAIL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/thumbnail/celular-iphone-11-azul.png), MediaDTO(sizeType=SMALL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/small/celular-iphone-11-azul.png), MediaDTO(sizeType=SMALL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/medium/celular-iphone-11-azul.png), MediaDTO(sizeType=LARGE, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/large/celular-iphone-11-azul.png), MediaDTO(sizeType=THUMBNAIL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/thumbnail/celular-iphone-11-vermelho.png), MediaDTO(sizeType=SMALL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/small/celular-iphone-11-vermelho.png), MediaDTO(sizeType=MEDIUM, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/medium/celular-iphone-11-vermelho.png), MediaDTO(sizeType=LARGE, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/large/celular-iphone-11-vermelho.png)]
I achieved filtering for one of the sizes. This works!
However, I could not figure out how can I filter over the 4 sizes and create 4 new lists of it.
If I fix some error another appears ... so I´m really stuck.
And by the way I´ve been searching for a solution on the internet and in the forum for a couple of days but did´nt find something that fits.
If someone might help, I´d really be grateful.
I was thinking about using a 'forEach' to filter but even like that, I could filter just one size.
Thanks in advance.
**This is what I got till now: **
public class ProcessProductDTO {
String processId;
OperationProcess operation;
String categoryId;
ProductDTO productDTO;
}
public class ProductDTO {
String id;
Boolean active;
String displayName;
String longDescription;
List<MediaDTO> medias;
List<AttributeDTO> attributes;
}
public class MediaDTO {
String sizeType;
String liveloUrl;
}
public Properties toOccProductPropertiesDTO(ProcessProductDTO processProductDTO) throws JsonProcessingException {
String pSpecs = convertAttributes(processProductDTO.getProductDTO().getAttributes());
//List<String> medias = convertMedias(processProductDTO.getProductDTO().getMedias());
return Properties.builder()
.id(processProductDTO.getProductDTO().getId()) .active(processProductDTO.getProductDTO().getActive())
.listPrices(new HashMap())
.p_specs(pSpecs)
//.medias(medias)
.displayName(processProductDTO.getProductDTO()
.getDisplayName())
.longDescription(processProductDTO.getProductDTO().getLongDescription())
.build(); }
private String convertAttributes(List<AttributeDTO> attributes) throws JsonProcessingException {
Map<String, String> attribs = attributes.stream()
.collect(Collectors.toMap(AttributeDTO::getName, AttributeDTO::getValue));
return objectMapper.writeValueAsString(attribs);
}
private List<MediaDTO> convertMedias(ProcessProductDTO processProduct, List<MediaDTO> mediaDTO){
List<MediaDTO> filteredList = processProduct.getProductDTO().getMedias();
Set<String> filterSet = mediaDTO.stream().map(MediaDTO::getSizeType).collect(Collectors.toSet());
return filteredList.stream().filter(url -> filterSet.contains("SMALL")).collect(Collectors.toList());
}
UPDATE
I got the following result:
private Properties toOccProductPropertiesDTO(ProcessProductDTO processProductDTO) throws JsonProcessingException {
String pSpecs = convertAttributes(processProductDTO.getProductDTO().getAttributes());
MediaOccDTO medias = convertMedias(processProductDTO.getProductDTO().getMedias());
return Properties.builder()
.id(processProductDTO.getProductDTO().getId())
.active(processProductDTO.getProductDTO().getActive())
.listPrices(new HashMap())
.p_specs(pSpecs)
.medias(medias)
.displayName(processProductDTO.getProductDTO().getDisplayName())
.longDescription(processProductDTO.getProductDTO().getLongDescription())
.build();
}
private MediaOccDTO convertMedias(List<MediaDTO> mediaDTOs){
String smallImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.SMALL);
String mediumImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.MEDIUM);
String largeImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.LARGE);
String thumbImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.THUMB);
return MediaOccDTO.builder()
.p_smallImageUrls(smallImageUrls)
.p_mediumImageUrls(mediumImageUrls)
.p_largeImageUrls(largeImageUrls)
.p_thumbImageUrls(thumbImageUrls)
.build();
}
private String generateOccUrl(List<MediaDTO> mediaDTOs, ImageSizeType imageSizeType){
return mediaDTOs.stream()
.filter(m -> m.getSizeType().equals(imageSizeType))
.map(MediaDTO::getLiveloUrl)
.reduce(",", String::concat);
}
The problem is:
the comparison: m.getSizeType().equals(imageSizeType)
is always false, so the list gets created empty...

Though the question is laborious, I could think of the requirement being, you need to create 4 new lists based on sizeType.
Stream collector, can collect the results to a single data structure. It can be a list, set, Map etc.
Since you need 4 lists based on sizeType, you will need to pass through the stream 4 times to create 4 lists.
Another Alternate will be to create a Map<SizeType, List<MediaDTO>>
This can be achieved through,
mediaDTO.stream().collect(Collectors.toMap(i -> i.getSizeType(), i->i)

I think the toMap doesn't collect the values in a list. We need groupingBy instead.
mediaDTO.stream()
.collect(Collectors.groupingBy(MediaDTO::getSizeType));

Related

Efficient way to group by a given list based on a key and collect in same list java 8

I have the below class:
class A{
String property1;
String property2;
Double property3;
Double property4;
}
So the property1 and property2 is the key.
class Key{
String property1;
String property2;
}
I already have a list of A like below:
List<A> list=new ArrayList<>();
I want to group by using the key and add to another list of A in order to avoid having multiple items with same key in the list:
Function<A, Key> keyFunction= r-> Key.valueOf(r.getProperty1(), r.getProperty2());
But then while doing group by I have to take a sum of property3 and average of property4.
I need an efficient way to do it.
Note: I have skipped the methods of the given classes.
Collecting to a Map is unavoidable since you want to group things. A brute-force way to do that would be :
yourListOfA
.stream()
.collect(Collectors.groupingBy(
x -> new Key(x.getProperty1(), x.getProperty2()),
Collectors.collectingAndThen(Collectors.toList(),
list -> {
double first = list.stream().mapToDouble(A::getProperty3).sum();
// or any other default
double second = list.stream().mapToDouble(A::getProperty4).average().orElse(0D);
A a = list.get(0);
return new A(a.getProperty1(), a.getProperty2(), first, second);
})))
.values();
This could be slightly improved for example in the Collectors.collectingAndThen to only iterate the List once, for that a custom collector would be required. Not that complicated to write one...
Try like this:
Map<A,List<A>> map = aList
.stream()
.collect(Collectors
.groupingBy(item->new A(item.property1,item.property2)));
List<A> result= map.entrySet().stream()
.map(list->new A(list.getValue().get(0).property1,list.getValue().get(0).property1)
.avgProperty4(list.getValue())
.sumProperty3(list.getValue()))
.collect(Collectors.toList());
and create avgProperty4 and sumProperty3 methods like to this
public A sumProperty3(List<A> a){
this.property3 = a.stream().mapToDouble(A::getProperty3).sum();
return this;
}
public A avgProperty4(List<A> a){
this.property4 = a.stream().mapToDouble(A::getProperty4).average().getAsDouble();
return this;
}
result = aList.stream().collect(Collectors
.groupingBy(item -> new A(item.property1, item.property2),
Collectors.collectingAndThen(Collectors.toList(), list ->
new A(list.get(0).property1, list.get(0).property1)
.avgProperty4(list).sumProperty3(list))
)
);

Why can BDDMockito not resolve types in this case?

Consider DynamoDB's QueryApi. Through a series of (unfortunate?) hoops,
ItemCollection<QueryOutcome>>
ends up being equivalent to
Iterable<Item>
I know this because I can do:
public PuppyDog getPuppy(final String personGuid, final String name) {
final QuerySpec spec = new QuerySpec()
.withKeyConditionExpression("#d = :guid and #n = :name")
.withNameMap(new NameMap().with("#d", "guid").with("#n", "name"))
.withValueMap(new ValueMap().withString(":guid", personGuid).withString(":name", name));
return getDog(index.query(spec));
}
private PuppyDog getDog(final Iterable<Item> itemCollection) {
// http://stackoverflow.com/questions/23932061/convert-iterable-to-stream-using-java-8-jdk
return StreamSupport.stream(itemCollection.spliterator(), false)
.map(this::createDogFor)
// it would be a little weird to find more than 1, but not sure what to do if so.
.findAny().orElse(new PuppyDog());
}
But when I try to write tests in Mockito using BDDMockito:
#Test
public void canGetPuppyDogByPersonGuidAndName() {
final PuppyDog dawg = getPuppyDog();
final ArgumentCaptor<QuerySpec> captor = ArgumentCaptor.forClass(QuerySpec.class);
final ItemCollection<QueryOutcome> items = mock(ItemCollection.class);
given(query.query(captor.capture())).willReturn(items);
}
The compiler complains when I try to make items an Iterable.
Why dis?
Dis not because of BDDMockito. Dis because ItemCollection<QueryOutcome> simply can't be cast safely into
Iterable<Item>. It can be cast into Iterable<QueryOutcome> or even Iterable<? extends Item>, but not Iterable<Item>.
Otherwise you could do this:
final ItemCollection<QueryOutcome> items = mock(ItemCollection.class);
Collection<Item> yourItemCollection = items;
yourItemCollection.add(itemThatIsNotAQueryOutcome); // violating safety of items
See also:
Is List<Dog> a subclass of List<Animal>? Why aren't Java's generics implicitly polymorphic?
Why are arrays covariant but generics are invariant?

Tranversing and filtering a Set comparing its objects' getters to an Array using Stream

I've got some working, inelegant code here:
The custom object is:
public class Person {
private int id;
public getId() { return this.id }
}
And I have a Class containing a Set<Person> allPersons containing all available subjects. I want to extract a new Set<Person> based upon one or more ID's of my choosing. I've written something which works using a nested enhanced for loop, but it strikes me as inefficient and will make a lot of unnecessary comparisons. I am getting used to working with Java 8, but can't quite figure out how to compare the Set against an Array. Here is my working, but verbose code:
public class MyProgram {
private Set<Person> allPersons; // contains 100 people with Ids 1-100
public Set<Person> getPersonById(int[] ids) {
Set<Person> personSet = new HashSet<>() //or any type of set
for (int i : ids) {
for (Person p : allPersons) {
if (p.getId() == i) {
personSet.add(p);
}
}
}
return personSet;
}
}
And to get my result, I'd call something along the lines of:
Set<Person> resultSet = getPersonById(int[] intArray = {2, 56, 66});
//resultSet would then contain 3 people with the corresponding ID
My question is how would i convert the getPersonById method to something using which streams allPersons and finds the ID match of any one of the ints in its parameter array? I thought of some filter operation, but since the parameter is an array, I can't get it to take just the one I want only.
The working answer to this is:
return allPersons.stream()
.filter(p -> (Arrays.stream(ids).anyMatch(i -> i == p.getId())) )
.collect(Collectors.toSet());
However, using the bottom half of #Flown's suggestion and if the program was designed to have a Map - it would also work (and work much more efficiently)
As you said, you can introduce a Stream::filter step using a Stream::anyMatch operation.
public Set<Person> getPersonById(int[] ids) {
Objects.requireNonNull(ids);
if (ids.length == 0) {
return Collections.emptySet();
}
return allPersons.stream()
.filter(p -> IntStream.of(ids).anyMatch(i -> i == p.getId()))
.collect(Collectors.toSet());
}
If the method is called more often, then it would be a good idea to map each Person to its id having a Map<Integer, Person>. The advantage is, that the lookup is much faster than iterating over the whole set of Person.Then your algorithm may look like this:
private Map<Integer, Person> idMapping;
public Set<Person> getPersonById(int[] ids) {
Objects.requireNonNull(ids);
return IntStream.of(ids)
.filter(idMapping::containsKey)
.mapToObj(idMapping::get)
.collect(Collectors.toSet());
}

Find the difference between two collections in Java 8?

I am trying to make a List of all of the books in one Collection that are not present in another. My problem is that I need to compare based on book ID, so I can't just test to see whether a book in the first is contained in the second, I have to determine whether any book in the second collection has the same ID as a book in the first.
I have the below code to compare two collections of books and filter the first collection:
List<Book> parentBooks = listOfBooks1.stream().filter(book->
!listOfBooks2.contains(book)).collect(Collectors.toList());
The code doesn't work correctly because I am comparing the objects themselves. I need to compare the objects based on the bookId instead of the whole book object. How should I change the code so it can do the comparison based on the bookId (book.getId())?
List<Book> books1 = ...;
List<Book> books2 = ...;
Set<Integer> ids = books2.stream()
.map(Book::getId)
.collect(Collectors.toSet());
List<Book> parentBooks = books1.stream()
.filter(book -> !ids.contains(book.getId()))
.collect(Collectors.toList());
The problem is complex, but it boils down to one thing, knows your data. Is it immutables, entities with an id, duplicate entries etc?
The code below works for immutables with only values (and with possible duplicates).
It first tries to remove all entries in the before list (from the copied after-list).
What is left will be the added elements. The ones from the before-list that can be removed from the after-list are the unchanged ones.
The rest are the removed ones
public class ListDiffer<T> {
private List<T> addedList = new ArrayList<>();
private List<T> unchangedList = new ArrayList<>();
private List<T> removedList = new ArrayList<>();
public ListDiffer(List<T> beforeList, List<T> afterList) {
addedList.addAll(afterList); // Will contain only new elements when all elements in the Before-list are removed.
beforeList.forEach(e -> {
boolean b = addedList.remove(e) ? unchangedList.add(e) : removedList.add(e);
});
}
public List<T> getAddedList() {
return addedList;
}
public List<T> getUnchangedList() {
return unchangedList;
}
public List<T> getRemovedList() {
return removedList;
}
}

Comparing ENUMs in Java 6

I am trying to find best way to compare Enums in Java 6.
Say, I have an ENUM of Ticket Types which can be associated with a Traveler. If there is a list of travelers, I would like to know the traveler with the highest class of travel.
I can iterate thru the list of travelers, create a Set of unique TicketTypes, Convert to List, Sort them, Return the last element as the highest. I would like to know if there is a better way to do this?
public enum TicketType {
ECONOMY_NON_REF(1,"Economy Class, Non-Refundable"),
ECONOMY_REF(2,"Economy Full Fare Refundable"),
BUSINESS(3,"Business Class"),
FIRST_CLASS(4,"First Class, Top of the world");
private String code;
private String description;
}
public class Traveler {
private TicketType ticketType;
public Traveler(TicketType ticketType) {
this.ticketType = ticketType;
}
}
#Test
public testCompareEnums{
List<Traveler> travelersGroup1 = new ArrayList<Travelers>();
travelersGroup1.add(new Traveler(TicketType.ECONOMY_REF));
travelersGroup1.add(new Traveler(TicketType.BUSINESS));
travelersGroup1.add(new Traveler(TicketType.ECONOMY_REF));
travelersGroup1.add(new Traveler(TicketType.ECONOMY_NON_REF));
//What is the best way to find the highest class passenger in travelersGroup1.
assertEquals(TicketType.BUSINESS, getHighestClassTravler(travelersGroup1));
List<Traveler> travelersGroup2 = new ArrayList<Travelers>();
travelersGroup2.add(new Traveler(TicketType.ECONOMY_REF));
travelersGroup2.add(new Traveler(TicketType.ECONOMY_NON_REF));
travelersGroup2.add(new Traveler(TicketType.ECONOMY_REF));
travelersGroup2.add(new Traveler(TicketType.ECONOMY_NON_REF));
assertEquals(TicketType.ECONOMY_REF, getHighestClassTravler(travelersGroup2));
}
private CredentialType getHighestClassTraveler(List travelers){
Set uniqueTicketTypeSet = new HashSet();
for (Traveler t: travelers) {
uniqueTicketTypeSet.add(t.getTicketType());
}
List<TicketType> uniqueTicketTypes = new ArrayList<TicketType>(uniqueTicketTypeSet);
Collections.sort(uniqueTicketTypes);
return uniqueTicketTypes.get(uniqueTicketTypes.size()-1);
}
There's a lot of problems with the code that you posted (it won't compile without fixing a lot of errors), but the easiest way is to make Traveler implement the Comparable interface, like so:
public int compareTo(Traveler other) {
return this.getTicketType().compareTo(other.getTicketType());
}
Then to find the the Traveler with the highest TicketType, you can simply do:
Collections.max(travelers);

Resources