I have something like this:
Integer totalIncome = carDealer.getBrands().stream().mapToInt(brand -> brand.getManufacturer().getIncome()).sum();
Integer totalOutcome = carDealer.getBrands().stream().mapToInt(brand -> brand.getManufacturer().getOutcome()).sum();
How could I write that in one stream ? to collect f.e. Pair<Integer, Integer> with totalIncome and totalOutcome ?
EDITED:
Thank you guys for your comments, answers, and involvment. I would have a question about different approach to that problem using streams. What do you think about that:
final IncomeAndOutcome incomeAndOutcome = carDealer.getBrands()
.stream()
.map(Brand::getManufacturer)
.map(IncomeAndOutcome::of)
.reduce(IncomeAndOutcome.ZERO, IncomeAndOutcome::sum);
static class IncomeAndOutcome {
private static final IncomeAndOutcome ZERO = of(0, 0);
#Getter
private final int income;
#Getter
private final int outcome;
public static IncomeAndOutcome of(final int income, final int outcome) {
return new IncomeAndOutcome(income, outcome);
}
public static IncomeAndOutcome of(final Manufacturer manufacturer) {
return new IncomeAndOutcome(manufacturer.getIncome(), manufacturer.getOutcome());
}
IncomeAndOutcome(final int income, final int outcome) {
this.income = income;
this.outcome = outcome;
}
IncomeAndOutcome sum(final IncomeAndOutcome incomeAndOutcome) {
return of(this.income + incomeAndOutcome.getIncome(), this.outcome + incomeAndOutcome.getOutcome());
}
}
Without measuring correctly - everything is guessing. The only argument I do agree with is about readability - this is hardly the case here; but in case you wanted to know this for academic purposes, you can do it:
int[] result = carDealer.getBrands()
.stream()
.map(brand -> new int[]{brand.getManufacturer().getIncome(),
brand.getManufacturer().getOutcome()})
.collect(Collector.of(
() -> new int[2],
(left, right) -> {
left[0] += right[0];
left[1] += right[1];
},
(left, right) -> {
left[0] += right[0];
left[1] += right[1];
return left;
}));
This will give you total of income & outcome. Here 1st argument of reduce() is the identity.
If you are not specifying that reduce() function will give optional value.
Pair<Integer, Integer> result = carDealer.getBrands()
.stream()
.map(brand -> Pair.of(brand.getManufacturer().getIncome(), brand.getManufacturer().getOutcome()))
.reduce(Pair.of(0, 0), (pair1, pair2) -> Pair.of(pair1.getFirst() + pair2.getFirst(), pair1.getSecond() + pair2.getSecond()));
Related
I have a method that sorts a List by different criteria and returns the name (an instance variable) of the one with maximum value. In case more than one instance is having the maximum, all of their names should be concatenated.
Let's say I have Class A as follows.
Class A {
...
String getName(){...}
int getValue1() {...}
int getValue2() {...}
...
int getValueN() {...}
...
}
I have a List<A> listToSort. I would normally sort this list as listToSort.sort(Comparator.comparing(A::getValue1)) or listToSort.sort(Comparator.comparing(A::getValue2)), so on and so forth. Then get the ones sharing the maximum value.
In a method I believe this should be done as:
String getMaxString (Comparator c) {
listToSort.sort(c);
...
}
and send Comparator.comparing(A.getValueX) as parameter to call it with different methods. (X here indicates an arbitrary number for the getValue function).
However, I need to also return other instances sharing the same values
I will need to pass the Class methods to my method and call on instances as:
String getMaxString (Comparator c) {
listToSort.sort(c);
int maxValue = listToSort.get(listToSort.size() - 1).getValueX();
String maxString = listToSort.get(listToSort.size() - 1).getName();
for (int i = listToSort.size() - 2; i >= 0; i--) {
if (listToSort.get(i).getValueX()() == maxValue) {
maxString += ", " + listToSort.get(i).getName();
}
}
return maxString;
}
How would I pass this method to call on instances here? Or do I need to consider another way?
Edit:
I have a list of Courses as List<Course> mylist where a course can be simplified as:
Class Course {
private String name;
private int capacity;
private int students;
...
//bunch of getters.
}
My task is to return Strings for the course(es) with maximum capacity, the course(es) with maximum registered students, the course(es) with most difficulty, the maximum filled percentage, the course(es) with the maximum number of TAs etc...
Edit 2:
As requested in the comment section.
List of
Course a (name "a", capacity 10, students 5)
Course b (name "b", capacity 20, students 5)
Course c (name "c", capacity 30, students 0)
Sorting based on capacity should return "c"
Sorting based on students should return "a b"
You can pass the getter method and create the Comparator in getMaxString:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
public class Foo {
static class AClass {
private final String name;
private final int value1;
private final int value2;
String getName() { return name; }
int getValue1() { return value1; }
int getValue2() { return value2; }
public AClass(String name, int value1, int value2) {
this.name = name;
this.value1 = value1;
this.value2 = value2;
}
}
static String getMaxString(Function<AClass,Integer> f, List<AClass> listToSort) {
listToSort.sort(Comparator.comparing(f));
int maxValue = f.apply(listToSort.get(listToSort.size() - 1));
String maxString = listToSort.get(listToSort.size() - 1).getName();
for (int i = listToSort.size() - 2; i >= 0; i--) {
if (f.apply(listToSort.get(i)) == maxValue) {
maxString += ", " + listToSort.get(i).getName();
}
}
return maxString;
}
public static void main(String[] args) {
List<AClass> list = new ArrayList<>();
list.add(new AClass("a", 1,2));
list.add(new AClass("b", 1,2));
list.add(new AClass("c", 2,1));
list.add(new AClass("d", 2,1));
System.out.println(getMaxString(AClass::getValue1, list));
System.out.println(getMaxString(AClass::getValue2, list));
}
}
As Tim Moore suggested above, it isn't necessary to sort the list (which has cost O(n*log n)), we can traverse it twice:
static String getMaxString2(ToIntFunction<AClass> f, List<AClass> listToSort) {
int maxValue = listToSort.stream().mapToInt(f).max().orElseThrow();
return listToSort.stream()
.filter(a -> maxValue == f.applyAsInt(a))
.map(AClass::getName)
.collect(Collectors.joining(", "));
}
Note that you should test your code with an empty list.
It's useful to look at the type signature for Comparator.comparing, because it sounds like you want to do something similar:
static <T,U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T,? extends U> keyExtractor)
The interesting part is the type of keyExtractor. Roughly speaking, it's a function from the type of the object you're comparing, to the type of the field you want to use for the comparison. In our case, these correspond to the A class and Integer. Because these types are fixed in this example, you can declare a method with a signature like this:
String getMaxString(Function<A, Integer> property)
With the existing algorithm, it can be used this way:
String getMaxString(Function<A, Integer> property) {
listToSort.sort(Comparator.comparing(property));
int maxValue = property.apply(listToSort.get(listToSort.size() - 1));
String maxString = listToSort.get(listToSort.size() - 1).getName();
for (int i = listToSort.size() - 2; i >= 0; i--) {
if (listToSort.get(i).getValueN() == maxValue) {
maxString += ", " + listToSort.get(i).getName();
}
}
return maxString;
}
However, it isn't necessary or efficient to sort the entire list in order to determine the maximum elements, as this can be determined by iterating through the list once:
String getMaxString(Function<A, Integer> property) {
int maxValue = Integer.MIN_VALUE;
StringBuilder maxString = new StringBuilder();
for (A element : listToSort) {
int currentValue = property.apply(element);
if (currentValue > maxValue) {
// there is a new maximum, so start the string again
maxString = new StringBuilder(element.getName());
maxValue = currentValue;
} else if (currentValue == maxValue) {
// equal to the existing maximum, append it to the string
if (maxString.length() > 0) {
maxString.append(", ");
}
maxString.append(element.getName());
}
// otherwise, it's less than the existing maximum and can be ignored
}
return maxString.toString();
}
Either way, you can call it using the same method reference syntax:
getMaxString(A::getValueN)
Time complexity O(n) - only one iteration through the dataset.
Hope it'll help.
If something will be unclear fill free to raise a question.
Main
public class MaxClient {
public static void main(String[] args) {
Comparator<A> comp = Comparator.comparingInt(A::getVal1);
List<A> items = List.of(new A(1, 8), new A(2, 8), new A(5, 8), new A(5, 27), new A(3, 8));
items.stream()
.collect(new GetMax(comp))
.forEach(System.out::println);
}
}
Custom collector GetMax
public class GetMax implements Collector <A, Deque<A>, Deque<A>> {
private final Comparator<A> comp;
public GetMax(Comparator<A> comp) {
this.comp = comp;
}
#Override
public Supplier<Deque<A>> supplier() {
return ArrayDeque::new;
}
#Override
public BiConsumer<Deque<A>, A> accumulator() {
return (stack, next) -> {
if (!stack.isEmpty() && comp.compare(next, stack.peekFirst()) > 0) stack.clear();
if (stack.isEmpty() || comp.compare(next, stack.peekFirst()) == 0) stack.offerLast(next);
};
}
#Override
public BinaryOperator<Deque<A>> combiner() {
return (stack1, stack2) -> {
if (stack1.isEmpty()) return stack2;
if (stack2.isEmpty()) return stack1;
if (comp.compare(stack1.peekFirst(), stack2.peekFirst()) == 0) {
stack1.addAll(stack2);
}
return stack1;
};
}
#Override
public Function<Deque<A>, Deque<A>> finisher() {
return stack -> stack;
}
#Override
public Set<Characteristics> characteristics() {
return Set.of(Characteristics.UNORDERED);
}
}
Class A that I used for testing purposes
public class A {
private int val1;
private int val2;
public A(int val1, int val2) {
this.val1 = val1;
this.val2 = val2;
}
public int getVal1() {
return val1;
}
public int getVal2() {
return val2;
}
#Override
public String toString() {
return "A val1: " + val1 + " val2: " + val2;
}
}
OUTPUT
A val1: 5 val2: 8
A val1: 5 val2: 27
Thanks for posting the information I requested. Here is what I came up with.
Create a list of Course objects
List<Course> list = List.of(
new Course("a", 10, 5),
new Course("b", 20, 5),
new Course("c", 30, 0));
Stream the methods and apply them to the list
List<String> results = Stream.<Function<Course, Integer>>of(
Course::getCapacity,
Course::getStudents)
.map(fnc-> getMaxString(fnc, list))
.toList();
results.forEach(System.out::println);
print the results
c
a b
I wrote a simple method that takes a method reference and list and finds the maximum. It does not do any sorting.
allocate a list to hold the names
set the maximum to the lowest possible
iterate thru the list applying the method.
if the value is greater than the current max replace it and clear the current list of names.
otherwise, if equal, add a new name.
once done, return the formatted string.
static String getMaxString(Function<Course, Integer> fnc,
List<Course> list) {
List<String> result = new ArrayList<>();
int max = Integer.MIN_VALUE;
for (Course obj : list) {
int val = fnc.apply(obj);
if (val >= max) {
if (val > max) {
result.clear();
}
max = val;
result.add(obj.getName());
}
}
return String.join(" ", result);
}
.collect(Collectors.groupingBy(Point::getName, Collectors.summingInt(Point::getCount)));
I have a list of Point objects that I want to group by a certain key (the name field) and sum by the count field of that class. The code above does the job but returns a map of Point objects. However, I want a list of Point objects returned - not a map.
What is the cleanest way to do this with java 8 streams?
Example:
input = [pt("jack", 1), pt("jack", 1), pt("jack", 1)]
result = [pt("jack", 3)]
Thanks
You can use Collectors.toMap() with a merge function as parameter.
If you add a function to sum count fields:
public class Point {
//...
public static Point sum(Point p1, Point p2) {
return new Point(p1.getName(), p1.getCount()+p2.getCount());
}
}
Then you can use it in toMap():
List<Point> list = Collections.nCopies(10, new Point("jack", 1));
Collection<Point> output = list.stream()
.collect(Collectors.toMap(Point::getName, Function.identity(), Point::sum)) // results as Map<String, Point> {"jack", Point("jack",10)}
.values(); // to get the Point instances
System.out.println(output);
Output:
[Point [name=jack, count=10]]
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class Pointers {
private String name;
private int count;
public Pointers(String name, int count) {
this.name = name;
this.count = count;
}
public String getName() {
return name;
}
public int getCount() {
return count;
}
public void incrementCount(int amount) {
count += amount;
}
public boolean equals(Object obj) {
boolean equal = false;
if (obj instanceof Pointers) {
Pointers other = (Pointers) obj;
equal = name.equals(other.getName());
}
return equal;
}
public String toString() {
return name + count;
}
public static void main(String[] args) {
List<Pointers> list = List.of(new Pointers("Jack", 1),
new Pointers("Jack", 1),
new Pointers("Jack", 1));
Supplier<List<Pointers>> supplier = () -> new ArrayList<Pointers>();
BiConsumer<List<Pointers>, Pointers> accumulator = (l, p) -> {
if (l.contains(p)) {
Pointers elem = l.get(l.indexOf(p));
elem.incrementCount(p.getCount());
}
else {
l.add(p);
}
};
BiConsumer<List<Pointers>, List<Pointers>> combiner = (l1, l2) -> {
};
List<Pointers> lst = list.stream()
.collect(supplier, accumulator, combiner);
System.out.println(lst);
}
}
Actually, you were close. You can take the key (name) and value (point sum) and repackage it into a new Point object and return as a list. Note that by re-assiging to list, you destroy the original one which will of course be garbage collected. This approach does not require a modification of your current class.
list = list.stream()
.collect(Collectors.groupingBy(Point::getName,
Collectors.summingInt(Point::getCount)))
.entrySet().stream()
.map(e -> new Point(e.getKey(), e.getValue()))
.collect(Collectors.toList());
I have a object list, which I need to group by 2 different atributes and then sum the values of an attribute, the structure of my object is something like this:
private Long id1;
private Long id2;
private Double amountReserved;
private Double amountRequired;
//... more atributes and getters and setters
So, I then have a list, for example:
List<MyList> list = Arrays.asList(
list(1, A, 50, 200)
list(1, A, 50, 200)
list(1, B, 0, 100)
list(2, A, 10, 15)
list(2, A, 5, 15)
list(3, A, 0, 25));
What I am trying to achieve is a new list with the below structure:
list(1, A, 100, 100)
list(1, B, 0, 100)
list(2, A, 15, 0)
list(3, A, 0, 25)
Elucidating what is the requisite I am trying to achieve:
Group objects by id1 and id2
sum the amountReservedof the grouped object
subtract amountRequired from the summed amountReserved
What I have so far:
This one got me the groupings as I wanted
Map<Long, Map<String, List<MyList>>> map = null;
map = lista.stream().collect(Collectors.groupingBy(PreSeparacaoDto::getCodigoPedido,
Collectors.groupingBy(PreSeparacaoDto::getCodigoProduto)));
This one sums by group id1, but I am struggling to add the second groupingby on it, as I get syntax errors:
lista.stream()
.collect(Collectors.groupingBy(PreSeparacaoDto::getCodigoPedido,
Collectors.summingDouble(PreSeparacaoDto::getProdutoQuantidadeSeparada)))
.forEach((codigoPedido, ProdutoQuantidadeSeparada) -> System.out.println( codigoPedido + ": " + ProdutoQuantidadeSeparada ));
My problem is that I failed to get those together ( as per requisite 2) and was not even close to achieve my requisite 3.
I tried to use reduction, as explained here , but honestly, I was not able to replicate it with a single grouping, the reducing is returning an error informing that my parameters don't meet the reducing parameters. I looked for some other options here on stackoverflow and other websites, but without success.
Can someone help me out and poiting where I am failing to combine the reduction with my group, or if that is the correct path I should be following.
I think an easy way is to use Collectors.grouping : you tell it how to group and what to collect.
Here's an example, computing only the sum of AmountReserved :
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class GroupedSums {
static class MyList {
Long id1;
char id2;
Double amountReserved;
Double amountRequired;
public Long getId1() {
return id1;
}
public char getId2() {
return id2;
}
public Double getAmountReserved() {
return amountReserved;
}
public Double getAmountRequired() {
return amountRequired;
}
public MyList(Long id1, char id2, Double amountReserved, Double amountRequired) {
super();
this.id1 = id1;
this.id2 = id2;
this.amountReserved = amountReserved;
this.amountRequired = amountRequired;
}
Key key() {
return new Key(id1, id2);
}
}
private static MyList list(Long id1, char id2, Double amountReserved, Double amountRequired) {
return new MyList(id1, id2, amountReserved, amountRequired);
}
public GroupedSums() {
}
private static class Key {
Long id1;
char id2;
public Long getId1() {
return id1;
}
public char getId2() {
return id2;
}
public Key(Long id1, char id2) {
super();
this.id1 = id1;
this.id2 = id2;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id1 == null) ? 0 : id1.hashCode());
result = prime * result + id2;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Key other = (Key) obj;
if (id1 == null) {
if (other.id1 != null)
return false;
} else if (!id1.equals(other.id1))
return false;
if (id2 != other.id2)
return false;
return true;
}
#Override
public String toString() {
return "[id1=" + id1 + ", id2=" + id2 + "]";
}
}
public static void main(String[] args) {
List<MyList> list = Arrays.asList(
list(1L, 'A', 50d, 200d),
list(1L, 'A', 50d, 200d),
list(1L, 'B', 0d, 100d),
list(2L, 'A', 10d, 15d),
list(2L, 'A', 5d, 15d),
list(3L, 'A', 0d, 25d));
list.stream().collect(Collectors.groupingBy(MyList::key, Collectors.summingDouble(MyList::getAmountReserved)))
.forEach((k,v)->System.out.println("" + k + " :" + v));
}
}
HTH!
You might just be looking for simply Collectors.toMap as :
List<MyList> output = new ArrayList<>(lista.stream()
.collect(Collectors.toMap(a -> a.getId1() + "-" + a.getId2(), a -> a, (myList1, myList2) -> {
myList1.amountReserved = myList1.amountReserved + myList2.amountReserved;
myList1.amountRequired = myList1.amountRequired - myList1.amountReserved;
return myList1;
})).values());
You can stream over the input list twice.
First time, you group by id1, id2 and compute the sum of amount reserved. Second time, you can stream the list again, group it (by id1 and id2) by making use of the above result to find the difference.
Map<Long, Map<Long, Double>> amountReservedGroup = list.stream()
.collect(Collectors.groupingBy(MyList::getId1, Collectors.groupingBy(MyList::getId2,
Collectors.summingDouble(MyList::getAmountReserved))));
Map<Long, Map<Long, List<MyList>>> finalResult = list.stream()
.collect(Collectors.groupingBy(MyList::getId1, Collectors.groupingBy(MyList::getId2,
Collectors.mapping(o -> new MyList(o.getId1(), o.getId2(),
amountReservedGroup.get(o.getId1()).get(o.getId2()),
o.getAmountRequired() - amountReservedGroup.get(o.getId1()).get(o.getId2())),
Collectors.toList()))));
Note:
This does not handle the case when the result of the subtraction is negative!!
As pointed out by nullpointer# in the comments, will the value of amountRequired be the same for a given id1 and id2?
you can do order by id1 and then order id2 (to make sure elements of the same list and sublist are after each other) and then you do nested foreach (before you iterate the sublist, you init result_reserved_amount to 0 and result_required_amount to the initial value)
then you do if same ids (if id1= previous_id1 and id2 = previous_id2) do result_reserved_amount+= current_reserved_amount and result_required_amount -= current_reserved_amount, otherwise update previous_id1, previous_id2, result_reserved_amount, result_required_amount
I have a method named calculate and it takes too long to complete. So I decided to send my info list objects to this method partially. How can I iterate over every n elements?
public static void main(String [] args){
Map<String, Long> info....; //my info Map
//I want to call method like
for(int i = 0; i<info.size(); i+=5)
calculate(info.submap(i,i+5));
}
public static boolean calculate(Map<String, Long> info){
//Some calculations
}
You can use following code
class SomeClass {
private final int BUFFER_SIZE = 5;
public static void main(String[] args) {
Map<String, Long> info = new HashMap<>();
LongStream.range(0, 30).boxed().forEach(i -> info.put("key" + i, i)); // for test
IntStream.range(0, info.size() / BUFFER_SIZE)
.boxed()
.parallel()
.map(i -> Arrays.copyOfRange(info.keySet().toArray(), BUFFER_SIZE * i, BUFFER_SIZE * (i + 1)))
.map(Arrays::asList)
.map(keys -> info.entrySet().stream()
.filter(x -> keys.contains(x.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
.forEach(SomeClass::calculate);
}
public static boolean calculate(Map<String, Long> info) {
System.out.println("calculation for " + info.toString());
return true;
}
}
It sounds like what you want to do is to implement a sort of batch processing for the data represented by your Map<String, Long> info instance. You can then create a generator for these batches as a Stream: This is in a way the inverse of the Stream.flatMap(...) family of methods, but, ironically, there doesn't seem to be any idiomatic functional way of doing this and so you may have to create the batches yourself in an imperative manner — for example:
private static <T> Stream<Stream<T>> createBatchStreams(final Iterator<T> iter, final int maxBatchSize) {
final Stream.Builder<Stream<T>> resultBuilder = Stream.builder();
{
// NOTE: This logic could also be encapsulated in a Collector class
// in order to make it less imperative style
Stream.Builder<T> currentBatchBuilder = Stream.builder();
int currentBatchSize = 0;
while (iter.hasNext()) {
final T next = iter.next();
if (currentBatchSize == maxBatchSize) {
resultBuilder.add(currentBatchBuilder.build());
// Start building a new batch
currentBatchBuilder = Stream.builder();
currentBatchSize = 0;
}
currentBatchBuilder.add(next);
currentBatchSize++;
}
// Check if there is a non-empty Stream to add (e.g. if there was a
// final batch which was smaller than the others)
if (currentBatchSize > 0) {
resultBuilder.add(currentBatchBuilder.build());
}
}
return resultBuilder.build();
}
Using this method, you can then create a generator of batches of your map data, which can then be fed to your calculate(...) function (albeit with a slightly different signature):
public static void main(final String[] args) {
final Map<String, Long> info = LongStream.range(0, 10).boxed()
.collect(Collectors.toMap(value -> "key" + value, Function.identity())); // Test data
final Stream<Stream<Entry<String, Long>>> batches = createBatchStreams(info.entrySet().iterator(), 5);
batches.forEach(batch -> {
calculate(batch);
// Do some other stuff after processing each batch
});
}
private static boolean calculate(final Stream<Entry<String, Long>> info) {
// Some calculations
}
I have a hashMap that contains key and value as 'String'. I am getting these values from a web page in my selenium automation script.
my hashmap has following
<Italy, 3.3 millions>
<Venezuela, 30.69 millions>
<Japan, 127.1 millions>
How can I convert all the string alphanumeric values to integers so that I can apply sorting on the hashmap?
I have to display the word 'millions'.
As far as I understand from your question what you need to do is to be able to sort those values, so what you need is a Comparator.
Here is the Comparator that could do the trick:
Comparator<String> comparator = new Comparator<String>() {
#Override
public int compare(final String value1, final String value2) {
return Double.compare(
Double.parseDouble(value1.substring(0, value1.length() - 9)),
Double.parseDouble(value2.substring(0, value2.length() - 9))
);
}
};
System.out.println(comparator.compare("3.3 millions", "30.69 millions"));
System.out.println(comparator.compare("30.69 millions", "30.69 millions"));
System.out.println(comparator.compare("127.1 millions", "30.69 millions"));
Output:
-1
0
1
If you have only millions you can try something like this
String str = "3.3 Millions";
String[] splitted = str.split(" ");
double i = Double.valueOf(splitted[0])*1000000;
System.out.println(i);
or do your calculation depending on the substring
not sure if this is what you are looking for.. If i get it right you have to change your map from
<String, String> to <String, Double>.
See my example below :
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
public class NewClass9 {
public static void main(String[] args) throws ParseException{
Map<String,String> oldMap = new HashMap<>();
oldMap.put("Italy", "3.3 millions");
oldMap.put("Venezuela", "30.69 millions");
oldMap.put("Japan", "127.1 millions");
Map<String,Double> newMap = new HashMap<>();
for(String key : oldMap.keySet()){
newMap.put(key, convert(oldMap.get(key)));
}
for(String key : newMap.keySet()){
System.out.printf("%.0f millions\n" ,newMap.get(key));
}
}
private static double convert(String str) {
String[] splitted = str.split(" ");
return Double.valueOf(splitted[0])*1000000;
}
}
A bit overkill but this should be extensible.
NB: I've only covered the multiplier lookup.
/**
* Possible units and their multipliers.
*/
enum Unit {
Unit(1),
Hundred(100),
Thousand(1000),
Million(1000000),
Billion(1000000000),
Squillion(Integer.MAX_VALUE);
private final int multiplier;
Unit(int multiplier) {
this.multiplier = multiplier;
}
}
/**
* Comparator that matches caseless and plurals
*
* NB: Not certain if this is consistent.
*/
private static final Comparator<String> COMPARECASELESSANDPLURALS
= (String o1, String o2) -> {
// Allow case difference AND plurals.
o1 = o1.toLowerCase();
o2 = o2.toLowerCase();
int diff = o1.compareTo(o2);
if (diff != 0) {
// One character different in length?
if (Math.abs(o1.length() - o2.length()) == 1) {
// Which may be plural?
if (o1.length() > o2.length()) {
// o1 might be plural.
if (o1.endsWith("s")) {
diff = o1.substring(0, o1.length() - 1).compareTo(o2);
}
} else if (o2.endsWith("s")) {
// o2 might be plural.
diff = -o2.substring(0, o2.length() - 1).compareTo(o1);
}
}
}
return diff;
};
// Build my lookup.
static final Map<String, Integer> MULTIPLIERS
= Arrays.stream(Unit.values())
// Collect into a Map
.collect(Collectors.toMap(
// From name of the enum.
u -> u.name(),
// To its multiplier.
u -> u.multiplier,
// Runtime exception in case of duplicates.
(k, v) -> {
throw new RuntimeException(String.format("Duplicate key %s", k));
},
// Use a TreeMap that ignores case and plural.
() -> new TreeMap(COMPARECASELESSANDPLURALS)));
// Gives the multiplier for a word.
public Optional<Integer> getMultiplier(String word) {
return Optional.ofNullable(MULTIPLIERS.get(word));
}
public void test() {
String[] tests = {"Million", "Millions", "Thousand", "Aardvark", "billion", "billions", "squillion"};
for (String s : tests) {
System.out.println("multiplier(" + s + ") = " + getMultiplier(s).orElse(1));
}
}