A question from a total newbie. Sorry.
I have this customersOrders HashMap that takes String as keys and ArrayList<Double> as values. I need to find the total sum of orders for each customer and the maximum total sum in order to find the biggest customer. How do I manage to do that using just nested For loops and HashMap methods? I'm totally stuck on that.
public class Test {
public static void main(String[] args) {
HashMap<String, ArrayList<Double>> customersOrders;
customersOrders = new HashMap<>();
ArrayList<Double> orders = new ArrayList<>();
orders.add(55.50);
orders.add(78.30);
orders.add(124.75);
customersOrders.put("John", orders);
orders = new ArrayList<>();
orders.add(28.35);
orders.add(37.40);
customersOrders.put("Helen", orders);
orders = new ArrayList<>();
orders.add(150.10);
customersOrders.put("Thomas", orders);
orders = new ArrayList<>();
orders.add(230.45);
orders.add(347.20);
customersOrders.put("Robert", orders);
orders = new ArrayList<>();
orders.add(530.25);
orders.add(325.40);
orders.add(11550.70);
orders.add(2480.65);
customersOrders.put("Jennifer", orders);
System.out.println(customersOrders);
}
}
So far I've been trying to do something like this but obviously with no success:
double maxOrder = 0;
String customerName = "";
for (ArrayList<Double> orders : customersOrders.values()) {
for (double orderPrice : orders) {
if (orderPrice > maxOrder) {
maxOrder = orderPrice;
}
}
}
for (String name : customersOrders.keySet()) {
if (maxOrder.equals(customersOrders.get(name))) {
customerName = name;
break;
}
}
You could create another HashMap which keeps your sums and then find the maximum of them.
First iterate through all your HashMap keys and find the sums for each customer like this:
HashMap<String, ArrayList<Double>> customerOrders = new HashMap<>();
// Fill your HashMap as you've done above
HashMap<String, Double> customerSums = new HashMap<>(); // The new HashMap that keeps the sums
for (String customerName : customerOrders.keySet()) // Foreach customer
{
double currentSum = 0;
for (Double aDouble : customerOrders.get(customerName))
{
currentSum += aDouble; // Sum the orders
}
customerSums.put(customerName, currentSum); // Put the sum in your new HashMap
}
Now finding the maximum should be very straightforward. Try to do that :D
Maybe something like this:
Map<String, Double> customersSum = new HashMap<>();
Double maxSum = Double.MIN_VALUE;
String customerWithMaxSum;
for (Map.Entry<String, List<Double>> entry: customersOrders.entrySet()) {
String customerId = entry.getKey();
List<Double> customerOrders = entry.getValue();
Double customerSum = customerOrders.stream().mapToDouble(d -> d).sum();
customersSum.put(customerId, customerSum);
if (customerSum > maxSum) {
maxSum = customerSum;
customerWithMaxSum = customerId;
}
}
Also could use stream pi for the second part:
Optional<String> customerWithMaxSum = customersSum.entrySet()
.stream()
.max(Comparator.comparingDouble(Map.Entry::getValue))
.map(Map.Entry::getKey);
Your logic code is not correct
for (double orderPrice : orders) {
if (orderPrice > maxOrder) {
maxOrder = orderPrice;
}
}
You are not adding the prices then comparing with maxOrder you instead compare each price with maxOrder. this is not what was required in your question.
You should do this instead
double ordersSum = 0;
for (double orderPrice : orders) {
ordersSum += orderPrice;
}
if (ordersSum > maxOrder) {
maxOrder = ordersSum;
}
and in your second for loop, you are comparing a double value with an arraylist which should result a compilation error you should compare the sum for products from each custom to maxOrder.
I should note here that this code is not very efficient you can instead loop on keys as you did in the second loop then calculate the sum inside of it and compare to maxOrder I will leave this implementation as an exercise for you.
You are iterating over the values in the orders Map. This doesn't tell you who that is associated with. Instead, you could iterate over the keyset or entryset, calculate the sum for that customer, and compare this to a running maximum. Since you specified using for-loops, the following excludes use of the Stream API.
String maxOrderCustomer = null;
double maxOrder = 0.0;
for (Map.EntrySet<String,List<Double>> entry : customerOrders.entrySet()) {
Double sum = 0.0;
for (Double order : entry.getValue();
sum += order;
}
if (order > maxOrder) {
maxOrder = order;
maxOrderCustomer = entry.getKey();
}
}
At this point, you will have the name of the customer with the largest sum and that sum value. If you want to display the relevant list of orders, you can use the name to pull the list from the original Map.
Related
I am having a sorted list like below
with the below code I have sorted.
Comparator<MyBean> typeComparator = Comparator.comparing(MyBean::getType);
Comparator<MyBean> costComparator = Comparator.comparing(MyBean::getCost);
Comparator<MyBean> multipleFieldsComparator = typeComparator.thenComparing(costComparator);
Collections.sort(mbList, multipleFieldsComparator);
Now I am identifying the total cost of each Type with the below code.
Map<String, Double> sum = mbList.stream().collect(
Collectors.groupingBy(MyBean::getType, Collectors.summingDouble(MyBean::getCost)));
Here I am having total cost of each type. like A = 109490.03 and B = 4431.218
Now I need to add this Total Cost object in middle of the mbList list.
My desired output should be like below
My whole code is like below.
Comparator<MyBean> typeComparator = Comparator.comparing(MyBean::getType);
Comparator<MyBean> costComparator = Comparator.comparing(MyBean::getCost);
Comparator<MyBean> multipleFieldsComparator = typeComparator.thenComparing(costComparator);
Collections.sort(mbList, multipleFieldsComparator);
Map<String, Double> sum = mbList.stream().collect(
Collectors.groupingBy(MyBean::getType, Collectors.summingDouble(MyBean::getCost)));
System.out.println(" $$$$$$$$$$$$$$$$$ "+sum);
for (MyBean mb : mbList) {
row = sheetType.createRow(sheetType.getPhysicalNumberOfRows());
cell = row.createCell(cellnum++);
cell.setCellValue((String) mb.getType() + "-" + mb.getCategory());
cell = row.createCell(cellnum++);
cell.setCellValue(mb.getCost());
cellnum = 0;
}
MyBean.java
public class MyBean{
private String type;
private Double total;
private String xxx;
private String yyy;
//setters and getters
}
Here my question is how can I add the Total cost at the end of each sorting value like After A Total cost needs to be added and After B we can add Total cost....
You're almost there, but instead of aggregating the sum directly, group by type first, and then calculate the sums for every sub-list:
a) group by type
Map<String, List<MyBean>> byType = mbList.stream()
.sorted(multipleFieldsComparator)
.collect(
Collectors.groupingBy(
MyBean::getType,
LinkedHashMap::new, // preserves order
Collectors.toList()
));
b) calculate sum for each type
for (Entry<String, List<MyBean>> entry : byType.entrySet()) {
List<MyBean> typeList = entry.getValue();
double sum = typeList.stream().mapToDouble(MyBean::getCost).sum();
typeList.add(new MyBean(entry.getKey(), sum));
}
c) flatten all the lists to one big list (they're already ordered correctly)
List<MyBean> listWithSums = byType.values()
.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
here is quick draft of idea, there must be better solution than this but this will do the trick (will update this answer later to improve the code)
lastMatch = "", totalCost = 0;
for ( mbList i = 0 -> n) {
currentMatch = mbList[i].getType(); // return A- or B- ...
if(currentMatch != lastMatch){
if(lastMatch != ""){
> print totalCost; // to avoid printing Total cost at first row
}
lastMatch = currentMatch;
totalCost = 0;
}
> print col name = col value
totalCost += mbList[i].getCost(); // keep adding cost for your currentRegExrMatch
}
This question already has answers here:
How to sum values in a Map with a stream?
(5 answers)
Closed 2 years ago.
I need some help, i was trying to get the sum of the values but im stuck
map values i want to sum
Grades grades = new Grades(Arrays.asList(1,2,3,4));
Grades grades2 = new Grades(Arrays.asList(2,3,4,5));
Grades grades3 = new Grades(Arrays.asList(4,5,6,1));
Grades grades4 = new Grades(Arrays.asList(1,2,2,4));
HashMap<Pupil, Grades> map = new HashMap<Pupil, Grades>();
map.put(pupil, grades);
map.put(pupil1, grades2);
map.put(pupil2, grades3);
map.put(pupil3, grades4);
I tried to do it by using for-each
int sum = 0;
for (int a : map.values()) {
sum += a;
}
But im getting an error "incompatible types: Grades cannot be converted to int, line 49"
class Grades{
private List<Integer> grades;
public Grades(List<Integer> grades){
this.grades = grades;
}
}
The method HashMap.values() returns a Collection<V> so here a Collection<Grades>, so when iterating you got a Grade. Then each Grade has a list of int
int sum = 0;
for (Grades g : map.values()) {
for(int a : g.getGrades()){ // use the getter name you have in Grade class
sum += a;
}
}
Using Streams it'll look like
int sum = map.values() // Collection<Grades>
.stream() // Stream<Grades>
.map(Grades::getGrades) // Stream<List<Integer>>
.flatMap(List::stream) // Stream<Integer>
.mapToInt(Integer::intValue) // IntStream
.sum(); // int
Tip 1 : For better understanding of what exactly the return type of your method would look like, you can hover pointer of calling method, so you will see something like below in the picture, by that you will get better understanding
Tip 2 : When you are initializing data members in your constructor, you no need to write setters untill and unless you want the change the value of datamember, but getter are must as you need to get the values which are initialized at the time of object creation.
Hope you are clear with above two points, now coming to the problem statement : PFB code for sum of 'v' in HashMap
public class StackOverFlowProblem {
public static void main(String[] args) {
Grades grades1 = new Grades(Arrays.asList(1,2,3,4));
Grades grades2 = new Grades(Arrays.asList(2,3,4,5));
Grades grades3 = new Grades(Arrays.asList(4,5,6,1));
Grades grades4 = new Grades(Arrays.asList(1,2,2,4));
HashMap<Pupil, Grades> map = new HashMap<Pupil, Grades>();
map.put(new Pupil(), grades1);
map.put(new Pupil(), grades2);
map.put(new Pupil(), grades3);
map.put(new Pupil(), grades4);
int sum =0;
for (Grades gradeValues : map.values()) {
for (Integer grades : gradeValues.getGrades()) {
sum += grades;
}
}
System.out.println(sum);
}
}
class Grades{
private List<Integer> grades;
public Grades(List<Integer> grades){
this.grades = grades;
}
public List<Integer> getGrades() {
return grades;
}
}
I have an array of Objects which includes products that has been sold and the amount of how many were sold in that one order. There may be one product appearing many times. I am trying to create a code, which would return the most popular product. e.g. Object1 10, Object 2 15, Object1 5, Object3 4 and it should return Object1 and the number 15 (10+5). Order has parameters product name and quantity, with getters and setters.
My idea was to use a set, which would get rid of all the duplicates (code example below), however it turned out to be a bust, since set would not work in this case and I wasn't able to even finish it with a set. I don't know what else to try. Thanks!
public class Orders {
private Product[] orders;
public Orders() {
orders = new order[0];
}
public void add(Order order) {
Order[] newOrder = Arrays.copyOf(orders,
orders.length + 1);
newOrder[newOrder.length - 1] = order;
orders = newOrders;
}
// the method described starts here
public Product findTopProduct() {
int[] array;
Set<String> set = new HashSet<>();
for (int i=0; i<orders.length; i++) {
set.add(orders[i].getProductName());
}
array = new int[set.size()];
for (int i=0; i<orders.length; i++) {
for (int i1=0; i1<set.size();i1++) {
}
}
}
}
Consider a little bit wider quest (find the max, min, average...). There is one powerful class called IntSummaryStatistics
You can try to understand it and "exstract" the desired data.
I'll give you an example how to use the Map collection and what would be the output for some sample data:
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.Map;
import java.util.stream.Collectors;
public class Answer {
static class Product{
private String name;
private int noOfSales;
public Product(String name, int noOfSales){
this.name = name;
this.noOfSales = noOfSales;
}
public String getName() {
return name;
}
public int getNoOfSales(){
return noOfSales;
}
}
public static void main(String[] args) {
Product[] products = {
new Product("tea", 4),
new Product("apple", 12),
new Product("tea", 15)
};
Map<String, IntSummaryStatistics> mapOfProducts =
Arrays.stream(products)
.collect(Collectors.groupingBy(
Product::getName,
Collectors.summarizingInt(Product::getNoOfSales)));
System.out.println(mapOfProducts);
}
}
The output is:
{apple=IntSummaryStatistics{count=1, sum=12, min=12, average=12.000000, max=12}, tea=IntSummaryStatistics{count=2, sum=19, min=4, average=9.500000, max=15}}
Please, notice that finding only maximum reducing method. (This contains an example code)
This is best addressed using Map data structure. You need to map each product name to its quantity across all orders you have. I'm using HashMap in Java, but you also need to understand the Map data structure and how/when to use it, here's the way I would implement it (I'm assuming you Product class has another property called quantity):
public Product findTopProduct() {
Map<String, Integer> map = new HashMap<>(); // this map should contain each product name pointing to its quantity across all orders (obj1 -> 10, obj2 -> 15, obj3 -> 4, ...)
for (int i=0; i<orders.length; i++) {
if(map.containsKey(orders[i].getProductName())) { // if it already exists in map, then you need to add the quantity only and not create a new entry
map.put(orders[i].getProductName(), map.get(orders[i].getProductName() + orders[i].getQuantity()));
} else { // add the productName maping to its quantity
map.put(orders[i].getProductName(), order[i].getQuantity);
}
}
String productName = null;
Integer max = null;
for(Map.Entry<String, Integer> entry : map.entrySet()) { // loop on map entries
if(productName == null && max == null) { // the first iteration they are both null
productName = entry.getKey();
max = entry.getValue();
continue;
}
if(entry.getValue() > max) { // maximize
max = entry.getValue();
productName = entry.getKey();
}
}
Product product = new Product(productName, max);
return product;
}
Try this:
public static Product findTopProduct(Product[] orders) {
Map<String,List<Product>> var0 = Stream.of(orders)
.collect(Collectors.groupingBy(Product::getName));
int maxCount = Integer.MIN_VALUE;
Product top = null;
for(Map.Entry<String,List<Product>>entry : var0.entrySet()) {
int size = entry.getValue().size();
if (size > maxCount) {
top = entry.getValue().get(0);
maxCount = size;
}
}
return top;
}
This getTopProduct method returns a new Product with the product name and total quantity.
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class TestClass {
public static void main(String[] args) {
//sample list of objects containing productName and quantity fields
List<Product> productList = new ArrayList<>();
Product p1 = new Product("toy", 10);
Product p2 = new Product("car", 14);
Product p3 = new Product("food", 2);
Product p4 = new Product("toy", 6);
productList.add(p1);
productList.add(p2);
productList.add(p3);
productList.add(p4);
Product topProduct = getTopProduct(productList);
System.out.println("Most sold product: " + topProduct.getProductName() + ", " + topProduct.getQuantity());
}
private static Product getTopProduct(List<Product> productList) {
//map to hold products and total quantity
Map<String, Integer> map = new HashMap<>();
//populate the map
for(Product product : productList) {
if(map.containsKey(product.getProductName())) {
//if product already exists in map, add on the quantity
map.put(product.getProductName(), map.get(product.getProductName()) + product.getQuantity());
}else {
//if product doesnt exist in map yet, add it
map.put(product.getProductName(), product.getQuantity());
}
}
//find the highest value in the map which is the product with highestQuantity
int highestQuantity = Collections.max(map.values());
String productWithHighestQuantity = "";
//go through map to find which product had the highest quantity
for (Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue()==highestQuantity) {
productWithHighestQuantity = entry.getKey();
break;
}
}
return new Product(productWithHighestQuantity, highestQuantity);
}
}
So I have this program that calculates the sum of all the petshops with the same key but different values. However, now, I would like to calculate the average of each petshop with the same key. I was thinking about using a counter in order to get how many times a petshop is contained in the arraylist. But it does not work. would I need to run another for each loop?
public class AverageCost {
public void calc(ArrayList<Pet> pets) {
Map<String, Double> hm = new HashMap<>();
for (Pet i : pets) {
String name = i.getShop();
// If the map already has the pet use the current value, otherwise 0.
double price = hm.containsKey(name) ? hm.get(name) : 0;
price += i.getPrice();
hm.put(name, price);
}
System.out.println("");
for (String key : hm.keySet()) {
System.out.printf("%s: %s%n", key, hm.get(key));
}
}
What you are asking for is an algorithm to calculate the cumulative moving average without storing the number of terms you have so far accumulated. I don't think this is possible (for example see https://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average where 'n', the number of terms so far, is required). My suggestion is to use two passes - the first to store the numbers and the second to calculate the averages.
public void calc(List<Pet> pets) {
// First pass
Map<String, List<Double>> firstPass = new HashMap<>();
for (Pet pet : pets) {
String name = pet.getShop();
if (firstPass.containsKey(name)) {
firstPass.get(name).add(pet.getPrice());
} else {
List<Double> prices = new ArrayList<>();
prices.add(pet.getPrice());
firstPass.put(name, prices);
}
}
// Second pass
Map<String, Double> results = new HashMap<>();
for (Map.Entry<String, List<Double>> entry : firstPass.entrySet()) {
Double average = calcAverage(entry.getValue());
results.put(entry.getKey(), average);
// Print results
System.out.printf("%s: %s%n", entry.getKey(), average);
}
}
private double calcAverage(List<Double> values) {
double result = 0;
for (Double value : values) {
result += value;
}
return result / values.size();
}
You can introduce second map for counting or use compound value object in your map to hold both accumulated price and number of pets:
Map<String, PetStatistics> hm = new HashMap<>();
for (Pet i : pets) {
String name = i.getShop();
// If the map already has the pet use the current value, otherwise 0.
PetStatistics stats = hm.get(name);
if (stats == null) {
stats = new PetStatistics(0, 0); // count and price
hm.put(name, stats);
}
stats.addPrice(i.getPrice());
stats.incrementCount();
}
You can use the Collections.frequency to get the number of occurrence and divide the whole sum
for (String key : hm.keySet()) {
int w = Collections.frequency(pets, new Pet(key));
System.out.printf("%s: %s%n", key, hm.get(key)/w);
}
Quick question. Suppose I have a function total (List list) and I have a MyObject class that have a String and an int displayed below and I want to compare two different object Strings in my total method. If they are the same, add the value on both of them. Otherwise, do nothing.
For example data is
{[Johanna, 200], [Jack, 205], [Johanna, 100], [Jack, 50]};
The output should look like
{[Johanna, 300], [Jack, 255]};
public static class MyObject {
int value;
String name;
public MyObject(String nm, int val)
{
name = nm;
value = val;
}
}
public void total(List<MyObject> list) {
List<MyObject> newList = new ArrayList<MyObject>();
Collections.sort(list);
Iterator<Order> ItrL = list.iterator();
int index = 0;
while(ItrL.hasNext())
{
MyObject compare = ItrL.next();
Iterator<MyObject> ItrR = list.listIterator(index);
index++;
while (cmp.name.equals(ItrR.next().name)))
newList.add(new MyObject(cmp.name, cmp.value + ItrR.value));
}
}
You can do summing and comparisons in parallel with no need to sort first using streams.
List<MyObject> newList = Arrays.asList(
new MyObject("Johanna", 200),
new MyObject("Jack", 205),
new MyObject("Johanna", 100),
new MyObject("Jack", 50)
);
Map<String,Integer> map =
newList.stream().parallel()
.collect(Collectors.groupingBy(mo -> mo.name,
Collectors.summingInt(mo -> mo.value)));
System.out.println("map = " + map);
There is no method that is "most optimal" as it depends on how big the data is. The problem seems suitable for map-reduce, but if you have like only 4 elements, then the overhead cost doesn't justify a real map reduce algorithm.
So anyway, here's one alternative that is pre-Java 8 (list doesn't need to be sorted first):
public static Map<String, Integer> total(List<MyObject> list) {
Map<String, Integer> result = new HashMap<String, Integer>();
for (MyObject myObject : list) {
Integer prevValue = result.get(myObject.name);
if (prevValue == null) {
result.put(myObject.name, myObject.value);
} else {
result.put(myObject.name, myObject.value + prevValue);
}
}
return result;
}
You can reduce from n^2 to n*(n/2) by using
for(int i = 0 ...
for(int j = i + 1 ...