Merging values contained by key in a map (Java) - java

final Multimap<String, Map.Entry<Integer, Integer>>[] actionMap = new Multimap[]{null};
final boolean[] loaded = {false};
db.execute(connection -> {
PreparedStatement statement = null;
ResultSet resultSet = null;
actionMap[0] = ArrayListMultimap.create();
try {
statement = connection.prepareStatement("Blah Blah...
while (resultSet.next()) {
final String name = ...
actionMap[0].put(name, new AbstractMap.SimpleEntry<>(int1, int2));
I have a map where I use SimpleEntry to insert two integer values (int1, int2). On a duplicate key I want to merge values of what's already mapped. My idea is computeIfPresent but I have no idea of the BiFunctions since I'm using AbstractMap.SimpleEntry to enter the two values. Any help would be much appreciated.

Before you put and overwrite, retrieve the existing value.
If you get null, there is no such value and you put as initially intended.
If you get something, merge your new data into something.

Based on input you gave (which could be more complete) it seems that you're trying to use wrong structure for your data. Assuming you want to merge value of the entry if both name and int1 exist, you should use Guava's Table instead. Let's consider this piece of code as an idea:
#Test
public void shouldMergeValues() throws SQLException {
given(resultSet.getString("name"))
.willReturn("name")
.willReturn("name")
.willReturn("name")
.willReturn("name2");
given(resultSet.getInt("key"))
.willReturn(1)
.willReturn(1)
.willReturn(2)
.willReturn(100);
given(resultSet.getInt("value"))
.willReturn(2)
.willReturn(40)
.willReturn(3)
.willReturn(200);
given(resultSet.next())
.willReturn(true)
.willReturn(true)
.willReturn(true)
.willReturn(true)
.willReturn(false);
Table<String, Integer, Integer> actionTable = HashBasedTable.create();
while (resultSet.next()) {
String name = resultSet.getString("name");
int int1 = resultSet.getInt("key");
int int2 = resultSet.getInt("value");
if (actionTable.contains(name, int1)) {
Integer oldInt2 = actionTable.get(name, int1);
actionTable.put(name, int1, oldInt2 + int2); // in this example "+" is the "merge" function
} else {
actionTable.put(name, int1, int2);
}
}
assertThat(actionTable) // {name={1=42, 2=3}, name2={100=200}}
.containsCell("name", 1, 42)
.containsCell("name", 2, 3)
.containsCell("name2", 100, 200)
.hasSize(3);
}
Given 4 rows: ("name", 1, 2), ("name", 1, 40), ("name", 2, 3) and ("name2", 100, 200) and example "merge" operation being addition, you'd get Table {name={1=42, 2=3}, name2={100=200}}. Please check if it fits your use case.

Related

Java streams: return results to custom object

I have a piece of code that has list of objects as follows.
List<PivotMapEgModel> pivotMapList = List.of(new PivotMapEgModel(1L, "1"), new PivotMapEgModel(1L, "2"), new PivotMapEgModel(1L, "3"), new PivotMapEgModel(2L, "5"));
It is guaranteed that there will always be a maximum of 3 codes per value.
I have a class that looks like this:
#Data
#AllArgsConstructor
class ResultSet {
long value;
String code_1;
String code_2;
String code_3;
}
I am currently doing the stream operation in this way:
pivotMapList.stream().collect(Collectors.groupingBy(PivotMapEgModel::getValue, Collectors.mapping(PivotMapEgModel::getCode, Collectors.toList())))
This is producing the output in the following way: {1=[1, 2, 3], 2=[5]}
I need to perform stream operations on the pivotMapList to get the output to show in List<ResultSet> as follows:
[{value=1, code_1=1, code_2=2, code_3=3},
{value=2, code_1=1, code_2=null, code_3=null}]
I am not sure how I can get List<ResultSet> from stream operations
Any help to achieve the desired output would be greatly appreciated! Thank you
You have already mapped value to its codes. You can just continue by streaming the entry set of the resulting map and map entries to ResultSet.
List<ResultSet> result = pivotMapList.stream()
.collect(Collectors.groupingBy(PivotMapEgModel::getValue, Collectors.mapping(PivotMapEgModel::getCode, Collectors.toList())))
.entrySet()
.stream()
.map(entry -> new ResultSet(entry.getKey(), getCode(entry.getValue(), 0), getCode(entry.getValue(), 1), getCode(entry.getValue(), 2)))
.collect(Collectors.toList());
getCode() is simple method taking care not to get exception when retrieving values from the list.
private static String getCode(List<String> codes, int index) {
if (index >= codes.size()) {
return null;
}
return codes.get(index);
}
Here is one way.
class ResultSet {
long value;
String code_1;
String code_2;
String code_3;
public ResultSet(long value, String code_1,String code_2, String code_3) {
this.value = value;
this.code_1 = code_1;
this.code_2 = code_2;
this.code_3 = code_3;
}
public String toString() {
return "{%d, %s, %s, %s}".formatted(value, code_1, code_2, code_3);
}
}
Use your existing map to build the list. Stream the entrySet of the map and use map to instantiate a ResultSet instance. The forloop will fill the list with nulls if it isn't fully populated.
List<ResultSet> resultSet = map.entrySet().stream()
.<ResultSet>map(ent-> {
List<String> lst = ent.getValue();
for( int i = lst.size(); i < 3; i++) {
lst.add(null);
}
return new ResultSet(ent.getKey(), lst.get(0),
lst.get(1), lst.get(2));
}).toList();
resultSet.forEach(System.out::println);
prints
{1, 1, 2, 3}
{2, 5, null, null}
Note that you could simply stream the existing entrySet from the original map to combine the process, returning the desired List<ResultSet>

How to set variable value conditionally

I am trying to set variable value based on conditions and I want to know if there is a better way to do this in Java 8 rather than using multiple if else or switch statements.
I need to set Request Number based on String values
123 for foo,
123 for bar,
456 for xyz,
000 as default value i.e for any other string value.
if(someString.equalsIgnorecase("foo")){
x = someObj.setRequestNumber(123);
}
else if(someString.equalsIgnorecase("bar")){
x = someObj.setRequestNumber(123);
}
else if(someString.equalsIgnorecase("xyz")){
x = someObj.setRequestNumber(456);
}
else{
x = someObj.setRequestNumber(000);
}
Try this:
switch (someString.toUpperCase()) {
case "XYZ":
myNum = 1;
break;
case "FOO":
myNum = 2;
break;
case "BAR":
case "ANOTHER_BAR":
myNum = 3;
break;
default:
myNum = -1;
break;
}
From now on, with the new Switch features (JDK 14+), you can even simplify the Switch statement! Link: https://openjdk.java.net/jeps/361
First of all, you could replace the first two if statements with one:
if (someString.equalsIgnorecase("foo") || someString.equalsIgnorecase("bar")) {
x = someObj.setRequestNumber(123);
} else if (someString.equalsIgnorecase("xyz")) {
x = someObj.setRequestNumber(456);
} else {
x = someObj.setRequestNumber(000);
}
Another way is to create a Map, and lookup the result from the map:
Map<String, Integer> map = Map.of("foo", 123, "bar", 123, "xyz", 456);
x = map.getOrDefault(someString.toLowerCase(), 0);
(Note: Map.of(...) was added in Java 9, if you're using an earlier version of Java you'll have to add the elements separately with map.put("foo", 123); etc.).
Note: the code has been updated adding a toLowerCase when asking the value connected to a particular key. By the way, a key should be uniquely identified and the OP should decide if using lower cases or upper cases for the keys instead of changing the code making ignore case comparisons.
You can create a Map<String, Integer> and use it to set your value (this solution works also in older versions of java, eventually for versions before java 5 don't use generics):
// Init the map with the values for each key
Map<String, Integer> values = new HashMap<>();
values.put("foo", 123);
values.put("bar", 123);
values.put("xyz", 456);
// Use the values map to set your value
someObject.setRequestNumber(value.getOrDefault(someString.toLowerCase(), 0));
Use the getOrDefault method to set a default value if none of the key is present in the values map:
Returns the value to which the specified key is mapped, or defaultValue if this map contains no mapping for the key.
Note that if you need to assign the value 000 as default value this is not a n int. It must be set as a String to hold all the three zeros. Changing it to a String is simple just change the code as follow:
// Init the map with the values for each key
Map<String, String> values = new HashMap<>();
values.put("foo", "123");
values.put("bar", "123");
values.put("xyz", "456");
// Use the values map to set your value
// The method setRequestNumber must receive a String
someObject.setRequestNumber(value.getOrDefault(someString, "000"));
As alternative using the switch the best is to create a method like the following (since java 7):
public String getValueFromKey(String key) {
switch(key.toLowerCase()) {
case "foo":
case "bar": // You can combine multiple case
return "123"; // You can exit directly with a return instead
// of saving the result in a variable and use a break
case "xyz":
return "456";
default: // Use the default to set the handle any value not previously handled
return "000";
}
...
someObj.setRequestNumber(getValueFromKey(someString));
Use a map.
Initialization:
Map<String,Integer> map = new HashMap<>();
map.put("foo", 123);
map.put("bar", 123);
map.put("xyz", 456);
:
Use:
Integer req = map.get(someString.toLowerCase());
if (req != null)
someObj.setRequestNumber(req);
else
error("....!");
This trades off a little initialization code for simplicity elsewhere, and is worthwhile if you have to make 'many' such lookups and have 'many' strings to look up.
You can use a Map
Map<String,Integer> map = new HashMap<>();
map.put("foo",123);
map.put("bar",123);
map.put("xyz",456);
x= map.get(someString);
When faced with many conditions see if you can apply polymorphism .
https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html

Decrementing values from a randomized HashMap

The context here is context is in allocating meals to airline passengers.
Given a HashMap called allMealsList which is extracted from an API response and looks like this:
...I want to choose a meal at random, and return it so an adult passenger has a random meal. Don't worry about the conditional if("adult"), I am handling that elsewhere.
Where I am struggling is in decrementing the correct value from the HashMap.
My method is as follows:
public String chooseAvailableMeals(HashMap<String, Integer> allMealsList, String paxType) {
for (Iterator<Map.Entry<String, Integer>> it = allMealsList.entrySet().iterator(); it.hasNext(); ) {
Random generator = new Random();
Map.Entry<String, Integer> entry = it.next();
if (Objects.equals(paxType, "adult")) {
Object[] meals = allMealsList.keySet().toArray();
Object randomMeal = meals[generator.nextInt(meals.length)];
entry.setValue(entry.getValue() - 1);
return (String) randomMeal;
}
}
Where I do:
entry.setValue(entry.getValue() - 1);
It is, of course, decrementing not the value from the randomised key but of the first key - in this case "BBML".
Bear in mind I'm a relative java novice so I'm sure there is a more elegant or efficient way of doing this ;). Thank you in advance.
You don't need to iterate over the entries of the HashMap, just retrieve the relevant value by its key:
public String chooseAvailableMeals(HashMap<String, Integer> allMealsList, String paxType) {
Random generator = new Random();
if (Objects.equals(paxType, "adult")) {
Object[] meals = allMealsList.keySet().toArray();
Object randomMeal = meals[generator.nextInt(meals.length)];
Integer value = allMealsList.get(randomMeal);
if (value != null && value > 0) {
allMealsList.put(randomMeal, value - 1);
}
return (String) randomMeal;
}
// you should throw some exception or return some default value here
}
P.S. it would be better to create one Random instance outside of this method, and re-use it each time you need to generate a random number.

Retrieve the first and second value from the map

What's the best way to get the first value and second value from the map.
I am trying to read the tableLists map and get the first and second value from the map.
Below is the code I have in which ReadTableConnectionInfo is the class.
private final LinkedHashMap<String, ReadTableConnectionInfo> tableLists;
ReadTableConnectionInfo table = tablePicker();
private ReadTableConnectionInfo tablePicker() {
Random r = new SecureRandom();
ReadTableConnectionInfo table;
if (r.nextFloat() < Read.percentageTable / 100) {
table = get first value from tableLists map
} else {
table = get second value from tableLists map
}
return table;
}
Assuming you are sure that your LinkedHashMap contains at least two values, you could do:
Iterator<Map.Entry<String, ReadTableConnectionInfo >> it = tableLists.entrySet().iterator();
if (r.nextFloat() < Read.percentageTable / 100) {
table = it.next().getValue();
} else { //since you have an else, you have to re-ignore the first value just below
it.next(); // ignoring the first value
table = it.next().getValue(); //repeated here in order to get the second value
}
Iteration of LinkedHashMap values is ordered by the insertion order. So values() is what you need:
Iterator it = values().iterator();
Object first = it.next().getValue();
Object second = it.next().getValue();

Java Parsing Using Hmap

I am new to Java. I want to Parse the data which is in this Format
Apple;Mango;Orange:1234;Orange:1244;...;
There could be more than one "Orange" at any point of time. Numbers (1,2...) increase and accordingly as the "Orange".
Okay. After splitting it, Lets assume I have stored the first two data(Apple, Orange) in a variable(in setter) to return the same in the getter function. And now I want to add the value(1234,1244....etc) in the 'orange' thing into a variable to return it later. Before that i have to check how many oranges have come. For that, i know i have to use for loop. But don't know how to store the "Value" into a variable.
Please Help me guys.
String input = "Apple;Mango;Orange:1234;Orange:1244;...;"
String values[] = input.split(";");
String value1 = values[0];
String value2 = values[1];
Hashmap< String, ArrayList<String> > map = new HashMap<String, ArrayList<String>>();
for(int i = 2; i < values.length; i = i + 2){
String key = values[i];
String id = values[i+1];
if (map.get(key) == null){
map.put(key, new ArrayList<String>());
}
map.get(key).add(id);
}
//for any key s:
// get the values of s
map.get(s); // returns a list of all values added
// get the count of s
map.get(s).size(); // return the total number of values.
Let me try to rephrase the question by how I interpreted it and -- more importantly -- how it focuses on the input and output (expectations), not the actual implementation:
I need to parse the string
"Apple;Mango;Orange:1234;Orange:1244;...;"
in a way so I can retrieve the values associated (numbers after ':') with the fruits:
I should receive an empty list for both the Apple and Mango in the example, because they have no value;
I should receive a list of 1234, 1244 for Orange.
Of course your intuition of HashMap is right on the spot, but someone may always present a better solution if you don't get too involved with the specifics.
There are a few white spots left:
Should the fruits without values have a default value given?
Should the fruits without values be in the map at all?
How input errors should be handled?
How duplicate values should be handled?
Given this context, we can start writing code:
import java.util.*;
public class FruitMarker {
public static void main(String[] args) {
String input = "Apple;Mango;Orange:1234;Orange:1244";
// replace with parameter processing from 'args'
// avoid direct implementations in variable definitions
// also observe the naming referring to the function of the variable
Map<String, Collection<Integer>> fruitIds = new HashMap<String, Collection<Integer>>();
// iterate through items by splitting
for (String item : input.split(";")) {
String[] fruitAndId = item.split(":"); // this will return the same item in an array, if separator is not found
String fruitName = fruitAndId[0];
boolean hasValue = fruitAndId.length > 1;
Collection<Integer> values = fruitIds.get(fruitName);
// if we are accessing the key for the first time, we have to set its value
if (values == null) {
values = new ArrayList<Integer>(); // here I can use concrete implementation
fruitIds.put(fruitName, values); // be sure to put it back in the map
}
if (hasValue) {
int fruitValue = Integer.parseInt(fruitAndId[1]);
values.add(fruitValue);
}
}
// display the entries in table iteratively
for (Map.Entry<String, Collection<Integer>> entry : fruitIds.entrySet()) {
System.out.println(entry.getKey() + " => " + entry.getValue());
}
}
}
If you execute this code, you will get the following output:
Mango => []
Apple => []
Orange => [1234, 1244]

Categories

Resources