I have an object:
class Sample
{
String currency;
String amount;
}
I want an output map with sum of all objects of a specific currency.
Example-
INR 40
USD 80
EUR 20
Pointer. I have been working with the following code but its not working as I expected. You can use this code as reference:
Map<String, Double> finalResult = samples
.stream()
.collect(Collectors.toMap(
Sample::getCurrency,
e -> e.getAmount()
.stream()
.sum()));
You need to use groupingBy with summingDouble and not toMap:
Map<String, Double> finalResult = samples
.stream()
.collect(Collectors.groupingBy(
Sample::getCurrency,
Collectors.summingDouble(Sample::getAmount)
));
I consider amount is of type Double and your example is not correct (String amount;)
Just wanted to offer the following alternative for future consideration. I am also presuming you meant double for the amount as well as having the appropriate getters. You could have used Collectors.toMap as long as you included a merge function. It looks like you had the right idea but the wrong syntax.
List<Sample> samples =
List.of(new Sample("USD", 10), new Sample("USD", 30),
new Sample("INR", 40), new Sample("INR", 90),
new Sample("EUR", 20), new Sample("EUR", 50));
The third argument to toMap is a merge function to properly handle duplicates. In this cases it computes a sum of the values for each key as they are encountered.
Map<String, Double> result = samples.stream()
.collect(Collectors.toMap(
Sample::getCurrency, Sample::getAmount, Double::sum));
result.entrySet().forEach(System.out::println);
prints
EUR=70.0
USD=40.0
INR=130.0
If you really want your class to store the amount as a String, you can replace Sample::getAmount with sample->Double.valueOf(sample.getAmount()) to convert the String to a Double. But since you will probably be using these values repeatedly in computations, it is best, imo, to store the amount in its most frequently used form when the class instances are created.
Map<String, Double> finalResult = samples.stream().collect(
Collectors.groupingBy(
(Sample::getCurrency),
Collectors.summingDouble(
sample -> { return Double.parseDouble(sample.getAmount());
})));
Related
I have a String a = "1x^2 2x^3 3x^4 4x^4 " and I want to store the power in each term as key and coefficient as value to treeMap. Since there is two same power with different coefficients, I need to sum up coefficients so that the result is {2=1,3=2,4=7}. Because treemap.put(key) can only override the previous value with the same key so I could only get the result as{2=1,3=2,4=4}. How to solve that?
It is recommended to use Map::merge function to accumulate the values for multiple identical keys:
Map<Integer, Integer> terms = new TreeMap<>();
for (String term : a.split("\\s+")) {
String[] pair = term.split("x\\^");
terms.merge(Integer.valueOf(pair[1]), Integer.valueOf(pair[0]), Integer::sum);
}
System.out.println(terms);
Output:
{2=1, 3=2, 4=7}
Using Stream API this can be resolved using Collectors.toMap in a similar way using Integer::sum as a merge function:
String a = "1x^2 2x^3 3x^4 4x^4 ";
Map<Integer, Integer> terms = Arrays.stream(a.split("\\s+")) // Stream<String>
.map(t -> t.split("x\\^")) // Stream<String[]>
.collect(Collectors.toMap(
t -> Integer.valueOf(t[1]), // power as key
t -> Integer.valueOf(t[0]), // coefficient as value
Integer::sum, // add coefficients for the same power
TreeMap::new // supply TreeMap as result
));
System.out.println(terms);
Output:
{2=1, 3=2, 4=7}
I have a list of items as below
List<SomeModel> smList = new ArrayList<>();
smList.add(new SomeModel(1L,6.0f));//id = 1L and capacity = 6.0f
smList.add(new SomeModel(2L,7.0f));
smList.add(new SomeModel(3L,7.0f));
smList.add(new SomeModel(4L,7.0f));
Now I am converting this list into
Map<Float, Set<Long>> complexList = new HashMap<>();
complexList = smList.stream().collect(Collectors.groupingBy(SomeModel::getCapacity,
Collectors.mapping(SomeModel::getId, Collectors.toSet())));
here complexList
gives output as
7.0=[2, 3, 4]
6.0=[1]
Now I need to count number of values for each "capacity" giving output as
7.0=3
6.0=1
I tried
Map<Float, Long> complexCount = complexList.entrySet().stream().
collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.counting())));
complexCount.forEach((k,v)->System.out.println(k+"="+v));
and it outputs
6.0=1
7.0=1
I must be mistaking in understanding streams or not be using right methods. Can anyone suggest an approach or a solution? Reference link to streams will also be helpful.
if all you want to do is print each key of the map along with the size of the corresponding value, then there is no need to stream again as it causes unnecessary overhead. simply iterate overly the complexList and print it like so:
complexList.forEach((k,v)->System.out.println(k+"="+v.size()));
or if you really want a map then one could also do:
Map<Float, Integer> accumulator = new HashMap<>();
complexList.forEach((k,v)->accumulator.put(k, v.size()));
You are making it very complex. Easier solution below:
Map<Float, Long> complexCount = complexList
.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> new Long(entry.getValue().size())
)
);
Here, you just need to call Collectors.toMap. It has two functions one for key and another for value of the map.
If there is no restriction of using Long as Map value type, then :
Map<Float, Integer> complexCount = complexList
.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().size()
)
);
You can make use of multiple Collectors and collectingAndThen(). And don't even need to collect it to an intermediate map:
import static java.util.stream.Collectors.*;
/* ... */
Map<Float, Integer> collect = smList.stream()
.collect(groupingBy(SomeModel::getCapacity,
collectingAndThen(
mapping(SomeModel::getId, toSet()),
Set::size
)
));
Is there a better way to transform "Map<String, Collection<String>>" to "Map<String, List<String>>"?
Map<String, Collection<String>> collectionsMap = ...
Map<String, List<String>> listsaps =
collectionsMap.entrySet().stream()
.collect(Collectors.<Map.Entry<String, Collection<String>>,
String, List<String>>toMap(
Map.Entry::getKey,
e -> e. getValue().stream().collect(Collectors.toList())
)
);
Thank you for helping us improve
For cases like this, I'd consider using Map.forEach to perform the operation using side effects. Streams over maps are somewhat cumbersome, as one needs to write extra code to stream the map entries and then extract the key and value from each entry. By contrast, Map.forEach passes each key and value to the function as a separate parameter. Here's what that looks like:
Map<String, Collection<String>> collectionsMap = ...
Map<String, List<String>> listsaps = new HashMap<>(); // pre-size if desired
collectionsMap.forEach((k, v) -> listsaps.put(k, new ArrayList<>(v)));
If your map is large, you'll probably want to pre-size the destination in order to avoid rehashing during its population. To do this properly you have to know that HashMap takes the number of buckets, not the number of elements, as its parameter. This requires dividing by the default load factor of 0.75 in order to pre-size properly given a certain number of elements:
Map<String, List<String>> listsaps = new HashMap<>((int)(collectionsMap.size() / 0.75 + 1));
1) In Collectors.toMap() you don't need to repeat the generic types as these are inferred.
So :
collect(Collectors.<Map.Entry<String, Collection<String>>,
String, List<String>>toMap(...)
can be replaced by :
collect(Collectors.toMap(...)
2) The way of transforming the collection into a List could also be simplified.
This :
e -> e. getValue().stream().collect(Collectors.toList())
could be written as :
e -> new ArrayList<>(e.getValue())
You could write :
Map<String, List<String>> listsaps =
collectionsMap.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> new ArrayList<>(e.getValue())
)
);
I think that this is easier to read:
Map<String, List<String>> listsaps = new HashMap<>();
collectionsMap.entrySet()
.stream()
.forEach(e -> listsaps.put(e.getKey(), new ArrayList<>(e.getValue())));
If you just want to convert the entries to lists but don't really care about changing the type of the collection then you can use map.replaceAll:
collectionsMap.replaceAll((k, v) -> new ArrayList<>(v));
I am working on a framework where we are trying to convert our traditional loops to streams. My problem is I wrote two separate logics to get price and colors but I would like to merge both together so it will be presentable
Code to get the price values
List<Double> productPrices = product.getUpcs()
.stream()
.map(e -> e.getUpcDetails().getPrice().getRetail().getPriceValue())
.distinct()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
Code to get the colors under prices
product.getUpcs()
.stream()
.filter(e -> e.getUpcDetails().getPrice().getRetail().getPriceValue() == 74.5)
.flatMap(e -> e.getUpcDetails().getAttributes().stream())
.filter(e2 -> e2.getName().contentEquals("COLOR"))
.forEach(e3 -> System.out.println(e3.getValues().get(0).get("value")));
I harcoded price in the above section to obtain the colors, instead, i would like to get that as input from the list of price values and get an output in
Map<Double,List<colors>
output Map<75.4, {blue,black,orange}>
I tried merging these both without success, any help would be appriciated.
I would suggest you examine this or similar tutorial to get a bit of understanding how this works.
The key to the solution is to learn about Collectors.groupingBy() functionality. As a side note, there it also shows a better way of handling pricing information in Java.
But what you would need to do is something like this:
Map<Double, Set<String>> productPrices = product
.stream()
.map(e -> e.getUpcDetails())
.collect(
Collectors.groupingBy(Details::getPrice,
Collectors.mapping(Details::getColors, Collectors.collectingAndThen(
Collectors.toList(),
(set) -> set
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toSet())))
));
Since your question is a bit unclear about the details of classes involved, I assumed this simple class structure:
class Details {
private double price;
private List<String> colors;
double getPrice() { return price; }
List<String> getColors() { return colors; }
}
class Product {
private Details details;
Details getUpcDetails() { return details; }
}
```
It would be possible to optimize the code above but I specifically left the possibility to filter and map colours in the mapping collector.
You can first turn your second stream into a method that gets a List of products (assumed to be filtered/grouped by price) and transforms it to a List of colors:
List<Color> productsToColors(final List<Product> products) {
return products.stream()
.flatMap(e -> e.getUpcDetails().getAttributes().stream())
.filter(e2 -> e2.getName().contentEquals("COLOR"))
.map(e3 -> e3.getValues().get(0).get("value"))
.collect(toList());
}
You can use the groupingBy collector to gather all products by their price in a List and then with a second create a second stream and the productsToColors method get the map you want:
Map<Double, List<Color>> colors = product.getUpcs().stream()
.collect(groupingBy(e -> e.getUpcDetails().getPrice().getRetail().getPriceValue())
.entrySet().stream()
.collect(toMap(Entry::getKey, e -> productsToColors(e.getValue())));
You can also have groupingBy create a TreeMap instead so that the colors map will be sorted by price.
As a side-note beware of comparing double values for equality like this. You may want to round them first. Or use long variables multiplied by 100 (i.e. cents).
I got 3 classes.
Angestellte (simply contains some stuff like names etc.)
Department (only contains a String)
and ttest (for testing obviously)
I want to put all the workers "Angestellte" into their Departments. So basically the output should be:
Personalabteilung: 4
Buchhaltung: 3
Fertigung: 3
I am trying to put the Map as Map with Department and Long
but ultimately I would like to have the Map with String and Long.
I also think my Collectors.counting() doesn't work that way I put it.
I don't really know how to address my Stream of Strings after I have already mapped it. Thats why I put three ? in the code.
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class ttest {
public static void main(String[] args){
Department d1 = new Department("Personalabteilung");
Department d2 = new Department("Buchhaltung");
Department d3 = new Department("Fertigung");
List<Angestellte> AlleAng = Arrays.asList(
new Angestellte("Sandra","Bullock",d3,3450, "Female"),
new Angestellte("Yutta","Knie",d1,2800, "Female"),
new Angestellte("Ludwig","Herr",d3,3850, "Male"),
new Angestellte("Peter","Pan",d2,1850, "Male"),
new Angestellte("Nicky","Smith",d3,2100, "Female"),
new Angestellte("Herbert","Rotwein",d2,2450, "Male"),
new Angestellte("Sandra","Siech",d1,1100, "Female"),
new Angestellte("Florian","Schwarzpeter",d2,2800, "Male"),
new Angestellte("Herrietta","Glas",d1,2100, "Female"),
new Angestellte("Brock","Builder",d1,6000, "Male"));
Map<Department, Long> DepAnz = AlleAng.stream()
.map(a -> a.getDep())
.collect(Collectors.toMap(a.getDep???, Collectors.counting()));
}
}
If you want to group by department and your getter is called getDep() You can do
Map<Department, Long> DepAnz = AlleAng.stream()
.collect(Collectors.groupingBy(a -> a.getDep(), Collectors.counting()));
You need to use a group by:
Map<Department, Long> DepAnz =
AlleAng.stream()
.collect(Collectors.groupingBy(Angestellte::getDep, Collectors.counting()));
The groupingBy(classifier, downstream) collector is a collector that collects the Stream element into a Map where the keys are returned by the classifier and the values are the result of applying the downstream collector to all Stream elements having an equal key. In this case, what we want is to count the values so we use Collectors.counting() as the downstream collector.
If you want to group by the the name of the department instead, you could have, assmuming the getter is called .getName() to retrieve the name:
Map<String, Long> DepAnz =
AlleAng.stream()
.collect(Collectors.groupingBy(a -> a.getDep().getName(), Collectors.counting()));
This will return a count of all the different name of departements.
Map<String, Long> map = AlleAng.stream().collect(Collectors.toMap(a -> a.getDept().getName(), a -> 1L, (Long acc, Long newValue) -> acc + newValue));
This is such a verbose usage of lambdas to achieve what you need.
Use a toMap Collector in order to map a specific key and value based on the actual Angestellte references you have got.
Of course, the groupers functions are best suited, but this works as a generic example for map grouping by some criterion