Filter and Sort internal Map java - filter

I have class Person
private String name;
private int age;
private Map<String, LocalDate> carsBoughWithDate;
You can ignore name and age. The important one here is carsBoughWithDate
Due to some reason I am saving person cars bough in a map with the date
Test Data
Map<String, LocalDate> carsbought = new HashMap<>();
carsbought.put("Toyota", LocalDate.of(2017, 2, 1));
carsbought.put("Corolla", LocalDate.of(2017, 2, 1));
Person john = new Person("John", 22, carsbought);
carsbought = new HashMap<>();
carsbought.put("Vauxhall", LocalDate.of(2017, 1, 1));
carsbought.put("BMW", LocalDate.of(2017, 1, 1));
carsbought.put("Toyota", LocalDate.of(2017, 1, 1));
Person michael = new Person("Michael", 44, carsbought);
List<Person> personList = new ArrayList<>();
personList.add(john);
personList.add(michael);
Output:
[Person{name='John', age=22, carsBoughWithDate={Toyota=2017-02-01, Corolla=2017-02-01}},
Person{name='Michael', age=44, carsBoughWithDate={Vauxhall=2017-01-01, Toyota=2017-01-01, BMW=2017-01-01}}]
Now, I have to find out the person which has bought cars but then sort the person who bought the car earliest on the top in the list
Example: search person who has cars "Toyota" or BMW
This is what I have done
**
System.out.println("Before sort >" + personList);
List<Person> sortedList = Lists.newArrayList();
HashMap<LocalDate, Person> collect = Maps.newHashMap();
for (Person person : personList) {
Map<String, LocalDate> docCarsBoughWithDate = person.getCarsBoughWithDate();
collect.putAll(docCarsBoughWithDate.entrySet().stream()
.filter(map -> Lists.newArrayList("Toyota", "BMW").contains(map.getKey()))
.collect(HashMap::new,
(m, v) -> m.put(
v.getValue(),
person),
HashMap::putAll
));
}
Map<String, List<Person>> collect1 = collect.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(m -> m.getValue()).collect(Collectors.groupingBy(Person::getName));
collect1.keySet().forEach(key -> sortedList.add(collect1.get(key).get(0)));
System.out.println("after sort > " + sortedList
);
This all works
Before sort >
[Person{name='John', age=22, carsBoughWithDate={Toyota=2017-02-01, Corolla=2017-02-01}}, Person{name='Michael', age=44, carsBoughWithDate={Vauxhall=2017-01-01, Toyota=2017-01-01, BMW=2017-01-01}}]
after sort >
[Person{name='Michael', age=44, carsBoughWithDate={Vauxhall=2017-01-01, Toyota=2017-01-01, BMW=2017-01-01}}, Person{name='John', age=22, carsBoughWithDate={Toyota=2017-02-01, Corolla=2017-02-01}}]
I feel this is bit cumbersome. Can I simplify the logic?

Here you go:
List<Person> sortedList = personList.stream() //
.flatMap(p -> p.getCarsBoughWithDate().entrySet().stream() //
.filter(e -> targetCarNames.contains(e.getKey())) // filter the bought cars which are in the target bought cars.
.sorted(Entry.comparingByValue()).limit(1) // sorted and only fetch the entry with earliest bought date.
.map(e -> new SimpleEntry<>(p, e.getValue()))) // composite a new entry with the person and the earliest bought date.
.sorted(Entry.comparingByValue()).map(e -> e.getKey()).collect(toList()); //

First of all, are you sure that "this all works"? I tried your code with your test data with the following additional person:
carsbought = new HashMap<>();
carsbought.put("BMW", LocalDate.of(2017, 2, 1));
Person sally = new Person("Sally", 25, carsbought);
and she overwrote John because she happened to have bought a car at the same date.
Second, the strategy to solve complex problems is to break them down into simpler problems. For example, I would first add a method which determines the first date at which a person bought one of a set of cars:
private Optional<LocalDate> firstDateOf(Person person, Collection<String> cars)
{
return person.getCarsBoughWithDate().entrySet().stream()
.filter(e -> cars.contains(e.getKey()))
.map(Map.Entry::getValue)
.min(Comparator.naturalOrder());
}
This will be the sort key of the people. Then use this method to map each person to the sort key and finally sort the list:
List<Person> sortCarOwners(Collection<Person> people, Collection<String> cars)
{
Map<Person, Optional<LocalDate>> personToDateMap = people.stream()
.collect(Collectors.toMap(p -> p, p -> firstDateOf(p, cars)));
return personToDateMap.entrySet().stream()
.filter(e -> e.getValue().isPresent())
.sorted(Comparator.comparing(e -> e.getValue().get()))
.map(e -> e.getKey())
.collect(Collectors.toList());
}
I don't know if you consider this "less cumbersome", but I hope it helps.

Related

Convert list of complex objects to Map using Java 8

I have an array of subjects
List<String> subjects = Arrays.asList(“physics”, “maths”);
I wanted to create a dummy list of users for each of these subjects and add them to a map with key as subject and value as List
Something like
Map<String,List<User>> userMap = new HashMap<>();
for(String subject: subjects){
List<User> users = new ArrayList<User>();
for(int i=0;i<10;i++){
User user = new User(“first name”+i+subject,”last name”+i+subject);
users.add(user);
}
userMap.put(subject,users);
}
I wanted to try this with the Java 8. Just tried something below, but doesn’t look like the right way.
subjects.stream().map((subjectName)->{
List<User> userList = new ArrayList<User>();
for(int i=0;i<10;i++){
User user = new User(“first name”+i+subject,”last name”+i+subject);
userList.add(user);
}
})
subjects.stream()
.map(subjectName -> {
List<User> users = .... create the users;
return new SimpleEntry<>(subjectName, users);
})
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
This would be one way if you really wanted to do it with java-8 and streams. One improvement would be to have a method that takes a String subjectName and create that Entry for example:
private static Entry<String, List<User>> createEntry(String subjectName) {
List<User> users = .... create the user;
return new SimpleEntry<>(subjectName, users);
}
And use it with:
subjects.stream()
.map(YourClass::createEntry)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
Just notice that your loop is the cleanest way to do it IMO
One way to do it with java 8:
Map<String,List<User>> userMap = new HashMap<>();
subjects.forEach(s -> {
for (int i = 0; i < 10; i++)
userMap.computeIfAbsent(s, k -> new ArrayList<>())
.add(new User("first name" + i + subject, "last name" + i + subject));
});
Let's do this one step at a time. First, the inner loop for creating 10 users can be written with streams as:
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
List<User> userList = IntStream.range(0, 10)
.mapToObj(i -> new User("first name" + i + subject, "last name" + i + subject)
.collect(toList());
And the outer loop can be written as
subjects.stream()
.collect(toMap(
subject -> subject, // key for the map is the subject
subject -> ... // whatever should be the value of the map
));
And now we can put it together:
Map<String, List<User>> userMap = subjects.stream()
.collect(toMap(
subject -> subject,
subject -> IntStream.range(0, 10)
.mapToObj(i -> new User("first name" + i + subject, "last name" + i + subject))
.collect(toList())
));

For loop optimisation with Java8

How do achieve this with one line ?
I am currently trying to do this
Example :
{{"id" :"2", values: ["10","11", "12"]} , {"id" : "3", values : ["23"]}}
to
{{"id" :"2","value": "10"},{"id": "2","value":"11"},
{"id" :"3","value":"23"} , {"id" : "2", "value":"12"}}
My java code is
Map<Integer, List<Integer>> attrMap = new HashMap<>();
//getAllData() & item.getValues() both returns List
getAllData().forEach(item - > {
item.getValues().forEach(val - > {
attrMap.computeIfAbsent(item.getId(), (k) - >
new ArrayList < > ()).add(val.getValue());
});
});
How can i do it only 1 line ?
Since the IDs are unique, you can do it like
Map<Integer, List<Integer>> attrMap = getAllData().stream()
.collect(Collectors.toMap(
item -> item.getId(),
item -> item.getValues().stream().map(i->i.getValue()).collect(Collectors.toList())));
But, of course, this will still have the performance characteristics of two nested loops. It would support parallel processing, though, but I doubt that your data will be large enough to draw a benefit from parallel processing.
Further, note that the resulting map still structurally matches your first pattern,
{{"id" :"2", values: ["10","11", "12"]} , {"id" : "3", values : ["23"]}}
you just converted item to an entry of the result Map and val to an element of a List<Integer>.
Assuming you have your input like this:
static class Data {
private final int id;
private final List<Integer> values;
public int getId() {
return id;
}
public List<Integer> getValues() {
return values;
}
public Data(int id, List<Integer> values) {
super();
this.id = id;
this.values = values;
}
}
It could be done via:
List<SimpleEntry<Integer, Integer>> result = Arrays.asList(
new Data(2, Arrays.asList(10, 11, 12)),
new Data(3, Arrays.asList(23)))
.stream()
.flatMap(d -> d.getValues().stream().map(x -> new AbstractMap.SimpleEntry<>(d.getId(), x)))
.collect(Collectors.toList());
System.out.println(result); // [2=10, 2=11, 2=12, 3=23]
I am collecting those to Pair or AbstractMap.SimpleEntry.

HashMap manipulation using streams Java 8

Please let me know if there is a possibility of changing the below code in terms of Java 8 using parallel streams?
I am looking for an option to run the "outer for loop" in parallel and finally all the values of stationMap gets collected together?
Map<Integer, Set<Integer>> stationMap = new HashMap<>();
Map<Integer, Set<Integer>> routes = function();
for (Map.Entry<Integer, Set<Integer>> entry : routes.entrySet())
{
Set<Integer> stations = entry.getValue();
for (Integer station : stations) {
Set<Integer> temporaryStations = new HashSet<>(stations);
Set<Integer> stationSet = stationMap.get(station);
if (stationSet == null) {
stationSet = new HashSet<>();
temporaryStations.remove(station);
stationSet.addAll(temporaryStations);
stationMap.put(station, stationSet);
} else {
temporaryStations.remove(station);
stationSet.addAll(temporaryStations);
}
}
}
More shorter version:
routes.forEach((k, stations) -> {
stations.forEach((station) -> {
Set<Integer> stationSet = stationMap.get(station);
if (stationSet == null) {
stationSet = new HashSet<>();
stationSet.addAll(stations);
stationMap.put(station, stationSet);
} else {
stationSet.addAll(stations);
}
});
});
Even the long pre-Java 8 version can be simplified as there is no need to iterate over the entry set, when you are only processing the values and there is no need for code duplication within the two conditional branches:
Map<Integer, Set<Integer>> routes = function();
Map<Integer, Set<Integer>> stationMap = new HashMap<>();
for(Set<Integer> stations: routes.values()) {
for(Integer station: stations) {
Set<Integer> temporaryStations = new HashSet<>(stations);
temporaryStations.remove(station);
Set<Integer> stationSet = stationMap.get(station);
if (stationSet == null) {
stationMap.put(station, temporaryStations);
} else {
stationSet.addAll(temporaryStations);
}
}
}
using Java 8 features, you may get the improved variant:
routes.values().forEach(stations ->
stations.forEach(station -> {
Set<Integer> temporaryStations = new HashSet<>(stations);
temporaryStations.remove(station);
Set<Integer> old = stationMap.putIfAbsent(station, temporaryStations);
if(old!=null) old.addAll(stations);
})
);
though it might be simpler to first merge all values and remove the keys afterwards in one step:
routes.values().forEach(stations ->
stations.forEach(station ->
stationMap.computeIfAbsent(station, key -> new HashSet<>()).addAll(stations)
)
);
stationMap.forEach((k,set) -> set.remove(k));
It’s possible to formulate an equivalent (parallel) Stream operation:
Map<Integer, Set<Integer>> stationMap=routes.values().parallelStream()
.flatMap(stations -> stations.stream().map(station -> {
Set<Integer> temporaryStations = new HashSet<>(stations);
temporaryStations.remove(station);
return new AbstractMap.SimpleImmutableEntry<>(station, temporaryStations);
})
).collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (a,b) -> {a.addAll(b); return a; }));
but this may also be simpler when removing the keys from the value set in a post processing step:
Map<Integer, Set<Integer>> stationMap=routes.values().parallelStream()
.flatMap(stations -> stations.stream().map(station ->
new AbstractMap.SimpleImmutableEntry<>(station, new HashSet<>(stations))
)
).collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (a,b) -> {a.addAll(b); return a; }));
stationMap.entrySet().parallelStream().forEach(e -> e.getValue().remove(e.getKey()));
or you use a custom collector instead of flatMap:
Map<Integer, Set<Integer>> stationMap=routes.values().parallelStream()
.collect(HashMap::new,
(map,stations) -> stations.forEach(station ->
map.computeIfAbsent(station, key -> new HashSet<>()).addAll(stations)
),
(m1,m2) -> m2.forEach((k,v)->m1.merge(k, v, (a,b)->{a.addAll(b); return a;})));
stationMap.entrySet().parallelStream().forEach(e -> e.getValue().remove(e.getKey()));
this might be more efficient as it doesn’t need the temporary Map.Entry instances.

Java 8 sort Map<String, List<Double>>

How to sort a Map > according to average of all the values in the list using Java8 streams?
I couldn't figure out how to collect the sorted map to another instance of map. Thanks for the help in advance.
Here is the code that I tried
Map<String,List<Double>> map = new HashMap<String,List<Double>>();
LinkedList<Double> list1 = new LinkedList<Double>(Arrays.asList(12.5,45.67));
map.put("1",list1);
LinkedList<Double> list2 = new LinkedList<Double>(Arrays.asList(13.5,49.67));
map.put("2", list2);
LinkedList<Double> list3 = new LinkedList<Double>(Arrays.asList(10.5,9.67));
map.put("3", list3);
LinkedList<Double> list4 = new LinkedList<Double>(Arrays.asList(1.5,40.67));
map.put("4", list4);
map.entrySet().stream().sorted(new Comparator<Map.Entry<String, List<Double>>>() {
#Override
public int compare(Entry<String, List<Double>> arg0, Entry<String, List<Double>> arg1) {
return (int)(((LinkedList<Double>)arg1.getValue()).stream().mapToDouble(Double::doubleValue).sum() - ((LinkedList<Double>)arg0.getValue()).stream().mapToDouble(Double::doubleValue).sum());
}
});
System.out.println(map);
The output I get is still the same Map with out sorting. The expected output here is a map with the entries in this order
<"2", list2>
<"1", list1>
<"4", list4>
<"3", list3>
Edit:
Here is the solution I got to
Map<String,List<Double>> map = new HashMap<String,List<Double>>();
LinkedList<Double> list1 = new LinkedList<Double>(Arrays.asList(12.5,45.67));
map.put("1",list1);
LinkedList<Double> list2 = new LinkedList<Double>(Arrays.asList(13.5,49.67));
map.put("2", list2);
LinkedList<Double> list3 = new LinkedList<Double>(Arrays.asList(10.5,9.67));
map.put("3", list3);
LinkedList<Double> list4 = new LinkedList<Double>(Arrays.asList(1.5,40.67));
map.put("4", list4);
LinkedHashMap<String,List<Double>> orderedMap = new LinkedHashMap<String,List<Double>>();
Iterator<Entry<String, List<Double>>> iterator = map.entrySet().stream().sorted(new Comparator<Map.Entry<String, List<Double>>>() {
#Override
public int compare(Entry<String, List<Double>> arg0,
Entry<String, List<Double>> arg1) {
return (int)((((LinkedList<Double>) arg1.getValue()).stream().mapToDouble(Double::doubleValue).sum() - ((LinkedList<Double>)arg0.getValue()).stream().mapToDouble(Double::doubleValue).sum()));
}
}).iterator();
while(iterator.hasNext()){
Entry<String, List<Double>> next = iterator.next();
orderedMap.put(next.getKey(), next.getValue());
}
System.out.println(orderedMap);
I am not happy with this solution. Is there a precise and better way of doing this?
Your comparator doesn’t do the job you want, or at least not always. It works only if all the lists have the same size because you are comparing the sum not the average values for each list. I created a new Comparator based on lists average values and simplified the code. Below is my code:
Comparator<Map.Entry<String,List<Double>>> byAverange =
(Entry<String,List<Double>> o1, Entry<String,List<Double>> o2)->
{
return ((Double)o2.getValue().stream().mapToDouble(a -> a).average().getAsDouble())
.compareTo(
o1.getValue().stream().mapToDouble(a -> a).average().getAsDouble());
};
LinkedHashMap<String, List<Double>> ordered = new LinkedHashMap<>();
map.entrySet().stream().sorted(byAverange).forEach(entry -> ordered.put(entry.getKey(), entry.getValue()));
System.out.println(ordered);
output:
{2=[13.5, 49.67], 1=[12.5, 45.67], 4=[1.5, 40.67], 3=[10.5, 9.67]}
hope this can help.

How to sort String in Arraylist

I have an ArrayList where I add customers. What i wnat to do is that i want to sort them, so they appear sorted on Console.
private static ArrayList <Kund> klista = new ArrayList<>();
Kund kundd = new Kund("a","b");
System.out.print("Namn: ");
String namn = scr.next();
System.out.print("Adress: ");
String adress = scr.next();
if (!namnKontroll(namn)){
System.out.println (namn + " " +"har lagts till \n");
klista.add(kundd);
Kund k = new Kund(namn, adress);
klista.add(k);
}else{
System.out.println("Kunden med det namnet finns redan i systemet!");
}
// this is how i add customers to my ArrayList. So now, how it is possible to sort those names in ArrayList. I want to sort them with Collections. thanks
Try use Collections.sort(klista, theComparator). You will need create a Comparator, like this:
public class KundComparator implements Comparator<Kund> {
#Override
public int compare(Kund o1, Kund o2) {
// write comparison logic here
return o1.getID().compareTo(o2.getID());
}
}
Then use the Comparator:
Collections.sort(klista, new KundComparator());
If you are using Java 8, you can do like this:
Collections.sort(klista, (Kund k1, Kund k2) -> k1.getId().compareTo(k2.getId()));

Resources