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.
Related
I am trying to iterate over three lists of different size but not getting the exact logic of how i can retrieve data from them and store in another list.
I was able to handle up to two list until I add some more filtration to the elements. For now I am using 3 for loops but i want to use Java 8 streams if possible. Can someone please suggest me the correct logic for the below iterations.
public class CustomDto {
public static void main(String... args) {
List<String> list1 = Arrays.asList("Hello", "World!");
List<String> list2 = Arrays.asList("Hi", "there");
List<String> list3 = Arrays.asList("Help Me");
Map<Integer, Object> map = new HashMap<>();
for (int i = 0; i < list1.size(); i++) {
List<String> list4 = new LinkedList();
for (int j = 0; j < list2.size(); j++) {
for (int k = 0; k < list3.size(); k++) {
if (!(list2.get(j).equals(list3.get(k))))
list4.add(list2.get(j));
}
if (j > list4.size() - 1) {
list4.add(null);
}
}
map.put(i, list4);
}
}
}
All i want to convert the above code into stream, in which i can iterate a list inside another list and can use the index of one another.
public static void main(String... args) {
List<String> list1 = Arrays.asList("Hello", "World!");
List<String> list2 = Arrays.asList("Hi", "there");
List<String> list3 = Arrays.asList("Help Me");
List<String> list4 = concat(list1, list2, list3); //you can add as many lists as you like here
System.out.println(list4);
}
private static List<String> concat(List<String>... lists) {
return Stream.of(lists)
.flatMap(List::stream)
.collect(Collectors.toList());
}
Output
[Hello, World!, Hi, there, Help Me]
Try this create a multiple dimension array out of List, from there you can use stream too
Customdto[][] listArray = new Customdto[][]{ l1.toArray(new Customdto[]{})
, l2.toArray(new Customdto[]{}), l3.toArray(new Customdto[]{})};
int size = listArray[0].length > listArray[1].length && listArray[0].length > listArray[2].length ?listArray[0].length
:(listArray[1].length > listArray[2].length ? listArray[1].length:listArray[2].length);
for(int i = 0; i <size;i++)
{
if(listArray[0].length >i && listArray[1].length >i && listArray[2].length >i &&
listArray[0][i].equals(listArray[1][i])
&& listArray[1][i].getCalendarDate().equals(listArray[2][i].getCalendarDate()))
{
l4.add(listArray[1][i]);
}else
{
l4.add(null);
}
}
Tried with Below Input
List<Customdto> l1 = new ArrayList<Customdto>();
List<Customdto> l2 = new ArrayList<Customdto>();
List<Customdto> l3 = new ArrayList<Customdto>();
List<Customdto> l4 = new ArrayList<Customdto>();
l1.add(new Customdto(1));
l1.add(new Customdto(2));
l1.add(new Customdto(3));
l1.add(new Customdto(4));
l2.add(new Customdto(1));
l2.add(new Customdto(2));
l2.add(new Customdto(3));
l3.add(new Customdto(1));
l3.add(new Customdto(2));
Output is
[Customdto [id=1], Customdto [id=2], null, null]
I have class Element with a list, my intended output is like this:
Map<String , List<Element>>
{
1 = [Element3, Element1],
2 = [Element2, Element1],
3 = [Element2, Element1], 4=[Element2]
}
And my input is set of element objects, I used forEach to get the desired outcome, but I'm looking for how to collect it using collectors.toMap. Any inputs are much appreciated
Set<Element> changes = new HashSet();
List<String> interesetList = new ArrayList();
interesetList.add("1");
interesetList.add("2");
interesetList.add("3");
Element element = new Element(interesetList);
changes.add(element);
interesetList = new ArrayList();
interesetList.add("2");
interesetList.add("3");
interesetList.add("4");
element = new Element(interesetList);
changes.add(element);
Map<String, List<Element>> collect2 = new HashMap();
changes.forEach(element -> {
element.getInterestedList().forEach(tracker -> {
collect2.compute(tracker, ( key , val) -> {
List<Element> elementList = val == null ? new ArrayList<Element>() : val;
elementList.add(Element);
return elementList;
});
});
});
class Element {
List<String> interestedList;
static AtomicInteger sequencer = new AtomicInteger(0);
String mName;
public Element(List<String> aList) {
interestedList = aList;
mName = "Element" + sequencer.incrementAndGet();
}
public List<String> getInterestedList() {
return interestedList;
}
#Override
public String toString() {
return mName;
}
}
You can do it by using Collectors.groupingBy instead of Collectors.toMap, along with Collectors.mapping, which adapts a collector to another collector:
Map<String, List<Element>> result = changes.stream()
.flatMap(e -> e.getInterestedList().stream().map(t -> Map.entry(t, e)))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
You need to use the Stream.flatMap method first and then pair the elements of the inner lists with the current Element instance. I did this via the new Java 9's Map.entry(key, value) method. If you're not on Java 9 yet, you could change it to new AbstractMap.SimpleEntry<>(key, value).
After flatmapping, we need to collect instances of Map.Entry. So I'm using Collectors.groupingBy to classify entries by key (where we had previously stored each element of the inner lists, aka what you call tracker in your code). Then, as we don't want to have instances of List<Map.Entry<String, Element>> as the values of the map, we need to transform each Map.Entry<String, Element> of the stream to just Element (that's why I'm using Map.Entry::getValue as the first argument of Collectors.mapping). We also need to specify a downstream collector (here Collectors.toList()), so that the outer Collectors.groupingBy collector knows where to place all the adapted elements of the stream that belong to each group.
A shorter and surely more efficient way to do the same (similar to your attempt) could be:
Map<String, List<Element>> result = new HashMap<>();
changes.forEach(e ->
e.getInterestedList().forEach(t ->
result.computeIfAbsent(t, k -> new ArrayList<>()).add(e)));
This uses Map.computeIfAbsent, which is a perfect fit for your use case.
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.
This question is partially answered here.
My map will have trade Ids grouped by trade Type. This link gives me Trades grouped by tradeType. I know I can further manipulate it to get desired result, but just wondering if its possible in one go.
Here is my code.
public static void main(String[] args) {
Trade trade1 = new Trade(1, TradeStatus.NEW, "type1",1);
Trade trade2 = new Trade(2, TradeStatus.FAILED, "type2",1);
Trade trade3 = new Trade(3, TradeStatus.NEW, "type1",1);
Trade trade4 = new Trade(4, TradeStatus.NEW, "type3",1);
Trade trade5 = new Trade(5, TradeStatus.CHANGED, "type2",1);
Trade trade6 = new Trade(6, TradeStatus.EXPIRED, "type1",2);
List<Trade> list = new ArrayList<>();
list.add(trade1);
list.add(trade2);
list.add(trade3);
list.add(trade4);
list.add(trade5);
list.add(trade6);
Map<String, List<Trade>> result =
list.stream().collect(Collectors.groupingBy(Trade::getTradeType));
System.out.println(result);//prints Trades grouped by trade type
Map<String, List<String>> unvaluedtradesMap = new HashMap<>();
for (Trade trade : list) {
String tradeType = trade.getTradeType();
if(unvaluedtradesMap.containsKey(trade.getTradeType())){
List<String> unValuedTrades = unvaluedtradesMap.get(tradeType);
unValuedTrades.add(trade.getId());
unvaluedtradesMap.put(tradeType, unValuedTrades);
}else{
unvaluedtradesMap.put(tradeType, Lists.newArrayList(trade.getId()));
}
}
System.out.println(unvaluedtradesMap);//prints TradeIDS grouped by trade type
}
You can do this by chaining a mapping collector to groupingBy :
Map<String, List<String>> unvaluedtradesMap
= list.stream().collect(Collectors.groupingBy(Trade::getTradeType,
Collectors.mapping(Trade::getId,
Collectors.toList())));
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.