Sort by multiple columns, Java 8 - java

Consider the following table:
Name Code Number
Mike x6 5.0
Mike b4 3.0
Mike y2 1.0
Tom y2 4.5
Tom x6 4.5
Tom b4 1.0
Susi x6 4.0
Susi y2 3.0
Susi b4 2.0
I have three columns, it should be sorted first of all by the column "Name" and then by the column "Number". I wanted to do this with Dictionary (use String array as value and Double as key) and then sort by value, but I miss the sort by the name.
Map<Double, String[]> map = new HashMap<Double, String[]>();
map.put(5.0, {"Mike", "x6"});
System.out.println(map.get(5.0));
I don't know what is the best way to store my data. I would like also to know the solution in Java 8.

First of all, you should make each line of your table an object:
public class MyData {
private String name;
private String code;
private Double number;
public MyData(String name, String code, Double number) {
this.name = name;
this.code = code;
this.number = number;
}
public String getName() {
return name;
}
public String getCode() {
return code;
}
public Double getNumber() {
return number;
}
}
Using Map<Double, String[]> does not represent what you are trying to achieve. A Map is used to create a link between an unique key an a value. Does it make sense for each number to be associated to a name and a code?
Once you have this object, it is much easier to sort it according to its properties:
List<MyData> list = new ArrayList<>();
list.add(new MyData("Mike", "x6", 5.0));
list.add(new MyData("Mike", "b4 ", 3.0));
list.add(new MyData("Mike", "y2", 1.0));
list.add(new MyData("Tom", "y2", 4.5));
List<MyData> sortedList = list.stream()
.sorted(Comparator.comparing(MyData::getName).thenComparing(MyData::getNumber))
.collect(Collectors.toList());

I think a Map is the wrong data structure for your case, as Maps explicitly DO NOT DEFINE an order based on the values.
But you may help yourself with streams. Something like:
map.entrySet().stream().sorted((e1, e2) -> e1.getValue()[0].compareTo(e2.getValue()[0])).map(e -> e.getKey()).toArray(l -> new Integer[l])
this will give you an array of keys, sorted by the first integer in the value array. the full value you may then look up in the original map.

Related

How do you create a method in objects that finds the highest number using hashmap?

I'm new with hashmap and I'm trying to find the most populous continent using hashmap and I don't know where to go from here.
These are my fields and I want to put the continent in the key section and population in the value section
private String name;
private int population;
private double area;
private String continent;
This is my attempt for creating that method but it's incomplete.
public void findMostPopulousContinent() {
/* Hashmap<Key, Value> */
HashMap<String, Integer> dummy = new HashMap<String, Integer>();
for (int i = 0; i < size; i++) {
if (dummy.containsKey(catalogue[i].getContinent())) {
Integer pop = dummy.get(catalogue[i].getContinent());
pop = pop + catalogue[i].getPopulation();
}
else {
dummy.put(catalogue[i].getContinent(), catalogue[i].getPopulation());
}
}
}
What I wanted to happen is to put my instances in the hashmap, and if my instances have the same continent then add their population and then compare that with others and then print the continent with the highest population like
North America, 100000000
You need to aggregate the population, which you can use Map's merge() method for.
But you don't need all that code:
Given:
record Country(String continent, Integer population) {}
Country[] catalog;
then:
Map<String, Integer> continentPopulations = Arrays.stream(catalog)
.collect(groupingBy(Country::continent, summingInt(Country::population)));
The secret sauce is using the groupingBy(classifier, downstream) collector:
groupingBy(Country::continent, summingInt(Country::population))
To get the most populous continent, you can skip the reference to the map and use a stream to find the max based on population for you:
String mostPopulousContinentName = Arrays.stream(catalog)
.collect(groupingBy(Country::continent, summingInt(Country::population)))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.get().getKey();

How to multiply key * value in Map in Java?

I have this class:
class Product {
public double price;
public Product(double price) {
this.price = price;
}
}
And a Map:
Map<Product, Integer> products = new HashMap<>();
That contains several products added like so:
products.put(new Product(2.99), 2);
products.put(new Product(1.99), 4);
And I want to calculate the sum of all products multiple the values using streams? I tried:
double total = products.entrySet().stream().mapToDouble((k, v) -> k.getKey().price * v.getValue()).sum();
But it doesn't compile, I get “Cannot resolve method getValue()”.
I expect:
(2.99 * 2) + (1.99 * 4) = 5.98 + 7.96 = 13.94
The stream of entries needs single parameter lambda for each entry, not (k,v):
double total = products.entrySet().stream().mapToDouble(e -> e.getKey().price * e.getValue()).sum();
You can avoid the explicit creation of a doubleStream with something like:
double total = products.entrySet()
.stream()
.collect(Collectors.summingDouble(e -> e.getKey().price * e.getValue()));
Not directly related to your question, but I wouldn't use a map for what you are doing. Instead create a new class
public class ProductAmount {
private Product product;
private int amount;
public ProductAmount(Product product, int amount) {
this.product = product;
this.amount = amount;
}
public double getCombinedPrice() {
return product.price * amount;
}
}
Then you can use a List instead of a Map.
List<ProductAmount> products = Arrays.asList(
new ProductAmount(new Product(2.99), 2),
new ProductAmount (new Product(1.99), 4));
products.stream().mapToDouble(ProductAmount::getCombinedPrice).sum();
You can also do it like so.
double sum = 0;
for(Entry<Product, Integer> e : products.entrySet()) {
sum += e.getKey().price * e.getValue();
}
System.out.println(sum);
prints
13.940000000000001
But you have a fundamental flaw in your class. You don't override equals or hashCode. So you're are you using the object reference as the key. Try doing the following:
System.out.println(products.get(new Product(1.99));
It will print null since there is no entry for that reference (it's a different object than the one used to store the value 4).
And finally you should make certain your keys are immutable. Otherwise, circumstances could result in the same error.
Check out why do I need to override hashcode and equals.
And since it was mentioned in the comments, also check out what data type to use for money in java.

Java: How do I Convert List<Integers> to corresponding Strings

I have a List of Integers collected from a JSON. I want to change those integers for their corresponding string value and print those strings.
For example: if my list of integers contains 4, 11, 67 and I know 4 means English, 11 means Bengali, 67 means Hindi.
So, I want to convert them to their corresponding string and print them as English, Bengali, Hindi.
Here is a similar question's answer, but I want to change the integer value to its corresponding string value instead of just print those integers: https://stackoverflow.com/a/13198957/9920173
Every help will be appreciated. Thank you.
An easier way to do this would be to use a HashMap and store the integer as the key and the String as the value. Something like this:
HashMap<Integer, String> languages = new HashMap<>(); {{
languages.put(4, "English");
languages.put(11, "Bengali");
//Etc
}}
Then when you want to print out the corresponding Strings you can do:
System.out.println(languages.get(4));
//Prints "English"
EDIT:
You can do something along these lines to print every corresponding String.
for(int num: yourList) {
System.out.println(languages.get(num));
}
Create an enum type to represent all the languages where an enum type's ordinal number is the integer value.
enum Language{
ASSAMESE,
...
ENGLISH//4th item
...
BENGALI//11th item
...
HINDI//67th item
...}
To get the language by the integer value say n:
Language.values()[n-1];
If you don't have languages for all integer values (say from 1, 2, 3,...n) you can use the below code:
public enum Language {
ASSAMESE(1),
ENGLISH(4),
HINDI(67);
private int langId;
Language(int langId) {
this.langId = langId;
}
public int getLangId() {
return langId;
}
public void setLangId(int langId) {
this.langId = langId;
}
public static Language getLanguageById(int langId) {
for(Language lang : values()) {
if(lang.getLangId() == langId) {
return lang;
}
}
return null;
}
}
You can have number to language mapping in a HashMap. Then it can be implemented using Java 8 like so,
List<Integer> numbers = Arrays.asList(4, 11, 67);
Map<Integer, String> languageMap = new HashMap<>();
languageMap.put(4, "English");
languageMap.put(11, "Bengali");
languageMap.put(67, "Hindi");
String langStr = numbers.stream().map(languageMap::get).collect(Collectors.joining(", "));
A possible imperative approach to solving the same problem would be like this.
StringJoiner joiner = new StringJoiner(", ");
for (Integer number : numbers) {
joiner.add(languageMap.get(number));
}
System.out.println(joiner.toString());
Still the latter solution necessitates the use of Java 8 since StringJoiner is available in Java 8.

First N values of a Map<K, V> sorted by value

I have a list of Strings. I want to evaluate each string based on a function that returns a double. Then I want the first 5 strings, based on their calculated values. If there are fewer than 5, I want all of them (in order). Let's say the strings are chemical compounds and the function computes the mass. The function is computationally expensive; I need to evaluate it once per string. (I'm just making up data here, though.)
H2O => 18.5
C12H11O22 => 109.1
HeNe => 32.0
H2SO4 => 54.37
HCl => 19.11
4FeO3 => 82.39
Xe6 => 281.9
The program should return the first five strings arranged in order by their respective values. For this sample data: H20, HCl, HeNe, H2SO4, 4FeO3. Actually, I don't really care about the order; I just need the five lowest in any order.
I thought about how I'd do this in Perl. It's just a few lines:
foreach $s (#str) {
$strmap{$s} = f($s);
}
#sorted = sort { $strmap{$a} <=> $strmap{$b} } keys %strmap;
return #sorted[0, 4]
But I need to do it in Java. And it's driving me crazy.
First I tried populating a HashMap<String, Double>, then using Collections.sort with a custom comparator, just like the Perl version. But scoping on the Comparator prevented it from referring to the HashMap to look up the values.
Then I tried a TreeMap<String, Double>, but it only sorts by key and no amount of coercing could get it to order the entries by value.
So I tried a TreeMap<Double, String>. It will discard entries with the same Double. However, the likelihood of having Strings that map to the same Double is low, so I pressed forward. Adding the entries to the TreeMap is no problem, but I ran into issues trying to extract the values from it.
TreeMap supplies a method called subMap, but its parameters are the keys that delimit the subset. I don't know what they are; I just want the first five of them. So I tried using the values method to get all the values out of the TreeMap, hoping they'd be in order. Then I can just get the first ten.
ArrayList<String> strs = (ArrayList<String>)(treemap.values());
return new ArrayList<String>(strs.subList(0, 5));
Nope. Runtime error: cannot cast TreeMap$Values to ArrayList.
List<String> strs = (List<String>)(treemap.values());
return new ArrayList<String>(strs.subList(0, 5));
Same. Runtime error trying to do the cast. OK, let's just assign to a Collection...
Collection<String> strs = treemap.values();
return new ArrayList<String>(strs.subList(0, 5));
Sorry, subList isn't a method of Collection.
Collection<String> strs = treemap.values();
ArrayList<String> a = new ArrayList<String>(strs);
return new ArrayList<String>(a.subList(0, 5));
Finally, something that works! But two extra data structures just to get the first five elements? And I'm not too wild about using Double as the key for TreeMap.
Is there a better solution?
I don't think you'll get more compact than the three lines above, not in Java.
Apart from that, I have the impression that a Map as a data structure is the wrong choice in the first place, since you do not seem to need by-string lookups (UNLESS you want in some way deal with multiple occurences of strings, but you didn't say so). An alternative approach would be to declare your own comparable data record class:
private static class Record implements Comparable<Record> {
// public final fields ok for this small example
public final String string;
public final double value;
public Record(String string, double value) {
this.string = string;
this.value = value;
}
#Override
public int compareTo(Record other) {
// define sorting according to double fields
return Double.compare(value, other.value);
}
}
// provide size to avoid reallocations
List<Record> records = new ArrayList<Record>(stringList.size());
for(String s : stringList)
records.add(new Record(s, calculateFitness(s));
Collections.sort(records); // sort according to compareTo method
int max = Math.min(10, records.size()); // maximum index
List<String> result = new ArrayList<String>(max);
for(int i = 0; i < max; i++)
result.add(records.get(i).string);
return result;
This is now much more verbose than the three lines above (this is Java, after all), but also includes the code that would be required to insert the key/value pairs into the map.
Would something like the following work for you?
Note that I've assumed you don't require the double value other than to sort the data.
public static void main(String[] args) throws Exception {
List<String> data = new ArrayList<>(Arrays.asList("t", "h", "i", "s", "i", "s", "t", "e", "s", "t", "d", "a", "t", "a"));
Collections.sort(data, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
double o1Value = evaluate(o1);
double o2Value = evaluate(o2);
return Double.compare(o1Value, o2Value);
}
});
List<String> result = data.subList(0, 10); // Note the end point is exclusive
for (String s : result) {
System.out.println(s);
}
}
private static double evaluate(String s) {
return s.codePointAt(0); // Nonsense, I know
}
This example prints:
a
a
d
e
h
i
i
s
s
s
Why don't you just create a class to combine the String, Double and function that does the calculation - something like:
public Thing implements Comparable<Thing>
{
private String s;
private Double d;
public Thing(String s)
{
this.s = s;
this.d = calculateDouble(s);
}
public String getString()
{
return this.s;
}
public Double getDouble()
{
return this.d;
}
public int compareTo(Thing other)
{
return getDouble().compareTo(other.getDouble());
}
public Double calculateDouble(String s)
{
...
}
}
Then all you need is a List<Thing>, Collections.sort and List.subList.

How to create an associative list in Java?

I am trying to have a user enter a String to search for a value in a list. This works fine, but I also want the String to have a numeric value. This way I can get the certain item in the lists price. I tried:
public List<String, double>
However this always gives me an error. How can I store strings and their corresponding numeric value?
Use a Map.
Map<String, Double> someMap = new HashMap<String, Double>();
Then, you can use Map#put(K, V) and Map#get(K) to put and get values.
Check out the Map documentation as well.
From Java 7 onwards, you can omit the generic type within the constructor's declaration:
Map<String, Double> someMap = new HashMap<>();
Are you only storing a String and a Double, or will you eventually need to store more information about each object?
For example, you're talking about storing a name and a price. If this is for a shopping-like program, you would probably be best to store all the information for each product in a new class, and then store the class in the HashMap. For example..
// Class for your product and all related information...
public class Product {
String name;
double price;
String color;
double weight;
public Product(String name, double price, String color, double weight){
this.name = name;
this.price = price;
this.color = color;
this.weight = weight;
}
}
// Now add each Product to a HashMap (in your main class)...
HashMap<String,Product> products = new HashMap<String,Product>();
products.put("Cheese", new Product("Cheese",1.10,"Yellow",0.5);
products.put("Milk", new Product("Milk",2.0,"White",1.5);
You will then be able to query the HashMap for "Cheese" and you'll get the Product and and all the information for it...
Product cheese = products.get("Cheese");
double price = cheese.getPrice();

Categories

Resources