Stream Collection to other structure - java

I try convert list objects of Dictionary to object with contains list.
public class Dictionary {
private String dictCode;
private String dictName;
private String dictDescription;
private String dictElemCode;
private String dictElemName;
private String dictElemDescription;
}
//parent
public class DictDTO {
private String code;
private String value;
private String description;
private List<DictElemDTO> listDictElemDTO;
}
//children
public class DictElemDTO {
private String code;
private String value;
private String description;
}
Example data for Dictionary class:
Dictionary d1 = new Dictionary("a1", "dict1", "desc1", "elem_code_1", "elem_code_name", "elem_code_desc");
Dictionary d2 = new Dictionary("a1", "dict1", "desc1", "elem_code_2", "elem_code_name2", "elem_code_desc2");
Dictionary d3 = new Dictionary("a2", "dict2", "desc2", "elem_code_3", "elem_code_name3", "elem_code_desc3");
And the result should by like this:
a1
------ elem_code_1
------ elem_code_2
a2
------ elem_code_3
My solution for stream works but it's slow because I use stream of list more than once.
public static void main(String[] args) {
Dictionary d1 = new Dictionary("a1", "dict1", "desc1", "elem_code_1", "elem_code_name", "elem_code_desc");
Dictionary d2 = new Dictionary("a1", "dict1", "desc1", "elem_code_2", "elem_code_name2", "elem_code_desc2");
Dictionary d3 = new Dictionary("a2", "dict2", "desc2", "elem_code_3", "elem_code_name3", "elem_code_desc3");
List<Dictionary> list = ImmutableList.of(d1, d2, d3);
List<DictDTO> newList = list.stream().filter(distinctByKey(Dictionary::getDictCode)).map(t -> {
DictDTO dto = new DictDTO();
dto.setCode(t.getDictCode());
dto.setValue(t.getDictName());
dto.setDescription(t.getDictDescription());
dto.setListDictElemDTO(
list.stream().filter(e -> e.getDictCode().equals(t.getDictCode())).map(w -> {
DictElemDTO elemDto = new DictElemDTO();
elemDto.setCode(w.getDictElemCode());
elemDto.setValue(w.getDictElemName());
elemDto.setDescription(w.getDictElemDescription());
return elemDto;
}).collect(Collectors.toList())
);
return dto;
}).collect(Collectors.toList());
for (DictDTO dto : newList) {
System.err.println(dto.getCode());
for (DictElemDTO dtoE : dto.getListDictElemDTO()) {
System.err.println("------ " + dtoE.getCode());
}
}
}
static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
What is better approach for this problem using stream in Java 8?

This looks like a job for Collectors.groupingBy:
Map<String,List<DictElemDTO>>
map = Stream.of(d1,d2,d3)
.collect(Collectors.groupingBy(Dictionary::getDictCode,
Collectors.mapping(d-> new DictElemDTO (d.getDictElemCode(),d.getDictElemName(),d.getDictElemDescription()),
Collectors.toList())));
This will give you a mapping of dictionary codes to the corresponding list of DictElemDTOs.
It requires a bit more work to create the DictDTO objects.

Related

Java Stream API : How to use method reference?

There is code
class Person {
private ZonedDateTime date ;
private int regionId;
private int centerId;
private int amount1;
private float percent1;
}
List<Person> entityList = new ArrayList<>();
I grouping by year of month like this:
listPerson.stream()
.collect(Collectors.groupingBy(i -> i.getDate().getMonth(),Collectors.collectingAndThen(Collectors.toList(),
l -> {
Integer sumAmount1 = l.stream().collect(Collectors.summingInt(i -> i.getAmount1()));
Double avgPerc1 = l.stream().collect(Collectors.averagingDouble(i -> i.getPercent1()));
List<String> data = new ArrayList<>();
data.add(Integer.toString(sumAmount1));
data.add(Double.toString(avgPerc1));
return data;
}
))).forEach((k,v) -> System.out.println(k.getValue() + "-" + v.toString()));
Also i group by year, regionId, centerId in same manner:
listPerson.stream()
.collect(Collectors.groupingBy(i -> i.getDate().getYear(),Collectors ......
But i got many duplicate code where part with
l -> {...}
repeated many times. How to instead of l -> {...} use a method reference?
IntelliJ can literally just do this for you. You don't even have to think about it.
Keyboard shortcut for hints (yellow) is AltEnter
Here's what I ended up with
public static void main(String[] args)
{
List<Person> listPerson = null;
listPerson.stream()
.collect(Collectors.groupingBy(i -> i.getDate().getMonth(), Collectors.collectingAndThen(Collectors.toList(),
Scratch::apply
)))
.forEach((k,v) -> System.out.println(k.getValue() + "-" + v.toString()));
}
private static List<String> apply(List<Person> l)
{
int sumAmount1 = l.stream().mapToInt(Person::getAmount1).sum();
Double avgPerc1 = l.stream().collect(Collectors.averagingDouble(Person::getPercent1));
List<String> data = new ArrayList<>();
data.add(Integer.toString(sumAmount1));
data.add(Double.toString(avgPerc1));
return data;
}
You can create a method reference like this:
private List<String> methodReference(List<Person> l) {
Integer sumAmount1 = l.stream().collect(Collectors.summingInt(i -> i.getAmount1()));
Double avgPerc1 = l.stream().collect(Collectors.averagingDouble(i -> i.getPercent1()));
List<String> data = new ArrayList<>();
data.add(Integer.toString(sumAmount1));
data.add(Double.toString(avgPerc1));
return data;
}
I have created a methodReference in my Test class. You can replace it with your own class name. And now in your stream() you can refer to it like this:
entityList.stream()
.collect(Collectors.groupingBy(i -> i.getDate().getMonth(), Collectors.collectingAndThen(Collectors.toList(),
Test::methodReference // replace Test with your class name
))).forEach((k, v) -> System.out.println(k.getValue() + "-" + v.toString()));
Might be a little of topic, but I think the beauty of using Stream API is allowing you to build a data pipeline. I would strive to build something that looks like a pipeline, with steps I can customize with pluggable functions.
I think the code would be more readable by refactoring towards a pipeline, and I would try below with the help of a new data structure called Tuple2, epscially its map method. It's easy to build, you can also use one from libraries like vavr.
For reuse, one can consider a function like groupAndSummarize (the name suggests it does two things, so is a smell).
class Tuple2<T1, T2> {
private final T1 t1;
private final T2 t2;
public Tuple2(final T1 t1, final T2 t2) {
this.t1 = t1;
this.t2 = t2;
}
public <U1, U2> Tuple2<U1, U2> map(final Function<T1, U1> func1,
final Function<T2, U2> func2) {
return new Tuple2<>(func1.apply(t1), func2.apply(t2));
}
public T1 _1() { return t1; }
public T2 _2() { return t2; }
}
private <T, K, V> List<Tuple2<K, V>> groupAndSummarize(final List<T> list, final Function<T, K> groupFn, final Function<List<T>, V> summarizeFn) {
return list.stream()
.collect(Collectors.groupingBy(groupFn))
.entrySet()
.stream()
.map(this::toTuple)
.map(t -> t.map(
Function.identity(),
summarizeFn
))
.collect(Collectors.toList());
}
private <K, V> Tuple2<K, V> toTuple(final Map.Entry<K, V> entry) {
return new Tuple2<>(entry.getKey(), entry.getValue());
}
private List<String> summarize(final List<Person> l) {
// your logic
}
public void test() {
final List<Person> entityList = new ArrayList<>();
groupAndSummarize(entityList, i -> i.getDate().getMonth(), this::summarize)
.forEach(t -> System.out.println(t.t1.getValue() + "-" + t.t2.toString()));
}

Transform Java List into the Pivot format

I have a below class and would like to transform the list of data objects into the pivot table format with java.
public class Data {
private String consultedOn;
private String consultedBy;
// Getters
// Setters
}
List<Data> reports = new ArrayList<Data>();
reports.add(new Data("04/12/2018","Mr.Bob"));
reports.add(new Data("04/12/2018","Mr.Jhon"));
reports.add(new Data("04/12/2018","Mr.Bob"));
reports.add(new Data("05/12/2018","Mr.Jhon"));
reports.add(new Data("06/12/2018","Mr.Bob"));
reports.add(new Data("06/12/2018","Mr.Jhon"));
reports.add(new Data("07/12/2018","Mr.Bob"));
I would like to transform the above list into the below table format with java within a collection.
consultedOn Mr.Bob Mr.Jhon
---------------------------------------
04/12/2018 2 1
05/12/2018 0 1
06/12/2018 1 1
07/12/2018 1 0
Note that the consultedOn field is not restricted to two values, this field may contain any data so that the collection should be dynamic.
I tried using Java8 streams with below code.
class DataMap {
private String consultedOn;
private String consultedBy;
public DataMap(String consultedOn) {
super();
this.consultedOn = consultedOn;
}
public DataMap(String consultedOn, String consultedBy) {
super();
this.consultedOn = consultedOn;
this.consultedBy = consultedBy;
}
public String getConsultedOn() {
return consultedOn;
}
public void setConsultedOn(String consultedOn) {
this.consultedOn = consultedOn;
}
public String getConsultedBy() {
return consultedBy;
}
public void setConsultedBy(String consultedBy) {
this.consultedBy = consultedBy;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((consultedOn == null) ? 0 : consultedOn.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof DataMap ))
return false;
DataMap other = (DataMap )obj;
if (consultedOn == null) {
if (other.consultedOn != null)
return false;
} else if (!consultedOn.equals(other.consultedOn))
return false;
return true;
}
}
Map<DataMap, List<DataReport>> map = reports.stream()
.collect(Collectors.groupingBy(x -> new DataMap(x.getConsultedOn(), x.getConsultedBy())));
But the map is not giving intend results as per my expectations.
I'm not sure how to go-ahead with this kind of data, any help will be appreciated.
Here's a complete answer, using the technique I explained in the comment, i.e. design a class Row representing what you want to generate for each row, i.e. a consultedOn string, and a number of consultations for each person.
public class Pivot {
private static final class Data {
private final String consultedOn;
private final String consultedBy;
public Data(String consultedOn, String consultedBy) {
this.consultedOn = consultedOn;
this.consultedBy = consultedBy;
}
public String getConsultedOn() {
return consultedOn;
}
public String getConsultedBy() {
return consultedBy;
}
}
private static final class Row {
private final String consultedOn;
private final Map<String, Integer> consultationsByPerson = new HashMap<>();
public Row(String consultedOn) {
this.consultedOn = consultedOn;
}
public void addPerson(String person) {
consultationsByPerson.merge(person, 1, Integer::sum);
}
public int getConsultationsFor(String person) {
return consultationsByPerson.getOrDefault(person, 0);
}
public String getConsultedOn() {
return consultedOn;
}
}
private static class PivotReport {
private final Map<String, Row> rowsByConsultedOn = new HashMap<>();
private SortedSet<String> persons = new TreeSet<>();
private PivotReport() {}
private void addData(Data d) {
rowsByConsultedOn.computeIfAbsent(d.getConsultedOn(), Row::new).addPerson(d.getConsultedBy());
persons.add(d.consultedBy);
}
public static PivotReport create(List<Data> list) {
PivotReport report = new PivotReport();
list.forEach(report::addData);
return report;
}
public String toString() {
String headers = "Consulted on\t" + String.join("\t", persons);
String rows = rowsByConsultedOn.values()
.stream()
.sorted(Comparator.comparing(Row::getConsultedOn))
.map(this::rowToString)
.collect(Collectors.joining("\n"));
return headers + "\n" + rows;
}
private String rowToString(Row row) {
return row.getConsultedOn() + "\t" +
persons.stream()
.map(person -> Integer.toString(row.getConsultationsFor(person)))
.collect(Collectors.joining("\t"));
}
}
public static void main(String[] args) {
List<Data> list = createListOfData();
PivotReport report = PivotReport.create(list);
System.out.println(report);
}
private static List<Data> createListOfData() {
List<Data> reports = new ArrayList<Data>();
reports.add(new Data("04/12/2018","Mr.Bob"));
reports.add(new Data("04/12/2018","Mr.Jhon"));
reports.add(new Data("04/12/2018","Mr.Bob"));
reports.add(new Data("05/12/2018","Mr.Jhon"));
reports.add(new Data("06/12/2018","Mr.Bob"));
reports.add(new Data("06/12/2018","Mr.Jhon"));
reports.add(new Data("07/12/2018","Mr.Bob"));
reports.add(new Data("07/12/2018","Mr.Smith"));
return reports;
}
}
Note that since you're using String instead of LocalDate for the consultedOn field, the dates will be sorted lexicographically instead of being sorted chronologically. You should use the appropriate type: LocalDate.
You are probably looking to use Collectors.groupingBy to group the List<DataMap> by consultedOn and further grouping it by consultedBy attribute and their count as :
Map<String, Map<String, Long>> finalMapping = reports.stream()
.collect(Collectors.groupingBy(DataMap::getConsultedOn,
Collectors.groupingBy(DataMap::getConsultedBy,Collectors.counting())));
This would provide you as an output:
{05/12/2018={Mr.Jhon=1}, 06/12/2018={Mr.Jhon=1, Mr.Bob=1},
07/12/2018={Mr.Bob=1}, 04/12/2018={Mr.Jhon=1, Mr.Bob=2}}
Further, if you require all the corresponding consultedBy values to be accounted in, you can create a Set of those from the initial List<DataMap> as :
Set<String> consultedBys = reports.stream()
.map(DataMap::getConsultedBy)
.collect(Collectors.toSet());
using which you can modify your existing map obtained to contain 0 count as well in the following manner:
finalMapping.forEach((k, v) -> consultedBys.forEach(c -> v.putIfAbsent(c, 0L)));
This would now provide you as the output:
{05/12/2018={Mr.Jhon=1, Mr.Bob=0}, 06/12/2018={Mr.Jhon=1, Mr.Bob=1},
07/12/2018={Mr.Jhon=0, Mr.Bob=1}, 04/12/2018={Mr.Jhon=1, Mr.Bob=2}}
The other would be like this:
Map<Pair<String, String>, Integer> map = reports
.stream()
.collect(toMap(data -> new Pair<>(data.getConsultedOn(),
data.getConsultedBy()), data -> 1, Integer::sum));
Map<String, DataMap> result= new HashMap<>();
-
class DataMap {
private String consultedOn;
private Map<String, Integer> map;
}
-
Set<String> persons = new HashSet<>();
persons = reports.stream().map(Data::getConsultedBy).collect(Collectors.toSet());
-
for (Map.Entry<Pair<String, String>, Integer> entry : map.entrySet()) {
Map<String, Integer> val = new HashMap<>();
for (String person : persons) {
if (!person.equals(entry.getKey().getValue()))
val.put(person, 0);
else
val.put(entry.getKey().getValue(), entry.getValue());
}
result.put(entry.getKey().getKey(), new DataMap(entry.getKey().getKey(), val));
}
and final result:
List<DataMap> finalResult = new ArrayList<>(result.values());
Instead of using a separate data structure, you can use a Map of key as consultedOn (date or String) and have the value as a list of (String or your own defined POJO with overridden equals() method.Here in I have used a map like Map<String, List<String>>
All you need is the two methods:
one to set report (addDataToReport) : for each consultedOn (key), create a list of doctors consulted . See comments for map.merge usage
and one to display the data in a report manner (printReport). We are using "%10s" to give proper formatting. Instead of println, format doesn't implicitly append a new line character
Moreover to get the report's column we need to have a set (unique value list), doctors.add(consultedBy); will serve us for this purpose . Java will take care of keeping the doctors' value unique.
public class Application {
Set<String> doctors = new LinkedHashSet<>();
private void addDataToReport(Map<String, List<String>> reportMap, String consultedOn, String consultedBy) {
doctors.add(consultedBy); // set the doctors Set
reportMap.merge(consultedOn, Arrays.asList(consultedBy)// if key = consultedOn is not there add , a new list
, (v1, v2) -> Stream.concat(v1.stream(), v2.stream()).collect(Collectors.toList()));//else merge previous and new values , here concatenate two lists
}
private void printReport(Map<String, List<String>> reportMap) {
/*Set Headers*/
String formatting = "%10s";//give a block of 10 characters for each string to print
System.out.format(formatting, "consultedOn");
doctors.forEach(t -> System.out.format(formatting, t));// print data on console without an implicit new line
System.out.println("\n---------------------------------------");
/*Set row values*/
for (Map.Entry<String, List<String>> entry : reportMap.entrySet()) {
Map<String, Integer> map = new LinkedHashMap<>();
doctors.forEach(t -> map.put(t, 0)); // initialise each doctor count on a day to 0
entry.getValue().forEach(t -> map.put(t, map.get(t) + 1));
System.out.format(formatting, entry.getKey());
map.values().forEach(t -> System.out.format(formatting, t));
System.out.println();
}
}
public static void main(String[] args) {
Application application = new Application();
Map<String, List<String>> reportMap = new LinkedHashMap<>();
String MR_JHON = "Mr.Jhon";
String MR_BOB = "Mr.Bob ";
application.addDataToReport(reportMap, "04/12/2018", MR_BOB);
application.addDataToReport(reportMap, "04/12/2018", MR_JHON);
application.addDataToReport(reportMap, "04/12/2018", MR_BOB);
application.addDataToReport(reportMap, "05/12/2018", MR_JHON);
application.addDataToReport(reportMap, "06/12/2018", MR_BOB);
application.addDataToReport(reportMap, "06/12/2018", MR_JHON);
application.addDataToReport(reportMap, "07/12/2018", MR_BOB);
application.printReport(reportMap);
}
}
Result
consultedOn Mr.Bob Mr.Jhon
---------------------------------------
04/12/2018 2 1
05/12/2018 0 1
06/12/2018 1 1
07/12/2018 1 0

Filter pojo properties by some pattern

I've some server response (a long one) which I've converted to POJO (by using moshi library).
Eventually I have list of "Items" , each "Item" looks like follow :
public class Item
{
private String aa;
private String b;
private String abc;
private String ad;
private String dd;
private String qw;
private String arew;
private String tt;
private String asd;
private String aut;
private String id;
...
}
What I actually need, is to pull all properties which start with "a" , and then I need to use their values for further req ...
Any way to achieve it without Reflection ? (usage of streams maybe ?)
Thanks
With guava-functions tranformation you might transform your items with somethng following:
public static void main(String[] args) {
List<Item> items //
Function<Item, Map<String, Object>> transformer = new Function<Item, Map<String, Object>>() {
#Override
public Map<String, Object> apply(Item input) {
Map<String, Object> result = new HashMap<String, Object>();
for (Field f : input.getClass().getDeclaredFields()) {
if(! f.getName().startsWith("a")) {
continue;
}
Object value = null;
try {
value = f.get(input);
} catch (IllegalAccessException e) {
throw new RuntimeException("failed to cast" + e)
}
result.put(f.getName(), value);
}
return result
};
Collection<Map<String, Object> result
= Collections2.transform(items, transformer);
}
Sounds like you may want to perform your filtering on a regular Java map structure.
// Dependencies.
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Map<String, String>> itemAdapter =
moshi.adapter(Types.newParameterizedType(Map.class, String.class, String.class));
String json = "{\"aa\":\"value1\",\"b\":\"value2\",\"abc\":\"value3\"}";
// Usage.
Map<String, String> value = itemAdapter.fromJson(json);
Map<String, String> filtered = value.entrySet().stream().filter(
stringStringEntry -> stringStringEntry.getKey().charAt(0) == 'a')
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
You could wrap up the filtering logic in a custom JsonAdapter, but validation and business logic tends to be nice to leave to the application usage layer.

Array with 2 values per item

I'm trying to make an array with 2 values per item, like a value with a custom header, and, coming from Ruby, can't find a correct way to do this in Java.
This is for a REST-assured tests that i need to automate.
This is the method that i need to do, I mix the declaration of obj with some sort of ruby way to do it, so the necessity it's more clear:
private String[] getHeaders() {
String[] obj = [
'Signature' => this.getSignature(),
'Timestamp' => this.getTimestamp(),
];
if(getSessionToken() != null) {
obj.sessionToken = this.getSessionToken();
}
}
}
You can achieve that by creating a model. For example:
public class MyModel {
private String signature;
private String timestamp;
public MyModel() {
// constructor
}
public MyModel(String signature, String timestamp){
this.signature = signature;
this.timestamp = timestamp;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
}
Then create an array of your model. You can use:
private static final int MODEL_SIZE = 5;
private MyModel[] models = new MyModel[MODEL_SIZE];
if you already know the size of your array. Or you can use this approach below if you don't know the size of array yet:
private ArrayList<MyModel> models = new ArrayList<>;
private MyModel model;
// Then fill your models
// by using this way
model = new MyModel("My Signature", "My Timestamp");
models.add(model);
// or this way
model = new MyModel();
model.setSignature("My Signature");
model.setTimestamp("My Timestamp");
models.add(model);
Another way to achieve that without creating a model is by using HashMap. This is the example:
List<HashMap<String, String>> objects = new ArrayList<>();
HashMap<String, String> object = new HashMap<>();
object.put("signature", "My Signature");
object.put("timestamp", "My Timestamp");
objects.add(object);
Something like this I suspect is what you want.
class Headers{
public String signature;
public String timeStamp;
}
Headers[] header = new Headers[10];
You probably don't need getters and setters, but you can throw those in too.

Filter a List<> content in to dictionary in Java

I have a List of a particular type of objects as follows:
List<Code>
The properties of Code class are as follows;
private Integer id;
private Amp amp;
private FF ff;
private Imp imp;
private Board b;
private Line line;
private int interface;
private String interfaceType;
private boolean chained;
I want to filter this list based on the interface. Interface can vary from 0-7, hence I want to create a dictionary as
Dictionary<int, List<Code>>
How do I filter the list according to the interface to create the dictionary?
Thanks in advance for an answer.
I think you are looking for something like this:
List<Code> codes = ...;
Map<Integer, List<Code>> grouped = new HashMap<>();
for (Code code : codes) {
if (! grouped.containsKey(code.getInterface()) {
grouped.put(code.getInterface(), new ArrayList<Code>());
}
grouped.get(code.getInterface()).add(code);
}
You can use guava Multimaps utilities to express your mapping. And then you put the result into a dictionary.
import com.google.common.base.Function;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimaps;
public class Code {
public int getInterface() {...}
...
public static void main(String[] args) {
List<Code> codes = new ArrayList<Code>();
ImmutableListMultimap<Integer, Code> listMultimap = Multimaps.index(codes, new Function<Code, Integer>() {
#Override
public Integer apply(Code code) {
return code.getInterface();
}
});
Hashtable<Integer, List<Code>> dictionary = new Hashtable<Integer, List<Code>>(Multimaps.asMap(listMultimap));
}
}
List<Code> codes = <your_init>;
Dictionary<Integer, List<Code>> dict = new Hashtable<>();
//initialize dictionary
for ( int i = 0; i < 8; ++i )
{
dict.put( i, new ArrayList<Code>() );
}
//iterate list and fill dictionary
for (Code code : codes )
{
List<Code> item = dict.get( code.interface );
item.add( code );
dict.put( code.interface, item );
}

Categories