How can I iterate over list of POJO classes for collecting result of some methods in a standard way to avoid copy past?
I want to have code like this:
//class 'Person' has methods: getNames(), getEmails()
List<Person> people = requester.getPeople(u.getId());
String names = merge(people, Person::getNames);
String emails = merge(people, Person::getEmails);
instead of such copy-pasted logic:
List<Person> people = requester.getPeople(u.getId());
Set<String> namesAll = new HashSet<>();
Set<String> emailsAll = new HashSet<>();
for (Person p : people) {
if(p.getNames()!=null) {
phonesAll.addAll(p.getNames());
}
if(p.getEmails()!=null) {
emailsAll.addAll(p.getEmails());
}
}
String names = Joiner.on(", ").skipNulls().join(namesAll);
String emails = Joiner.on(", ").skipNulls().join(emailsAll);
Thus, is it possible to implement some standard approach for iterating and processing special method of POJO in list that could be reused?
If I understand you correctly, you want something like this :
String names = people.stream().flatMap(p->p.getNames().stream()).distinct().collect(Collectors.joining(", "));
Now, if you want to save typing that line for each property, you can have this merge method as you suggested :
public static String merge (List<Person> people, Function<Person, Collection<String>> mapper)
{
return people.stream().flatMap(p->mapper.apply(p).stream()).distinct().collect(Collectors.joining(", "));
}
This would make your first snippet work.
Now, you can make this method generic :
public static <T> String merge (List<T> list, Function<T, Collection<String>> mapper)
{
return list.stream().flatMap(p->mapper.apply(p).stream()).distinct().collect(Collectors.joining(", "));
}
I think this should work (haven't tested it).
Related
I have a huge group of keys like more than 10L like the below in csv file
ABA,100
ABC,200
ABCs,50
ABM,65
ABMs,86
ABS,86
AC,54
ACLU,123
ACT,56
ACTH,154
AD,644
ADC,76
ADD,10.
Do I need to create the user define an object for the above key pairs? Will it create any memoery problem for creating more than 10L user define object?
My input String looks like [ABS,AC,ACLU,ABC]
I want the output AC,ABS,ACLU,ABC based on the count.
How to achieve it in easier way of Java 1.8.
Thanks.
You could add each line of your csv to a List<String> myList
Then, you will have to create a custom Comparator in order to sort your list based on the value, so something like the following,
private void customSorting(List<String> myList) {
Collections.sort(myList, (String s1, String s2) -> {
String valuePart1 = s1.split(",")[1];
String valuePart2 = s2.split(",")[1];
return Integer.valueOf(valuePart1).compareTo(Integer.valueOf(valuePart2));
});
}
Finally, just call your method like customSorting(myList); in any place of your code you need it
Of course, you have to modify the sorted list as well to keep only the first part (before comma) for each String value but that's easy.
An alternative could also be to create a class like the following,
public class MyClass {
private String key;
private String value;
// All the getters, setters, constructors, etc
}
, and then read each line of your csv, create an equivalent MyClass POJO and add it to a List<MyClass> myList.
You have to write your own custom Comparator again for List<MyClass> in a similar way like I did for the List<String>,
private void customSorting(List<MyClass> myList) {
Collections.sort(myList, (MyClass a, MyClass b) -> {
return Integer.valueOf(a.getValue()).compareTo(Integer.valueOf(b.getValue()));
});
}
Finally, create a new list from the sorted one by keeping only the keys
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));
I have a List which can be null;
List<T> list; // may or may not null
I want to process for each element with a consumer.
So far, I do.
ofNullable(list)
.map(List::stream)
.ifPresent(stream -> stream.forEach(e -> {}));
or
ofNullable(eventDataList).ifPresent(v -> v.forEach(e -> {}));
Is there any easy or concise way to do this?
To avoid ugly null checking, use orElse(Collections.emptyList())
Optional.ofNullable(eventDataList)
.orElse(Collections.emptyList())
.forEach(e -> {});
With static imports, it's pretty concise:
ofNullable(eventDataList).orElse(emptyList()).forEach(e -> {});
Technically, if (list != null) { list.stream().forEach(e -> {...}); } is both shorter and more efficient in terms of CPU/memory usage than your variants.
Architecturally, if you have control over initialization of the list and its usage, it's often better to use either Collections.emptyList() instead of null (if the logic of your program allows) or make the list Optional from the very beginning. That would save you from necessity to make checks or create Optionals every time you want to use the list.
If you require to do something with every value in the list and say return a value then ifPresent will not work. Rather you can do something like below. In my example the optional list contains a user defined object Person which has a few attributes. I am iterating over the list and concatenating the values of a specific attribute and returning it.
public static class Person
{
String name;
int age;
public Person(final String name, final int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
public static void main(String[] args)
{
Person a = new Person("Alice", 1);
Person b = new Person("Bob", 2);
List<Person> personList = Lists.newArrayList(a, b);
String concatNames = Optional.of(personList).map(people -> people.stream().map(Person::getName).collect(Collectors.joining(" "))).orElse(null);
System.out.println("Names: "+concatNames);
}
I'm not sure that you can make it more concise. However, if you are frequently using the construct of looping over a nullable list and consuming each element, you could make a small class which does just that:
public class ListConsumer {
public static <H> Consumer<List<H>> of(Consumer<H> consumer) {
return hs -> hs.forEach(consumer);
}
}
You can then consume each element in a list as follows (e.g. print all Strings in list):
List<String> list = Arrays.asList("A", "B", "C");
Consumer<String> consumer = System.out::println;
Optional.ofNullable(list).ifPresent(ListConsumer.of(consumer));
I have a simple object like this
public class Person {
private int id;
private int age;
private String hobby;
//getters, setters
}
I want to group a list of Person by attributes
Output should be like this
Person count/Age/Hobby
2/18/Basket
5/20/football
With a chart for more understanding
X axis : hobby repartition
Y axis : count of person distribution
Colors represents age
I managed to group by one attribute using map, but I can't figure how to group by multiples attributes
//group only by age . I want to group by hobby too
personMapGroupped = new LinkedHashMap<String, List<Person>>();
for (Person person : listPerson) {
String key = person.getAge();
if (personMapGroupped.get(key) == null) {
personMapGroupped.put(key, new ArrayList<Person>());
}
personMapGroupped.get(key).add(person);
}
Then I retrieve the groupable object like this
for (Map.Entry<String, List<Person>> entry : personMapGroupped .entrySet()) {
String key = entry.getKey();// group by age
String value = entry.getValue(); // person count
// I want to retrieve the group by hobby here too...
}
Any advice would be appreciated.
Thank you very much
Implement methods for comparing people according to the different fields. For instance, if you want to group by age, add this method to Person:
public static Comparator<Person> getAgeComparator(){
return new Comparator<Person>() {
#Override
public int compare(Person o1, Person o2) {
return o1.age-o2.age;
}
};
}
Then you can simply call: Arrays.sort(people,Person.getAgeComparator()) or use the following code to sort a Collection:
List<Person> people = new ArrayList<>();
people.sort(Person.getAgeComparator());
To sort using more than one Comparator simultaneously, you first define a Comparator for each field (e.g. one for age and one for names). Then you can combine them using a ComparatorChain. You would use the ComparatorChain as follows:
ComparatorChain chain = new ComparatorChain();
chain.addComparator(Person.getNameComparator());
chain.addComparator(Person.getAgeComparator());
You could simply combine the attributes to a key.
for (Person person : listPerson) {
String key = person.getAge() + ";" + person.getHobby();
if (!personMapGrouped.contains(key)) {
personMapGrouped.put(key, new ArrayList<Person>());
}
personMapGrouped.get(key).add(person);
}
The count of entries is easy to determine by using personMapGrouped.get("18;Football").getSize().
I'm not sure about your requirements, but I'd probably use multiple maps (Google Guava's Multimap would make that easier btw) and sets, e.g. something like this:
//I'm using a HashMultimap since order of persons doesn't seem to be relevant and I want to prevent duplicates
Multimap<Integer, Person> personsByAge = HashMultimap.create();
//I'm using the hobby name here for simplicity, it's probably better to use some enum or Hobby object
Multimap<String, Person> personsByHobby = HashMultimap.create();
//fill the maps here by looping over the persons and adding them (no need to create the value sets manually
Since I use value sets Person needs a reasonable implementation of equals() and hashCode() which might make use of the id field. This also will help in querying.
Building subsets would be quite easy:
Set<Person> age18 = personsByAge.get(18);
Set<Person> basketballers = personsByHobby.get( "basketball" );
//making use of Guava again
Set<Person> basketballersAged18 = Sets.intersection( age18, basketballers );
Note that I made use of Google Guava here but you can achieve the same with some additional manual code (e.g. using Map<String, Set<Person>> and manually creating the value sets as well as using the Set.retainAll() method).
I have 2 arraylists, one of type String and the other is a custom class Person.
List names = new ArrayList<String>();
List people = new ArrayList<Person>();
Both lists are populated like so:
names.add("bob");
names.add("joe");
names.add("tom");
people.add(new Person("joe")); //Assuming Person has a name property
people.add(new Person("tom"));
people.add(new Person("bob"));
Notice that the same names have been used in both lists, but added in different order. How can I sort the people arraylist in the same order as the names?
Strange requirement, but you can do it by using a Map:
Map<String, Person> personMap = new HashMap<>();
//Assuming people is declared rightfully as List<Person> rather than just List
for (Person people : people) {
personMap.put(person.getName(), person);
}
List<Person> results = new ArrayList<>();
for (String name : names) {
if (personMap.containsKey(name)) {
results.add(personMap.get(name));
}
}
//in case you need to work with people only
people.clear();
people.addAll(results);
Since the names array can apparently be in an arbitrary order, the concept of "sorting" isn't very applicable. The most direct approach, I think, is to rebuild the people array from the given names array by using a map. Something like this might work:
void reoderPeople(ArrayList<Person> people, ArrayList<String> names) {
// first build the map
Map<String, Person> map = new HashMap<>();
for (Person p : people) {
map.add(p.getName(), p);
}
// now re-create the people array
people.clear();
for (String name : names) {
people.add(map.get(name));
}
}
This assumes that there is a one-to-one correspondence between the elements of names and people based on the name. If that's not a correct assumption, then this approach would have to be modified accordingly.
Use a comparator for sorting people which uses ordering of names list. (Untested code below)
Collections.sort(people, new Comparator<Person>(){
public int comapre(Person a, Person b) {
Integer indexA = names.indexOf(a.getName());
Integer indexB = names.indexOf(b.getName());
return indexA.compareTo(indexB);
}
});