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);
}
Related
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.
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
}
I am trying to find n number of values from my Hash Map and put them into different lists to assign to a different font size. I have the maximum and second maximum values in different lists but when I try 3rd or 4th or so on it adds words that have already been found with only the last word being different. There may be more than key (word) with the same value so sorting through and going by index isn't really an option. Can you help.
//searching for biggest value
for (Map.Entry<String, Integer> en : wordToCountMap.entrySet()) {
if (max1 == null || en.getValue().compareTo(max1.getValue()) > 0) {
max1 = en;
largestList.clear();
largestList.add(max1.getKey());
}
}
//searching for second largest value
for (Map.Entry<String, Integer> en : wordToCountMap.entrySet()) {
if (en != max1 && (max2 == null || (en.getValue().compareTo(max2.getValue()) > 0))) {
max2 = en;
secondlargestList.add(max2.getKey());
}
}
//searching for third largest value
for (Map.Entry<String, Integer> en : wordToCountMap.entrySet()) {
if (en != max1 && en != max2 && (max3 == null || (en.getValue().compareTo(max3.getValue()) > 0))) {
max3 = en;
thirdlargestList.add(max3.getKey());
}
}
which outputs
[season][mostly, going, much, seasons, one][mostly, going, much, seasons]
It really takes some time to understand what you're trying to do.
I think you should first get the entries' values as a list - that you can sort easily.
List<Integer> values = wordToCountMap.values().stream.collect(Collectors.toList());
Collections.sort(values);
Collections.reverse(values);
Now the values are sorted high to low, and you can use that list.
Integer highestValue = values.get(0);
List<String> wordsWithHighestValue = wordToCountMap.entrySet().stream()
.filter(e -> e.getValue().equals(highestValue))
.collect(Collectors.toList());
However, I'd probably go with first creating lists of words with the same count.
NavigableMap<Integer,List<String>> countToWordsMap = new TreeMap<>(wordToCountMap.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
Or the somewhat more readable
NavigableMap<Integer,List<String>> countToWordsMap = new TreeMap<>();
for (Map.Entry<String,Integer> entry : wordToCountMap.entrySet()) {
Integer count = entry.getValue();
List<String> list = countToWordsMap.computeIfAbsent(count, new ArrayList<String>());
list.add(entry.getKey());
countToWordsMap.put(count, list);
}
You need to check:
en.getValue() != max2.getVaue();
because you can't compare objects:
en != max2;
If I understand you correctly, you are trying to retrieve a list of words with highest count. Here is some code. Hope it will help:
/**
* This is only convenient facade, using in tests.
*
* #param wordMap
* #param level
* #return
*/
public List<String> getMaxList(Map<String, Integer> wordMap, int level) {
return getMaxList(wordMap.entrySet(), level);
}
/**
* Returns list of words with highest counts, from highest to lowest
*
* #param wordMapEntrySet
* #param level indicates how many words with highest count you want.
* #return
*/
private List<String> getMaxList(Set<Map.Entry<String, Integer>> wordMapEntrySet, int level) {
//Convert set to list, because set is not sortable
List<Map.Entry<String, Integer>> list = new ArrayList<>(wordMapEntrySet);
//Sort the list
list.sort((mapEntry1, mapEntry2) -> {
//Comparing two entries - comparing values (which is actualy a word count)
//Ordering from highest value to lowest
//Changing compare to mapEntry1...compareTo(mapEntry2) will sort the list in reverse order, i.e. from lowest to highest
return mapEntry2.getValue().compareTo(mapEntry1.getValue());
});
//Return a sublist of the sorted list
//Need to return only the keys from the Map.Entry, because the word is the key.
return list.subList(0, level).stream().map(stringIntegerEntry -> stringIntegerEntry.getKey()).collect(Collectors.toList());
}
Here is a unit test for testing:
public class WordCountTest {
WordCount wc = new WordCount();
private Map<String, Integer> createMap() {
Map<String, Integer> wordMap = new HashMap<>();
wordMap.put("AA", 3);
wordMap.put("AB", 30);
wordMap.put("AC", 300);
wordMap.put("AD", 3000);
wordMap.put("AE", 30000);
wordMap.put("AF", 1000);
wordMap.put("AG", 100);
wordMap.put("AH", 10);
wordMap.put("AI", 1);
return wordMap;
}
private Map<String, Integer> createMapSame() {
Map<String, Integer> wordMap = new HashMap<>();
wordMap.put("AA", 3);
wordMap.put("AB", 30);
wordMap.put("AC", 300);
wordMap.put("AD", 3000);
wordMap.put("AE", 30000);
wordMap.put("AF", 30000);
wordMap.put("AG", 3000);
wordMap.put("AH", 300);
wordMap.put("AI", 30);
return wordMap;
}
#Test
public void testMaxWordCount() {
List<String> maxWordList = wc.getMaxList(createMap(),3);
Assert.assertEquals(maxWordList.get(0), "AE");
Assert.assertEquals(maxWordList.get(1), "AD");
Assert.assertEquals(maxWordList.get(2), "AF");
}
#Test
public void testMaxWordCountWithSameCount() {
List<String> maxWordList = wc.getMaxList(createMapSame(), 4);
Assert.assertEquals(maxWordList.get(0), "AE");
Assert.assertEquals(maxWordList.get(1), "AF");
Assert.assertEquals(maxWordList.get(2), "AD");
Assert.assertEquals(maxWordList.get(3), "AG");
}
}
By calling a method ,at my case countInNumbers, is returning results as an array.
System.out.println(countIntInNumbers(Array));
result:
{1=17, 2=10, 3=16, 4=17, 5=13, 6=22, 7=10, 8=15, 9=16, 10=19, 11=11, 12=15, 13=16, 14=13, 15=19, 16=17, 17=13, 18=21, 19=19, 20=15,}
I try to separate the numbers on different table depending their total value.
Example... I want to display the numbers that their total is between 3 and 4 to separate table than the other numbers.
Facing this problem cause the results as you may notice are Map since i am new in Java and I am so confused at this point.
Anyone can suggest from something to start of?
Updated:::
countIntInNumbers method as follows
public static Map<Integer, Integer> countIntInNumbers(int[][] mat) {
Map<Integer, Integer> intOccurences = new HashMap<>();
for (int[] row : mat) {
for (int intInRow : row) {
Integer occurences = intOccurences.get(intInRow);
if (occurences == null) { // first occurrence
intOccurences.put(intInRow, 1);
} else { // increment
intOccurences.put(intInRow, occurences.intValue() + 1);
}
}
}
return intOccurences;
I try to separate the numbers on different table depending their total value. Example... I want to print all numbers that their total is between 3 and 4 to separate table than the other numbers.
We are not sure what you are asking here but if you mean that you want to display the numbers which have a total between 2 numbers then you could do something like:
private void printNumbers(Map<Integer, Integer> intOccurences, int minTotal, int maxTotal){
boolean first = false;
System.out.print("{");
for (Map.Entry<Integer, Integer> entry : intOccurences.entrySet()) {
int total = entry.getValue();
if (total >= minTotal && total <= maxTotal) {
if (first) {
first = false;
} else {
System.out.print(", ");
}
System.out.print(entry.getKey() + "=" + total);
}
}
System.out.print("}");
}
If you are taking about copying the values to a new map then maybe something like:
private Map<Integer, Integer> extractNumbers(Map<Integer, Integer> intOccurences,
int minTotal, int maxTotal) {
Map<Integer, Integer> result = new HashMap<>();
for (Map.Entry<Integer, Integer> entry : intOccurences.entrySet()) {
int total = entry.getValue();
if (total >= minTotal && total <= maxTotal) {
result.put(entry.getKey(), total);
}
}
// not sure if you want to remove the ones from the original map
return result;
}
If you want to compare the value of a map just get it by key. Then since the values of the map are of wrapper Integer you can compare using ==, >=, <= since the Integer equals() method simply compares the int value it wraps with the other Integer's int value. In example:
// Adding some test values to the map
Map<Integer, Integer> map = new HashMap<>();
map.put(1, 5);
map.put(2, 6);
map.put(3, 5);
// Get values by key map.get(key)
// Compare values map.get(key) == map.get(key) or use >=, <=
System.out.println(map.get(1) <= map.get(2)); // true
System.out.println(map.get(1) == map.get(3)); // true
System.out.println(map.get(1) >= map.get(2)); // false
In your countIntInNumbers it seems you are just returning and printing the map by using its toString() method. If I got you right you want to print the keys which values are between 3 and 4. In this case the values are Integer so there will not be any value between 3 and 4 other than the integers themselves.
Okay after seeing your edit, convert your raw matrix to a map and then search for the values you need, and put them into a new map. Something like this:
public static Map<Integer, Integer> countIntInNumbers(int[][] mat) {
Map<Integer, Integer> matConvertedToMap = new HashMap<>();
for(int i=0; i<mat.length; i++)
{
matConvertedToMap.put(mat[i][0], mat[i][1]);
}
Map<Integer, Integer> intOccurences = new HashMap<>();
for (Map.Entry<Integer, Integer> entry : matConvertedToMap.entrySet())
{
if(entry.getValue() == 3 || entry.getValue() == 4)
{
intOccurences.put(entry.getKey(), entry.getValue());
}
}
return intOccurences;
}
Not sure what the comparison really is and what you are expected to return but that should give you a general feeling of how to iterate through the map.
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 ...