Collectors.groupingBy functionality and summing a field - java

.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());

Related

How to choose pass instance method to use to a method in Java

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);
}

Java stream - find most frequent element based on a specific field

I have a list of Person objects, I would like to find the most frequent name in the list, and the frequency, only using java streams. (When there is a tie, return any result)
Currently, my solution uses groupingBy and counting, then again finding the max element in the resulting map.
The current solution makes 2 passes on the input (list/map).
Is it possible to make this a bit more efficient and readable?
Person p1 = Person.builder().id("p1").name("Alice").age(1).build();
Person p2 = Person.builder().id("p2").name("Bob").age(2).build();
Person p3 = Person.builder().id("p3").name("Charlie").age(3).build();
Person p4 = Person.builder().id("p4").name("Alice").age(4).build();
List<Person> people = ImmutableList.of(p1, p2, p3, p4);
Map.Entry<String, Long> mostCommonName = people
.stream()
.collect(collectingAndThen(groupingBy(Person::getName, counting()),
map -> map.entrySet().stream().max(Map.Entry.comparingByValue()).orElse(null)
));
System.out.println(mostCommonName); // Alice=2
If you are insisting on only using streams then your best bet is likely to have a custom collector that includes the info required to aggregate in a single pass:
class MaxNameFinder implements Collector<Person, ?, String> {
public class Accumulator {
private final Map<String,Integer> nameFrequency = new HashMap<>();
private int modeFrequency = 0;
private String modeName = null;
public String getModeName() {
return modeName;
}
public void accept(Person person) {
currentFrequency = frequency.merge(p.getName(), 1, Integer::sum);
if (currentFrequency > modeFrequency) {
modeName = person.getName();
modeFrequency = currentFrequency;
}
}
public Accumulator combine(Accumulator other) {
other.frequency.forEach((n, f) -> this.frequency.merge(n, f, Integer::sum));
if (this.frequency.get(other.modeName) > frequency.get(this.modeName))
modeName = other.modeName;
modeFrequency = frequency.get(modeName);
return this;
};
}
public BiConsumer<Accumulator,​Person> accumulator() {
return Accumulator::accept;
}
public Set<Collector.Characteristics> characteristics() {
return Set.of(Collector.Characteristics.CONCURRENT);
}
public BinaryOperator<Accumulator> combiner() {
return Accumulator::combine;
}
public Function<Accumulator,String> finisher() {
return Accumulator::getModeName;
}
public Supplier<Accumulator> supplier() {
return Accumulator::new;
}
}
Usage would be:
people.stream().collect(new MaxNameFinder())
which would return a string representing the most common name.
It may be possible to squeeze two passes into one using loops and Map::merge function returning the calculated frequency value immediately:
String mostCommonName = null;
int maxFreq = 0;
Map<String, Integer> freq = new HashMap<>();
for (Person p : people) {
if (freq.merge(p.getName(), 1, Integer::sum) > maxFreq) {
maxFreq = freq.get(p.getName());
mostCommonName = p.getName();
}
}
System.out.printf("Most common name '%s' occurred %d times.%n", mostCommonName, maxFreq);

How can I add two IntStreams element by element?

Example;
IntStream a = create(3, 1); // => [0,0,1]
IntStream b = create(5, 2); // => [0,0,0,0,2]
The first stream gives an infinite stream of [0,0,1,0,0,1...] and the second an infinite stream of [0,0,0,0,2,0,0,0,0,2,...].
The result stream is ri = ai + bi meaning that I just want to take the sum of the elements at the same position from each stream.
Is this possible in Java ?
You can use Guava's Streams.zip() helper:
IntStream sum(IntStream a, IntStream b) {
return Streams.zip(a.boxed(), b.boxed(), Integer::sum)
.map(Integer::intValue);
}
You can define your own Spliterator to create a stream from it later.
import java.util.Comparator;
import java.util.Spliterators;
import java.util.function.IntConsumer;
public class SumSpliterator extends Spliterators.AbstractIntSpliterator {
private OfInt aSplit;
private OfInt bSplit;
SumSpliterator(OfInt a, OfInt b) {
super(Math.min(a.estimateSize(), b.estimateSize()), Spliterator.ORDERED);
aSplit = a;
bSplit = b;
}
#Override
public boolean tryAdvance(IntConsumer action) {
SummingConsumer consumer = new SummingConsumer();
if (aSplit.tryAdvance(consumer) && bSplit.tryAdvance(consumer)) {
action.accept(consumer.result);
return true;
}
return false;
}
static class SummingConsumer implements IntConsumer {
int result;
#Override
public void accept(int value) {
result += value;
}
}
}
Then create a stream and check the results
IntStream a = //create stream a
IntStream b = //create stream b
SumSpliterator spliterator = new SumSpliterator(a.spliterator(), b.spliterator());
Stream<Integer> stream = StreamSupport.stream(spliterator, false);
stream.limit(20).forEach(System.out::println);

Sort ArrayList items by name

I am trying to rearrange an ArrayList based on the name of the items to be on specific index.
My list currently is this:
"SL"
"TA"
"VP"
"SP"
"PR"
and i want to rearrange them to:
"SL"
"SP"
"TA"
"PR"
"VP"
but based on the name and not in the index.
I have tried this:
for (int i=0; i< list.size(); i++){
if (list.get(i).getCategoryName().equals("SL")){
orderedDummyJSONModelList.add(list.get(i));
}
}
for (int i=0; i< list.size(); i++){
if (list.get(i).getCategoryName().equals("SP")){
orderedDummyJSONModelList.add(list.get(i));
}
}
for (int i=0; i< list.size(); i++){
if (list.get(i).getCategoryName().equals("TA")){
orderedDummyJSONModelList.add(list.get(i));
}
}
for (int i=0; i< list.size(); i++){
if (list.get(i).getCategoryName().equals("PR")){
orderedDummyJSONModelList.add(list.get(i));
}
}
for (int i=0; i< list.size(); i++){
if (list.get(i).getCategoryName().equals("VP")){
orderedDummyJSONModelList.add(list.get(i));
}
}
and it works fine, but i want to know if there is a more efficient way to do in 1 for loop or maybe a function. I do not wish to do it like this:
orderedDummyJSONModelList.add(list.get(0));
orderedDummyJSONModelList.add(list.get(3));
orderedDummyJSONModelList.add(list.get(1));
orderedDummyJSONModelList.add(list.get(4));
orderedDummyJSONModelList.add(list.get(2));
Which also works. Any ideas?
You can use Collection.Sort method as Collection.Sort(list) since list is a List<String> you will be fine. But if you want to implement a new comparator:
Collections.sort(list, new NameComparator());
class NameComparator implements Comparator<String> { //You can use classes
#Override
public int compare(String a, String b) { //You can use classes
return a.compareTo(b);
}
}
EDIT:
You can define a class comparator for your needs:
class ClassComparator implements Comparator<YourClass> { //You can use classes
#Override
public int compare(YourClass a, YourClass b) { //You can use classes
return a.name.compareTo(b.name);
}
}
The key thing here is: you need to get clear on your requirements.
In other words: of course one can shuffle around objects stored within a list. But: probably you want to do that programmatically.
In other words: the correct approach is to use the built-in Collection sorting mechanisms, but with providing a custom Comparator.
Meaning: you better find an algorithm that defines how to come from
"SL"
"TA"
"VP"
"SP"
"PR"
to
"SL"
"SP"
"TA"
"PR"
"VP"
That algorithm should go into your comparator implementation!
The point is: you have some List<X> in the first place. And X objects provide some sort of method to retrieve those strings you are showing here. Thus you have to create a Comparator<X> that works on X values; and uses some mean to get to those string values; and based on that you decide if X1 is <, = or > than some X2 object!
here´s an answer just specific for your problem working just for the given output. If the List contains anything else this might break your ordering, as there is no rule given on how to order it and the PR just randomly appears in the end.
public static void main(String[] args) {
List<String> justSomeNoRuleOrderingWithARandomPRInside = new ArrayList<String>();
justSomeNoRuleOrderingWithARandomPRInside.add("SL");
justSomeNoRuleOrderingWithARandomPRInside.add("TA");
justSomeNoRuleOrderingWithARandomPRInside.add("VP");
justSomeNoRuleOrderingWithARandomPRInside.add("SP");
justSomeNoRuleOrderingWithARandomPRInside.add("PR");
java.util.Collections.sort(justSomeNoRuleOrderingWithARandomPRInside, new NameComparator());
for(String s : justSomeNoRuleOrderingWithARandomPRInside) {
System.out.println(s);
}
}
static class NameComparator implements Comparator<String> { //You can use classes
#Override
public int compare(String a, String b) { //You can use classes
// Lets just add a T in front to make the VP appear at the end
// after TA, because why not
if (a.equals("PR")) {
a = "T"+a;
} else if(b.equals("PR")) {
b = "T"+b;
}
return a.compareTo(b);
}
}
O/P
SL
SP
TA
PR
VP
But honestly, this solution is crap, and without any clear rule on how to order these this will be doomed to fail as soon as you change anything as #GhostCat tried to explain.
How about this
// define the order
List<String> ORDER = Arrays.asList("SL", "SP", "TA", "PR", "VP");
List<MyObject> list = ...
list.sort((a, b) -> {
// lamba syntax for a Comparator<MyObject>
return Integer.compare(ORDER.indexOf(a.getString()), ORDER.indexOf(b.getString());
});
Note that this will put any strings that aren't defined in the ORDER list at the start of the sorted list. This may or may not be acceptable - it may be worth checking that only valid strings (i.e. members of ORDER) appear as the result of MyObject.getString().
Use a hashmap to store the weight of all strings (Higher the value of the hashmap means the later this string should come in the final list).
Using a Hashmap, so you can expand it later for other strings as well. It'll be easier to enhance in future.
Finally, Use a custom Comparator to do it.
Required Setup:
List<String> listOfStrings = Arrays.asList("SL", "TA", "VP", "SP", "PR");
HashMap<String, Integer> sortOrder = new HashMap<>();
sortOrder.put("SL", 0);
sortOrder.put("TA", 1);
sortOrder.put("VP", 2);
sortOrder.put("SP", 3);
sortOrder.put("PR", 4);
Streams:
List<String> sortedList = listOfStrings.stream().sorted((a, b) -> {
return Integer.compare(sortOrder.get(a), sortOrder.get(b));
}).collect(Collectors.toList());
System.out.println(sortedList);
Non-Stream:
Collections.sort(listOfStrings, (a, b) -> {
return Integer.compare(sortOrder.get(a), sortOrder.get(b));
});
OR
listOfStrings.sort((a, b) -> {
return Integer.compare(sortOrder.get(a), sortOrder.get(b));
});
System.out.println(listOfStrings);
Output:
[SL, TA, VP, SP, PR]
You can build an index map using a LinkedHashMap. This will be used to lookup the order which to sort using the category names of your items.
ItemSorting
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ItemSorting {
public static void main(String[] args) {
List<Item> list = new ArrayList<Item>();
IndexMap indexMap = new IndexMap("SL", "SP", "TA", "PR", "VP");
ItemComparator itemComparator = new ItemComparator(indexMap);
list.add(new Item("SL"));
list.add(new Item("TA"));
list.add(new Item("VP"));
list.add(new Item("SP"));
list.add(new Item("PR"));
Collections.sort(list, itemComparator);
for (Item item : list) {
System.out.println(item);
}
}
}
ItemComparator
import java.util.Comparator;
public class ItemComparator implements Comparator<Item> {
private IndexMap indexMap;
public IndexMap getIndexMap() {
return indexMap;
}
public void setIndexMap(IndexMap indexMap) {
this.indexMap = indexMap;
}
public ItemComparator(IndexMap indexMap) {
this.indexMap = indexMap;
}
#Override
public int compare(Item itemA, Item itemB) {
if (itemB == null) return -1;
if (itemA == null) return 1;
if (itemA.equals(itemB)) return 0;
Integer valA = indexMap.get(itemA.getCategoryName());
Integer valB = indexMap.get(itemB.getCategoryName());
if (valB == null) return -1;
if (valA == null) return 1;
return valA.compareTo(valB);
}
}
IndexMap
import java.util.LinkedHashMap;
public class IndexMap extends LinkedHashMap<String, Integer> {
private static final long serialVersionUID = 7891095847767899453L;
public IndexMap(String... indicies) {
super();
if (indicies != null) {
for (int i = 0; i < indicies.length; i++) {
this.put(indicies[i], new Integer(i));
}
}
}
}
Item
public class Item {
private String categoryName;
public Item(String categoryName) {
super();
this.categoryName = categoryName;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((categoryName == null) ? 0 : categoryName.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Item other = (Item) obj;
if (categoryName == null) {
if (other.categoryName != null) return false;
} else if (!categoryName.equals(other.categoryName)) return false;
return true;
}
#Override
public String toString() {
return String.format("Item { \"categoryName\" : \"%s\" }", categoryName);
}
}
Result
Item { "categoryName" : "SL" }
Item { "categoryName" : "SP" }
Item { "categoryName" : "TA" }
Item { "categoryName" : "PR" }
Item { "categoryName" : "VP" }
You coud define a helper method like this one:
public static int get(String name) {
switch (name) {
case "SL":
return 1;
case "SP":
return 2;
case "TA":
return 3;
case "PR":
return 4;
case "VP":
return 5;
default:
return 6;
}
}
and write in your main method something like:
ArrayList<String> al = new ArrayList<>();
al.add("SL");
al.add("TA");
al.add("VP");
al.add("SP");
al.add("PR");
Collections.sort(al, (o1, o2) -> return get(o1) - get(o2); );
al.forEach((s) -> System.out.println(s));
You can create a Map that maintains the position. When you iterate through the unordered list just get the position of that string value and insert into new array(not arraylist), then later if required you can convert that array to ArrayList.
Example code:
Map<String,Integer> map = new HashMap<>(); //you can may be loop through and make this map
map.put("SL", 0);
map.put("SP", 1);
map.put("TA",2);
map.put("PR",3);
map.put("VP",3);
List<String> list1 // your unordered list with values in random order
String[] newArr = new String[list1.size()];
for(String strName: list1){
int position = map.get(strName);
arr[position] = strName;
}
//newArr has ordered result.

Converting alpha numeric string to integer?

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));
}
}

Categories

Resources